@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,18 +1,18 @@
|
|
|
1
|
-
import { EFFECT_RENDER, EFFECT_USER, STATUS_PENDING, REACTIVE_IN_HEAP_HEIGHT, REACTIVE_DISPOSED, CONFIG_HELD_TRUTH, REACTIVE_ZOMBIE, CONFIG_HAS_LANE, CONFIG_HAS_SNAPSHOT, REACTIVE_RECOMPUTING_DEPS, REACTIVE_MISSED_WAKE, CONFIG_IN_SNAPSHOT_SCOPE, REACTIVE_SNAPSHOT_STALE,
|
|
1
|
+
import { EFFECT_RENDER, EFFECT_USER, STATUS_PENDING, CONFIG_ADOPTED_UNFLUSHED, NOT_PENDING, REACTIVE_MANUAL_WRITE, STATUS_UNINITIALIZED, REACTIVE_OPTIMISTIC_DIRTY, REACTIVE_IN_HEAP_HEIGHT, REACTIVE_DISPOSED, CONFIG_HELD_TRUTH, CONFIG_HAS_COMPANIONS, EFFECT_TRACKED, CONFIG_INPUTS_PUBLISHED, REACTIVE_ZOMBIE, CONFIG_HAS_LANE, CONFIG_HAS_SNAPSHOT, REACTIVE_RECOMPUTING_DEPS, REACTIVE_MISSED_WAKE, CONFIG_IN_SNAPSHOT_SCOPE, REACTIVE_SNAPSHOT_STALE, CONFIG_SLOT_NODE, CONFIG_HELD_CHILDREN, REACTIVE_REASK, CONFIG_AUTHORITATIVE_READ } from "./constants.js";
|
|
2
2
|
|
|
3
3
|
import { attrHooks } from "./attribution-hooks.js";
|
|
4
4
|
|
|
5
|
-
import { currentOptimisticLane, slotUnobservedHook } from "./core.js";
|
|
5
|
+
import { resyncUnflushedCompanions, currentOptimisticLane, slotUnobservedHook, markUnflushedStaged, hasActiveOverride } from "./core.js";
|
|
6
6
|
|
|
7
7
|
import { DEV, GRAPH_SIZE_WARN_AT, noteFanOut } from "./dev.js";
|
|
8
8
|
|
|
9
9
|
import { NotReadyError } from "./error.js";
|
|
10
10
|
|
|
11
|
-
import { sweepDormant } from "./graph.js";
|
|
11
|
+
import { sweepDormant, trimStaleDeps } from "./graph.js";
|
|
12
12
|
|
|
13
13
|
import { runHeap, deleteFromHeap, enqueueSub } from "./heap.js";
|
|
14
14
|
|
|
15
|
-
import { activeLanes, assignOrMergeLane, findLane
|
|
15
|
+
import { activeLanes, assignOrMergeLane, findLane } from "./lanes.js";
|
|
16
16
|
|
|
17
17
|
export { getOrCreateLane, mergeLanes, resolveLane } from "./lanes.js";
|
|
18
18
|
|
|
@@ -23,23 +23,31 @@ const transitions = new Set;
|
|
|
23
23
|
const dirtyQueue = {
|
|
24
24
|
eE: new Array(2e3).fill(undefined),
|
|
25
25
|
tE: false,
|
|
26
|
-
|
|
26
|
+
Ct: 0,
|
|
27
27
|
EE: 0
|
|
28
28
|
};
|
|
29
29
|
|
|
30
30
|
const zombieQueue = {
|
|
31
31
|
eE: new Array(2e3).fill(undefined),
|
|
32
32
|
tE: false,
|
|
33
|
-
|
|
33
|
+
Ct: 0,
|
|
34
34
|
EE: 0
|
|
35
35
|
};
|
|
36
36
|
|
|
37
37
|
/** runHeap callback that discards a queued zombie recompute instead of running
|
|
38
38
|
* it: unlink pure recompute entries; strip just the recompute bit from dirtied
|
|
39
|
-
* height-adjust entries so their height work still happens.
|
|
40
|
-
|
|
39
|
+
* height-adjust entries so their height work still happens. A zombie dirtied
|
|
40
|
+
* through the lane channel (OPTIMISTIC_DIRTY — an override or a `latest()`
|
|
41
|
+
* companion) runs instead (#3444): a zombie renders mainline until the commit
|
|
42
|
+
* disposes it, and the lane's values ARE the mainline frame — the still-visible
|
|
43
|
+
* branch a held `Show` is removing showed the old `latest(count)` beside the
|
|
44
|
+
* new one outside. Its pass runs under the lane and its run lands on the
|
|
45
|
+
* lane's effect queue, so a held lane defers it exactly as it defers every
|
|
46
|
+
* other reader's. */ function cancelZombieRecompute(e) {
|
|
47
|
+
if (e.ft & REACTIVE_OPTIMISTIC_DIRTY) return GlobalQueue._t(e);
|
|
48
|
+
if (e.ft & REACTIVE_IN_HEAP_HEIGHT) e.ft &= -12; else {
|
|
41
49
|
deleteFromHeap(e, zombieQueue);
|
|
42
|
-
e.
|
|
50
|
+
e.ft &= -4;
|
|
43
51
|
}
|
|
44
52
|
}
|
|
45
53
|
|
|
@@ -68,15 +76,23 @@ function exitActionStep() {
|
|
|
68
76
|
actionStepDepth--;
|
|
69
77
|
}
|
|
70
78
|
|
|
71
|
-
// Store property nodes
|
|
72
|
-
//
|
|
73
|
-
//
|
|
74
|
-
//
|
|
79
|
+
// Store property nodes whose last subscriber left while they carried state
|
|
80
|
+
// the backing cannot reconstruct — an optimistic override (overrides live on
|
|
81
|
+
// nodes, over a clone the setter discards) or a staged write. Releasing the
|
|
82
|
+
// slot then would drop the override: an optimistic store key read `0` the
|
|
83
|
+
// moment its only reader gated away while the action was live (S7). Swept
|
|
84
|
+
// after each flush — a node still without subs whose override and staging
|
|
85
|
+
// have resolved is released through the slot hook; one that regained a
|
|
86
|
+
// subscriber leaves the set.
|
|
75
87
|
const transientStoreNodes = new Set;
|
|
76
88
|
|
|
89
|
+
/** Slot hook's deferral: release this node when its carried state resolves. */ function deferSlotRelease(e) {
|
|
90
|
+
transientStoreNodes.add(e);
|
|
91
|
+
}
|
|
92
|
+
|
|
77
93
|
function canUseSimpleSyncFlush(e) {
|
|
78
94
|
const t = e.m;
|
|
79
|
-
return transitions.size === 0 && activeLanes.size === 0 && e.
|
|
95
|
+
return transitions.size === 0 && activeLanes.size === 0 && e.Bn.length === 0 && t.tn.length === 0 && t.A.length === 0 && t.rn.size === 0 && transientStoreNodes.size === 0;
|
|
80
96
|
}
|
|
81
97
|
|
|
82
98
|
function sweepTransientStoreNodes() {
|
|
@@ -86,14 +102,14 @@ function sweepTransientStoreNodes() {
|
|
|
86
102
|
transientStoreNodes.delete(e);
|
|
87
103
|
continue;
|
|
88
104
|
}
|
|
89
|
-
if (e.
|
|
90
|
-
if (e.o?.
|
|
105
|
+
if (e._e !== NOT_PENDING) continue;
|
|
106
|
+
if (e.o?.Ne !== undefined && e.o?.Ne !== NOT_PENDING) continue;
|
|
91
107
|
// A live affects() mark keeps the node addressable: sweeping it would
|
|
92
108
|
// detach the refcount from the slot (a fresh probe would upsert a new,
|
|
93
109
|
// unmarked node for the same property).
|
|
94
110
|
if (e.o?.t) continue;
|
|
95
111
|
transientStoreNodes.delete(e);
|
|
96
|
-
if (e.
|
|
112
|
+
if (e.C & CONFIG_SLOT_NODE) slotUnobservedHook(e); else e.o?.Kt?.();
|
|
97
113
|
}
|
|
98
114
|
}
|
|
99
115
|
|
|
@@ -120,34 +136,35 @@ function setProjectionWriteActive(e) {
|
|
|
120
136
|
* land there directly — no per-field aliasing.
|
|
121
137
|
*/ function createBatch() {
|
|
122
138
|
return {
|
|
123
|
-
|
|
139
|
+
fe: clock,
|
|
140
|
+
sn: [],
|
|
141
|
+
ot: new Map,
|
|
124
142
|
tn: [],
|
|
125
|
-
Ie: new Map,
|
|
126
|
-
en: [],
|
|
127
143
|
A: [],
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
144
|
+
rn: new Set,
|
|
145
|
+
dt: [],
|
|
146
|
+
qn: {
|
|
147
|
+
Wn: [ [], [] ],
|
|
148
|
+
Bn: []
|
|
133
149
|
},
|
|
134
|
-
|
|
135
|
-
|
|
150
|
+
yt: false,
|
|
151
|
+
Ht: new Set,
|
|
136
152
|
xt: null
|
|
137
153
|
};
|
|
138
154
|
}
|
|
139
155
|
|
|
140
156
|
function mergeTransitionState(e, t) {
|
|
141
157
|
if (attrHooks !== null) attrHooks.transitionMerged(e, t);
|
|
142
|
-
t.
|
|
143
|
-
e.
|
|
144
|
-
|
|
145
|
-
if (t
|
|
158
|
+
t.yt = e;
|
|
159
|
+
e.dt.push(...t.dt);
|
|
160
|
+
e.Tt ||= t.Tt;
|
|
161
|
+
for (const n of activeLanes) if (n.Te === t) n.Te = e;
|
|
162
|
+
if (t.tn.length) {
|
|
146
163
|
// Move (don't copy): the global queue's batch may still be the outgoing
|
|
147
164
|
// transition, and the adoption pass in initTransition would re-push its
|
|
148
165
|
// contents into the target — duplicating every entry.
|
|
149
|
-
e.
|
|
150
|
-
t.
|
|
166
|
+
e.tn.push(...t.tn);
|
|
167
|
+
t.tn.length = 0;
|
|
151
168
|
}
|
|
152
169
|
if (t.A.length) {
|
|
153
170
|
// Move (don't copy): the global queue's batch may still be the outgoing
|
|
@@ -156,13 +173,13 @@ function mergeTransitionState(e, t) {
|
|
|
156
173
|
e.A.push(...t.A);
|
|
157
174
|
t.A.length = 0;
|
|
158
175
|
}
|
|
159
|
-
for (const
|
|
160
|
-
for (const [
|
|
161
|
-
let t = e.
|
|
162
|
-
if (!t) e.
|
|
163
|
-
for (const e of
|
|
176
|
+
for (const n of t.rn) e.rn.add(n);
|
|
177
|
+
for (const [n, i] of t.ot) {
|
|
178
|
+
let t = e.ot.get(n);
|
|
179
|
+
if (!t) e.ot.set(n, t = new Set);
|
|
180
|
+
for (const e of i) t.add(e);
|
|
164
181
|
}
|
|
165
|
-
for (const
|
|
182
|
+
for (const n of t.Ht) e.Ht.add(n);
|
|
166
183
|
if (t.xt) (e.xt ??= []).push(...t.xt);
|
|
167
184
|
}
|
|
168
185
|
|
|
@@ -184,7 +201,7 @@ function mergeTransitionState(e, t) {
|
|
|
184
201
|
* `until()` retain this.
|
|
185
202
|
*/ function entangleConfirmingTransitions(e, t) {
|
|
186
203
|
t = currentTransition(t);
|
|
187
|
-
if (t.
|
|
204
|
+
if (t.yt === true) return;
|
|
188
205
|
// The confirming evidence is a dep whose value is STAGED (pending,
|
|
189
206
|
// uncommitted) at flip evaluation — committed deps are public already and
|
|
190
207
|
// carry nothing to entangle. A staged dep lives in one of two carriers: a
|
|
@@ -196,22 +213,22 @@ function mergeTransitionState(e, t) {
|
|
|
196
213
|
// and stashes are its own future (a live stream's flight must not chain
|
|
197
214
|
// the awaiting transaction to landings that haven't happened; a merged
|
|
198
215
|
// reporter deadlocked exactly that way).
|
|
199
|
-
let
|
|
200
|
-
for (let
|
|
201
|
-
const
|
|
202
|
-
if (
|
|
203
|
-
const e =
|
|
204
|
-
const
|
|
216
|
+
let n = false;
|
|
217
|
+
for (let i = e.ee; i !== null; i = i.te) {
|
|
218
|
+
const s = i.re;
|
|
219
|
+
if (s._e !== NOT_PENDING) {
|
|
220
|
+
const e = s.Te;
|
|
221
|
+
const i = e != null ? currentTransition(e) : null;
|
|
205
222
|
// Skip the awaiting transaction's own cargo (t === target: a
|
|
206
223
|
// fold-staged landing or a write the action itself issued — hold and
|
|
207
224
|
// reveal already correct) and dead carriers. Ambient-batch staging
|
|
208
225
|
// (t === null) must leave the batch NOW — it commits at this flush's
|
|
209
226
|
// end, which would reveal the confirmation under the live optimism
|
|
210
227
|
// it just confirmed.
|
|
211
|
-
const o =
|
|
212
|
-
if (o !== null)
|
|
228
|
+
const o = i === null ? currentBatch.sn : i !== t && i.yt !== true ? i.sn : null;
|
|
229
|
+
if (o !== null) n = stealEntangledCargo(o, t) || n;
|
|
213
230
|
}
|
|
214
|
-
if (
|
|
231
|
+
if (i === e.Fe) break;
|
|
215
232
|
}
|
|
216
233
|
// The steal never activates the awaiting transaction: the predicate can
|
|
217
234
|
// flip inside another transaction's finalize heap, and adopting the queue
|
|
@@ -240,20 +257,20 @@ function mergeTransitionState(e, t) {
|
|
|
240
257
|
* committed value — while re-running them would re-derive the OLD world
|
|
241
258
|
* and re-stage it over the held truth. The reveal re-notifies
|
|
242
259
|
* (commitPendingNodes), which is when they re-derive for real. */ function stealEntangledCargo(e, t) {
|
|
243
|
-
if (e === t.
|
|
244
|
-
for (let
|
|
245
|
-
const
|
|
246
|
-
|
|
247
|
-
t.
|
|
260
|
+
if (e === t.sn || e.length === 0) return false;
|
|
261
|
+
for (let n = 0; n < e.length; n++) {
|
|
262
|
+
const i = e[n];
|
|
263
|
+
i.Te = t;
|
|
264
|
+
t.sn.push(i);
|
|
248
265
|
// Override-covered nodes stay silent AND unmasked: the override is the
|
|
249
266
|
// display (A17 — its staging never notified, its revert will), so their
|
|
250
267
|
// subs saw nothing and re-running one would break the silence with a
|
|
251
268
|
// duplicate fire of an unchanged view.
|
|
252
|
-
if (!hasActiveOverride(
|
|
253
|
-
|
|
254
|
-
for (let e =
|
|
255
|
-
const t = e.
|
|
256
|
-
if (t.
|
|
269
|
+
if (!hasActiveOverride(i)) {
|
|
270
|
+
i.C |= CONFIG_HELD_TRUTH;
|
|
271
|
+
for (let e = i.u; e !== null; e = e.ie) {
|
|
272
|
+
const t = e.se;
|
|
273
|
+
if (t.Pe && !(t.C & CONFIG_AUTHORITATIVE_READ)) enqueueSub(t);
|
|
257
274
|
}
|
|
258
275
|
}
|
|
259
276
|
}
|
|
@@ -269,14 +286,51 @@ function schedule() {
|
|
|
269
286
|
}
|
|
270
287
|
if (scheduled) return;
|
|
271
288
|
scheduled = true;
|
|
272
|
-
if (!syncDepth && !globalQueue.
|
|
289
|
+
if (!syncDepth && !globalQueue.Sn && !projectionWriteActive) queueMicrotask(flush);
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
/**
|
|
293
|
+
* Parked transactions whose reporter set changed without a write. A
|
|
294
|
+
* transaction completes when nothing live reports a flight it waits on, but
|
|
295
|
+
* the flush only judges the ACTIVE transaction: a parked one is re-entered by
|
|
296
|
+
* a stamped node's landing or an action's resume. A reporter that stops
|
|
297
|
+
* counting for another reason — its loading boundary flipped to the fallback
|
|
298
|
+
* (#3375), or it was disposed by ambient work (#3372) — is neither: the
|
|
299
|
+
* pruning in `reporterBlocksSource` would drop it at the next check, but no
|
|
300
|
+
* check comes, and the writes held with it stay staged. Such sites record the
|
|
301
|
+
* transaction here (deduped: one idle pass per transaction, however many
|
|
302
|
+
* reporters changed); the flush re-enters it on an otherwise idle pass, so
|
|
303
|
+
* the re-evaluation adopts no unrelated ambient work.
|
|
304
|
+
*/ const wokenTransitions = [];
|
|
305
|
+
|
|
306
|
+
/** Wake every parked transaction — for a site that knows a reporter stopped
|
|
307
|
+
* counting but not whose (a boundary reset). */ function wakeParked() {
|
|
308
|
+
for (const e of transitions) wokenTransitions.includes(e) || wokenTransitions.push(e);
|
|
309
|
+
schedule();
|
|
273
310
|
}
|
|
274
311
|
|
|
312
|
+
/** Transactions a mainline tick has PROPOSED against (A34, #3494): a write to a
|
|
313
|
+
* node one of them holds — the same value or another — is a second proposal
|
|
314
|
+
* on a contested node, and the tick reveals with the hold ("both are
|
|
315
|
+
* suggesting a value; if one finished before the other that would be odd").
|
|
316
|
+
* Entered at the next flush's start, where the ambient batch is adopted;
|
|
317
|
+
* never from the write itself, which left `activeTransition` set across the
|
|
318
|
+
* caller's block and made creation after the write the transaction's (A29). */ const batchJoins = [];
|
|
319
|
+
|
|
275
320
|
/**
|
|
276
321
|
* Permanently halts the reactive system. Called when a user error escapes
|
|
277
322
|
* every boundary — app state is undefined at that point, so scheduling stops
|
|
278
323
|
* entirely rather than limping along with a half-applied update.
|
|
279
|
-
*/
|
|
324
|
+
*/
|
|
325
|
+
/**
|
|
326
|
+
* The key a root owner carries its client error hook under (`render`'s
|
|
327
|
+
* `onError`) — registered, so a runtime writes it with no import of the hook
|
|
328
|
+
* module (core/error-hooks.ts) and no property mangling in the way. Defined
|
|
329
|
+
* HERE, not there: a runtime that only writes the key must not retain the
|
|
330
|
+
* hook machinery (pay-for-use).
|
|
331
|
+
*/ const ROOT_ERROR_HOOK = Symbol.for("solid-js/root-error-hook");
|
|
332
|
+
|
|
333
|
+
function haltReactivity(e) {
|
|
280
334
|
if (halted) return;
|
|
281
335
|
halted = true;
|
|
282
336
|
let t = "[REACTIVITY_HALTED]";
|
|
@@ -289,9 +343,9 @@ function schedule() {
|
|
|
289
343
|
// nothing an app or its telemetry can act on (#3338 — an uncaught throw
|
|
290
344
|
// during the hydration render). The rethrow may reach the top as well in
|
|
291
345
|
// the non-swallowed cases; a duplicate report beats a silent one.
|
|
292
|
-
const
|
|
293
|
-
|
|
294
|
-
|
|
346
|
+
const n = e !== undefined && globalThis.reportError;
|
|
347
|
+
n || e === undefined ? console.error(t) : console.error(t, e);
|
|
348
|
+
n && n(e);
|
|
295
349
|
}
|
|
296
350
|
|
|
297
351
|
// Logs on the first write after a halt so a frozen interaction is traceable.
|
|
@@ -311,30 +365,30 @@ function notifyHalted() {
|
|
|
311
365
|
let queueRunToken = 0;
|
|
312
366
|
|
|
313
367
|
class Queue {
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
368
|
+
_parent=null;
|
|
369
|
+
Wn=[ [], [] ];
|
|
370
|
+
Bn=[];
|
|
371
|
+
Zn=0;
|
|
318
372
|
created=clock;
|
|
319
373
|
addChild(e) {
|
|
320
|
-
this.
|
|
321
|
-
e.
|
|
374
|
+
this.Bn.push(e);
|
|
375
|
+
e._parent = this;
|
|
322
376
|
}
|
|
323
377
|
removeChild(e) {
|
|
324
|
-
const t = this.
|
|
378
|
+
const t = this.Bn.indexOf(e);
|
|
325
379
|
if (t >= 0) {
|
|
326
|
-
this.
|
|
327
|
-
e.
|
|
380
|
+
this.Bn.splice(t, 1);
|
|
381
|
+
e._parent = null;
|
|
328
382
|
}
|
|
329
383
|
}
|
|
330
|
-
notify(e, t,
|
|
331
|
-
if (this.
|
|
384
|
+
notify(e, t, n, i) {
|
|
385
|
+
if (this._parent) return this._parent.notify(e, t, n, i);
|
|
332
386
|
return false;
|
|
333
387
|
}
|
|
334
388
|
run(e) {
|
|
335
|
-
if (this.
|
|
336
|
-
const t = this.
|
|
337
|
-
this.
|
|
389
|
+
if (this.Wn[e - 1].length) {
|
|
390
|
+
const t = this.Wn[e - 1];
|
|
391
|
+
this.Wn[e - 1] = [];
|
|
338
392
|
runQueue(t, e);
|
|
339
393
|
}
|
|
340
394
|
// Effects run here can dispose owners, and disposal removes queues from
|
|
@@ -344,29 +398,29 @@ class Queue {
|
|
|
344
398
|
// can be recovered by rescanning from the front and every child still runs
|
|
345
399
|
// exactly once. Children appended mid-pass carry a stale stamp and run,
|
|
346
400
|
// matching the previous live-array behaviour.
|
|
347
|
-
const t = this.
|
|
348
|
-
const
|
|
349
|
-
for (let
|
|
350
|
-
const
|
|
351
|
-
if (
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
if (t[
|
|
355
|
-
|
|
401
|
+
const t = this.Bn;
|
|
402
|
+
const n = ++queueRunToken;
|
|
403
|
+
for (let i = 0; i < t.length; ) {
|
|
404
|
+
const s = t[i];
|
|
405
|
+
if (s.Zn !== n) {
|
|
406
|
+
s.Zn = n;
|
|
407
|
+
s.run?.(e);
|
|
408
|
+
if (t[i] !== s) {
|
|
409
|
+
i = 0;
|
|
356
410
|
continue;
|
|
357
411
|
}
|
|
358
412
|
}
|
|
359
|
-
|
|
413
|
+
i++;
|
|
360
414
|
}
|
|
361
415
|
}
|
|
362
416
|
enqueue(e, t) {
|
|
363
417
|
if (e) {
|
|
364
418
|
// Route to lane's effect queue if we're in an optimistic recomputation
|
|
365
419
|
if (currentOptimisticLane) {
|
|
366
|
-
const
|
|
367
|
-
|
|
420
|
+
const n = findLane(currentOptimisticLane);
|
|
421
|
+
n.vn[e - 1].push(t);
|
|
368
422
|
} else {
|
|
369
|
-
this.
|
|
423
|
+
this.Wn[e - 1].push(t);
|
|
370
424
|
}
|
|
371
425
|
}
|
|
372
426
|
schedule();
|
|
@@ -375,42 +429,42 @@ class Queue {
|
|
|
375
429
|
// Attribution hook: the parking transition's lane effects have run; its
|
|
376
430
|
// queues are being stashed. Root call only (children recurse below).
|
|
377
431
|
if (attrHooks !== null && this === globalQueue) attrHooks.holdEnd();
|
|
378
|
-
e.
|
|
379
|
-
e.
|
|
380
|
-
this.
|
|
381
|
-
for (let t = 0; t < this.
|
|
382
|
-
let
|
|
383
|
-
let
|
|
384
|
-
if (!
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
432
|
+
e.Wn[0].push(...this.Wn[0]);
|
|
433
|
+
e.Wn[1].push(...this.Wn[1]);
|
|
434
|
+
this.Wn = [ [], [] ];
|
|
435
|
+
for (let t = 0; t < this.Bn.length; t++) {
|
|
436
|
+
let n = this.Bn[t];
|
|
437
|
+
let i = e.Bn[t];
|
|
438
|
+
if (!i) {
|
|
439
|
+
i = {
|
|
440
|
+
Wn: [ [], [] ],
|
|
441
|
+
Bn: []
|
|
388
442
|
};
|
|
389
|
-
e.
|
|
443
|
+
e.Bn[t] = i;
|
|
390
444
|
}
|
|
391
|
-
|
|
445
|
+
n.stashQueues(i);
|
|
392
446
|
}
|
|
393
447
|
}
|
|
394
448
|
restoreQueues(e) {
|
|
395
|
-
this.
|
|
396
|
-
this.
|
|
397
|
-
for (let t = 0; t < e.
|
|
398
|
-
const
|
|
399
|
-
let
|
|
400
|
-
if (
|
|
449
|
+
this.Wn[0].push(...e.Wn[0]);
|
|
450
|
+
this.Wn[1].push(...e.Wn[1]);
|
|
451
|
+
for (let t = 0; t < e.Bn.length; t++) {
|
|
452
|
+
const n = e.Bn[t];
|
|
453
|
+
let i = this.Bn[t];
|
|
454
|
+
if (i) i.restoreQueues(n);
|
|
401
455
|
}
|
|
402
456
|
}
|
|
403
457
|
}
|
|
404
458
|
|
|
405
459
|
class GlobalQueue extends Queue {
|
|
406
|
-
|
|
460
|
+
Sn=false;
|
|
407
461
|
// The current transaction-shaped batch: a plain ambient batch while no
|
|
408
462
|
// transition is active, the active transition itself after initTransition.
|
|
409
463
|
m=createBatch();
|
|
410
|
-
static
|
|
411
|
-
static
|
|
412
|
-
static
|
|
413
|
-
static
|
|
464
|
+
static _t;
|
|
465
|
+
static It;
|
|
466
|
+
static Lt;
|
|
467
|
+
static zn=null;
|
|
414
468
|
// Store-side hook: drops a keyless affects() mark's identity scope when the
|
|
415
469
|
// carrier node's last registration releases (wired by store.ts, mirroring
|
|
416
470
|
// _clearOptimisticStore).
|
|
@@ -421,30 +475,30 @@ class GlobalQueue extends Queue {
|
|
|
421
475
|
// once the gate holds.
|
|
422
476
|
static G=null;
|
|
423
477
|
static M=null;
|
|
424
|
-
static
|
|
478
|
+
static j=null;
|
|
425
479
|
// External-source bridge (wired by enableExternalSource(); null while no
|
|
426
480
|
// config is active — including after _resetExternalSourceConfig()).
|
|
427
|
-
static
|
|
428
|
-
static
|
|
481
|
+
static _n=null;
|
|
482
|
+
static In=null;
|
|
429
483
|
// Verdict-layer hooks (wired by verdict.ts when isPending()/latest() are
|
|
430
484
|
// imported; null in apps that never use them). Call sites either guard for
|
|
431
485
|
// null or sit behind state only the verdict layer can create (`!` is safe
|
|
432
486
|
// there: `_pendingSignal`/`_latestValueComputed` are only ever assigned by
|
|
433
487
|
// verdict.ts, and `pendingCheckActive`/`latestReadActive` only flip inside
|
|
434
488
|
// isPending()/latest()).
|
|
435
|
-
static
|
|
436
|
-
static
|
|
437
|
-
static
|
|
438
|
-
static
|
|
439
|
-
static In=null;
|
|
440
|
-
static Nn=null;
|
|
489
|
+
static De=null;
|
|
490
|
+
static le=null;
|
|
491
|
+
static be=null;
|
|
492
|
+
static Qn=null;
|
|
441
493
|
static Cn=null;
|
|
442
|
-
static
|
|
494
|
+
static pn=null;
|
|
495
|
+
static Pn=null;
|
|
496
|
+
static bt=null;
|
|
443
497
|
static k=null;
|
|
444
|
-
static
|
|
498
|
+
static Kn=null;
|
|
445
499
|
// Re-asks probes whose verdict was provisionally suppressed by a fresh read
|
|
446
500
|
// of a held value, once the transaction gains an async blocker (#3028).
|
|
447
|
-
static
|
|
501
|
+
static Yn=null;
|
|
448
502
|
// Optimistic-engine hooks (wired by core/optimistic.ts via
|
|
449
503
|
// installOptimisticEngine(), called from verdict.ts / createOptimistic /
|
|
450
504
|
// createOptimisticStore — every module that can create optimistic state).
|
|
@@ -452,31 +506,31 @@ class GlobalQueue extends Queue {
|
|
|
452
506
|
// `_overrideValue` slot, a lane in `activeLanes`, an `_optimisticNodes`
|
|
453
507
|
// entry, or a non-null `currentOptimisticLane`, so `!` invocations are safe
|
|
454
508
|
// once the gate holds.
|
|
455
|
-
static
|
|
509
|
+
static mn=null;
|
|
456
510
|
static Un=null;
|
|
457
|
-
static
|
|
458
|
-
static
|
|
459
|
-
static
|
|
511
|
+
static kn=null;
|
|
512
|
+
static xn=null;
|
|
513
|
+
static Mn=null;
|
|
460
514
|
/** Patch-channel optimistic drain (next/patch.ts): optimistic emissions
|
|
461
515
|
* apply at lane-effect timing — visible in flight, unlike the regular
|
|
462
516
|
* effect queues an action stashes. Injected; null when unused. */
|
|
463
|
-
static
|
|
464
|
-
static
|
|
465
|
-
static
|
|
517
|
+
static Vn=null;
|
|
518
|
+
static hn=null;
|
|
519
|
+
static Gn=null;
|
|
466
520
|
/** Is the node routed through a LIVE lane (`resolveLane`)? read()'s reveal
|
|
467
521
|
* carve-out asks before showing a foreign-held pending node's committed
|
|
468
522
|
* value: a lane-derived flight's inputs are already revealed through the
|
|
469
523
|
* lane (#3334). Gated on CONFIG_HAS_LANE, which only the engine sets. */
|
|
470
|
-
static
|
|
471
|
-
static
|
|
472
|
-
static
|
|
473
|
-
static
|
|
524
|
+
static Rn=null;
|
|
525
|
+
static An=null;
|
|
526
|
+
static gt=null;
|
|
527
|
+
static kt=null;
|
|
474
528
|
/** Authoritative-view reader wakeup: installed by until() and refresh() before
|
|
475
529
|
* their first read. Call sites are gated by CONFIG_AUTHORITATIVE_OBSERVED, which
|
|
476
530
|
* only such a reader's carve-out read can set, so `!` invocations are safe once
|
|
477
531
|
* the gate holds (#3303). */
|
|
478
|
-
static
|
|
479
|
-
static
|
|
532
|
+
static Tn=null;
|
|
533
|
+
static vt=null;
|
|
480
534
|
/** A18 supersession (#3331): own-source truth `value` landed under an active
|
|
481
535
|
* override. The engine decides whether the graph re-derives — the value
|
|
482
536
|
* differs from the override and is not a stale (older-action) answer (mark
|
|
@@ -485,53 +539,91 @@ class GlobalQueue extends Queue {
|
|
|
485
539
|
* authoritative-observer wake for a silent confirm. Installed with the
|
|
486
540
|
* optimistic engine; only reachable on a node that has an override. */
|
|
487
541
|
static Oe=null;
|
|
542
|
+
/** The flush's pre-verdict step (#3427): once the transaction's action
|
|
543
|
+
* bodies have all ended and nothing authoritative is left in flight, the
|
|
544
|
+
* engine supersedes every override still in force with the truth it
|
|
545
|
+
* reverts to, so the graph re-derives from it now, as the transaction's
|
|
546
|
+
* held work, instead of after the flights the overrides fed have landed.
|
|
547
|
+
* True when it superseded something: the caller re-runs the heap ahead of
|
|
548
|
+
* the verdict. The engine owns every gate (acted, actions drained, has
|
|
549
|
+
* overrides, no store edits, no authoritative flight); null without it. */
|
|
550
|
+
static yn=null;
|
|
488
551
|
/** read()'s value for a TRACKED reader of a superseded node (#3331): the
|
|
489
552
|
* staged truth, unless the reader is a stale (render) reader of another
|
|
490
553
|
* transaction — then the displayed override, as it keeps a foreign
|
|
491
554
|
* transaction's committed value over its staged write. */
|
|
492
|
-
|
|
555
|
+
/** A tracked read of an active override: the lane outside-view rule
|
|
556
|
+
* (#3460) and the A18 supersession selection (#3331) — see optimistic.ts. */
|
|
557
|
+
static Dn=null;
|
|
558
|
+
/** A lane pass's publish for a memo (#3479, lanes stage): the speculative
|
|
559
|
+
* result becomes a DERIVED override, `_value` stays committed — see
|
|
560
|
+
* optimistic.ts laneOverride. Set with the engine, which a lane implies. */
|
|
561
|
+
static ge=null;
|
|
562
|
+
/** Verdict-layer recompute in progress (companion creation, latest()/
|
|
563
|
+
* isPending() pulls): never born held — see core.ts enterStagedRead. */
|
|
564
|
+
static On=false;
|
|
493
565
|
/** setSignal's authoritative (projection-write) landing on an override-
|
|
494
566
|
* covered node (#3331 store twin): stage the truth for its transaction's
|
|
495
567
|
* commit whatever its relation to the committed value — a landing equal to
|
|
496
568
|
* committed still differs from the override — then _supersedeOverride
|
|
497
569
|
* decides. Installed with the optimistic engine; only reachable on a node
|
|
498
570
|
* that has an override. */
|
|
499
|
-
static
|
|
500
|
-
static
|
|
571
|
+
static Fn=null;
|
|
572
|
+
static jn=null;
|
|
501
573
|
flush() {
|
|
502
|
-
if (this.
|
|
574
|
+
if (this.Sn) return;
|
|
503
575
|
// Fast drain: nothing in flight but plain pending commits — no dirty
|
|
504
576
|
// computeds, no queued effects, no child queues, no transitions/lanes/
|
|
505
577
|
// optimistic state. Commit and go; anything a commit hook schedules
|
|
506
578
|
// (companion snaps, store folds notifying subs) re-arms `scheduled`
|
|
507
579
|
// below and the outer drain loop takes the full spine next round.
|
|
508
|
-
if (activeTransition === null && dirtyQueue.EE < dirtyQueue.
|
|
509
|
-
|
|
580
|
+
if (activeTransition === null && dirtyQueue.EE < dirtyQueue.Ct && this.Wn[0].length === 0 && this.Wn[1].length === 0 && this.Bn.length === 0 && !wokenTransitions.length && !batchJoins.length && // a join must drain in its own tick (#3519 review)
|
|
581
|
+
canUseSimpleSyncFlush(this)) {
|
|
582
|
+
this.Sn = true;
|
|
510
583
|
try {
|
|
584
|
+
// A28: companions of nodes written since the last flush mirror the
|
|
585
|
+
// flushed world — re-synced inside the running window (the write is
|
|
586
|
+
// "flushed" from here on).
|
|
587
|
+
resyncUnflushedCompanions();
|
|
511
588
|
// Sweep first: unobserved() pulls swept nodes out of the dirty heap,
|
|
512
589
|
// so a dormant memo dirtied in the same tick is reclaimed instead of
|
|
513
590
|
// recomputed (matching the old inline dispose-on-read counts).
|
|
514
|
-
|
|
591
|
+
sweepDormant();
|
|
515
592
|
commitPendingNodes();
|
|
516
593
|
} finally {
|
|
517
|
-
this.
|
|
594
|
+
this.Sn = false;
|
|
518
595
|
}
|
|
519
596
|
clock++;
|
|
520
|
-
scheduled = dirtyQueue.EE >= dirtyQueue.
|
|
597
|
+
scheduled = dirtyQueue.EE >= dirtyQueue.Ct || this.Wn[0].length !== 0 || this.Wn[1].length !== 0 || this.m.sn.length !== 0;
|
|
521
598
|
return;
|
|
522
599
|
}
|
|
523
|
-
this.
|
|
524
|
-
|
|
600
|
+
this.Sn = true;
|
|
601
|
+
resyncUnflushedCompanions();
|
|
602
|
+
// A28, see above
|
|
603
|
+
try {
|
|
604
|
+
// The tick proposed against a hold (#3494): adopt its batch into it.
|
|
605
|
+
// Inside the try: the adoption runs user comparators (the no-proposal
|
|
606
|
+
// drop), and a throw there must not leave `_running` set.
|
|
607
|
+
while (batchJoins.length) this.initTransition(batchJoins.pop());
|
|
525
608
|
if (false) ;
|
|
526
609
|
// Before runHeap for the same reason as the fast drain above; late
|
|
527
610
|
// subscribers (an effect reading a swept memo this flush) revive it,
|
|
528
611
|
// which is the pay-for-use contract.
|
|
529
612
|
sweepDormant();
|
|
530
|
-
runHeap(dirtyQueue, GlobalQueue.
|
|
613
|
+
runHeap(dirtyQueue, GlobalQueue._t);
|
|
531
614
|
if (activeTransition) {
|
|
615
|
+
// The action bodies are over: the overrides they leave in force
|
|
616
|
+
// revert at this settle, and the correction is this transaction's
|
|
617
|
+
// held work — re-derived here, under it, ahead of the verdict — not a
|
|
618
|
+
// waterfall after the flights the overrides fed (#3427). After the
|
|
619
|
+
// heap, not before: a synchronous body's own writes (a refresh that
|
|
620
|
+
// puts an override node's source in flight) are judged applied.
|
|
621
|
+
if (GlobalQueue.yn?.(activeTransition)) runHeap(dirtyQueue, GlobalQueue._t);
|
|
532
622
|
const e = transitionComplete(activeTransition);
|
|
533
623
|
if (!e) {
|
|
534
624
|
const e = activeTransition;
|
|
625
|
+
// Parked: the unchanged passes' inputs are held; their tails stay (A30).
|
|
626
|
+
heldTrims.length = 0;
|
|
535
627
|
// When the parking batch IS the transition, all of its writes commit
|
|
536
628
|
// only with it — every zombie recompute they queued would run against
|
|
537
629
|
// a world the zombie never displays (zombies render mainline until
|
|
@@ -540,7 +632,7 @@ class GlobalQueue extends Queue {
|
|
|
540
632
|
// zombies here. Height-adjust entries still process normally: a
|
|
541
633
|
// dirtied one keeps its height flag and falls through to runHeap's
|
|
542
634
|
// adjustHeight path on the next pass of the bucket.
|
|
543
|
-
runHeap(zombieQueue, this.m === e ? cancelZombieRecompute : GlobalQueue.
|
|
635
|
+
runHeap(zombieQueue, this.m === e ? cancelZombieRecompute : GlobalQueue._t);
|
|
544
636
|
// Detach: the stashed transition keeps its batch; ambient work that
|
|
545
637
|
// follows lands in a fresh one. If the batch is already a separate
|
|
546
638
|
// ambient one — action done() restored activeTransition without
|
|
@@ -550,48 +642,48 @@ class GlobalQueue extends Queue {
|
|
|
550
642
|
if (this.m === e) currentBatch = this.m = createBatch();
|
|
551
643
|
// Run lane effects immediately (before stashing) - lanes with no pending async
|
|
552
644
|
if (activeLanes.size) {
|
|
553
|
-
GlobalQueue.
|
|
554
|
-
GlobalQueue.
|
|
645
|
+
GlobalQueue.Mn(EFFECT_RENDER);
|
|
646
|
+
GlobalQueue.Mn(EFFECT_USER);
|
|
555
647
|
}
|
|
556
|
-
this.stashQueues(e.
|
|
648
|
+
this.stashQueues(e.qn);
|
|
557
649
|
clock++;
|
|
558
650
|
// A kept ambient batch may hold pending nodes (#2916): stay
|
|
559
651
|
// scheduled so the outer drain loop commits them via the plain
|
|
560
652
|
// flush path instead of leaving them until the next natural flush.
|
|
561
|
-
scheduled = dirtyQueue.EE >= dirtyQueue.
|
|
562
|
-
reassignPendingTransition(e.
|
|
653
|
+
scheduled = dirtyQueue.EE >= dirtyQueue.Ct || this.m.sn.length > 0;
|
|
654
|
+
reassignPendingTransition(e.sn);
|
|
563
655
|
activeTransition = null;
|
|
564
656
|
finalizePureQueue(null, true);
|
|
565
657
|
return;
|
|
566
658
|
}
|
|
567
659
|
const t = activeTransition;
|
|
568
|
-
const
|
|
569
|
-
|
|
570
|
-
this.restoreQueues(t.
|
|
660
|
+
const n = this.m;
|
|
661
|
+
n !== t && n.sn.push(...t.sn);
|
|
662
|
+
this.restoreQueues(t.qn);
|
|
571
663
|
transitions.delete(t);
|
|
572
664
|
activeTransition = null;
|
|
573
|
-
reassignPendingTransition(
|
|
665
|
+
reassignPendingTransition(n.sn);
|
|
574
666
|
finalizePureQueue(t);
|
|
575
|
-
if (
|
|
667
|
+
if (n === t) {
|
|
576
668
|
// Drop the dead Transition wrapper but keep its (drained) containers
|
|
577
669
|
// as the ambient batch — late registrations during finalization live
|
|
578
670
|
// there and must survive to the next flush.
|
|
579
671
|
const e = createBatch();
|
|
580
|
-
e.
|
|
581
|
-
e.
|
|
582
|
-
e.A =
|
|
583
|
-
e.
|
|
672
|
+
e.sn = n.sn;
|
|
673
|
+
e.tn = n.tn;
|
|
674
|
+
e.A = n.A;
|
|
675
|
+
e.rn = n.rn;
|
|
584
676
|
currentBatch = this.m = e;
|
|
585
677
|
}
|
|
586
678
|
} else {
|
|
587
679
|
if (canUseSimpleSyncFlush(this)) {
|
|
588
680
|
commitPendingNodes();
|
|
589
|
-
if (dirtyQueue.EE >= dirtyQueue.
|
|
590
|
-
runHeap(dirtyQueue, GlobalQueue.
|
|
681
|
+
if (dirtyQueue.EE >= dirtyQueue.Ct) {
|
|
682
|
+
runHeap(dirtyQueue, GlobalQueue._t);
|
|
591
683
|
commitPendingNodes();
|
|
592
684
|
}
|
|
593
685
|
} else {
|
|
594
|
-
if (transitions.size) runHeap(zombieQueue, GlobalQueue.
|
|
686
|
+
if (transitions.size) runHeap(zombieQueue, GlobalQueue._t);
|
|
595
687
|
finalizePureQueue();
|
|
596
688
|
}
|
|
597
689
|
}
|
|
@@ -602,26 +694,36 @@ class GlobalQueue extends Queue {
|
|
|
602
694
|
// since are its to apply, not this flush's — runEffect leaves them queued
|
|
603
695
|
// and the next pass parks them with it (#3319). Everything computed
|
|
604
696
|
// mainline applies now.
|
|
605
|
-
scheduled = dirtyQueue.EE >= dirtyQueue.
|
|
697
|
+
scheduled = dirtyQueue.EE >= dirtyQueue.Ct || activeTransition !== null;
|
|
606
698
|
// Run lane effects first (for ready lanes), then regular effects
|
|
607
|
-
activeLanes.size && GlobalQueue.
|
|
699
|
+
activeLanes.size && GlobalQueue.Mn(EFFECT_RENDER);
|
|
608
700
|
this.run(EFFECT_RENDER);
|
|
609
|
-
activeLanes.size && GlobalQueue.
|
|
701
|
+
activeLanes.size && GlobalQueue.Mn(EFFECT_USER);
|
|
610
702
|
this.run(EFFECT_USER);
|
|
611
703
|
if (false) ;
|
|
612
704
|
if (false && !scheduled && !activeTransition && transitions.size === 0 && activeLanes.size === 0) ;
|
|
613
705
|
if (false) ;
|
|
614
706
|
} finally {
|
|
615
|
-
|
|
707
|
+
// Re-enter a woken transaction (see wokenTransitions) only from an
|
|
708
|
+
// idle pass: entering adopts the ambient batch, and staged or dirty
|
|
709
|
+
// ambient work would be held behind flights it never read. `scheduled`
|
|
710
|
+
// is that test here — after the park exit as well as the normal one:
|
|
711
|
+
// it was recomputed from the heap this pass, every write since re-armed
|
|
712
|
+
// it, and optimistic ambient nodes reverted with the finalize — so a
|
|
713
|
+
// wake in a pass with work simply falls to the next. Entering re-arms
|
|
714
|
+
// it itself; a dead (completed) wake is a bare return in
|
|
715
|
+
// initTransition, and the loop moves on to the next.
|
|
716
|
+
while (!scheduled && !activeTransition && wokenTransitions.length) this.initTransition(wokenTransitions.pop());
|
|
717
|
+
this.Sn = false;
|
|
616
718
|
}
|
|
617
719
|
}
|
|
618
|
-
notify(e, t,
|
|
720
|
+
notify(e, t, n, i) {
|
|
619
721
|
// Only track async if the boundary is propagating STATUS_PENDING (not caught by boundary)
|
|
620
722
|
if (t & STATUS_PENDING) {
|
|
621
|
-
if (
|
|
723
|
+
if (n & STATUS_PENDING) {
|
|
622
724
|
// Callers pass either nothing or this node's own `_x._error`, so `??`
|
|
623
725
|
// is exact (a null error falls back to the same null).
|
|
624
|
-
const t =
|
|
726
|
+
const t = i ?? e.o?._;
|
|
625
727
|
// A visibility-only mark notification (the affects() boundary
|
|
626
728
|
// channel) updates display state on its way up but must be invisible
|
|
627
729
|
// to completion accounting BY CONSTRUCTION: it never registers a
|
|
@@ -633,16 +735,16 @@ class GlobalQueue extends Queue {
|
|
|
633
735
|
// new. Fresh/reset loading boundaries consume pending before it
|
|
634
736
|
// reaches here. A reader already parked in a transition must not
|
|
635
737
|
// open a second one.
|
|
636
|
-
if (!activeTransition && !e.
|
|
738
|
+
if (!activeTransition && !e.Te && currentBatch.sn.length) this.initTransition();
|
|
637
739
|
if (activeTransition) {
|
|
638
|
-
const
|
|
639
|
-
let
|
|
640
|
-
if (!
|
|
641
|
-
const
|
|
642
|
-
|
|
643
|
-
if (
|
|
740
|
+
const n = t.source;
|
|
741
|
+
let i = activeTransition.ot.get(n);
|
|
742
|
+
if (!i) activeTransition.ot.set(n, i = new Set);
|
|
743
|
+
const s = i.size;
|
|
744
|
+
i.add(e);
|
|
745
|
+
if (i.size !== s) {
|
|
644
746
|
schedule();
|
|
645
|
-
GlobalQueue.
|
|
747
|
+
GlobalQueue.Yn?.(activeTransition);
|
|
646
748
|
}
|
|
647
749
|
}
|
|
648
750
|
}
|
|
@@ -661,9 +763,9 @@ class GlobalQueue extends Queue {
|
|
|
661
763
|
// new transaction identity each pass. Stamps are cleared at commit, so
|
|
662
764
|
// this is a belt for paths that hand over a chased-dead reference
|
|
663
765
|
// (merged chains, async settles racing completion).
|
|
664
|
-
if (e.
|
|
766
|
+
if (e.yt === true || e === activeTransition) return;
|
|
665
767
|
}
|
|
666
|
-
if (!e && activeTransition && activeTransition.
|
|
768
|
+
if (!e && activeTransition && activeTransition.fe === clock) return;
|
|
667
769
|
if (!activeTransition) {
|
|
668
770
|
activeTransition = e ?? createBatch();
|
|
669
771
|
} else if (e) {
|
|
@@ -673,12 +775,12 @@ class GlobalQueue extends Queue {
|
|
|
673
775
|
// now: back onto the live queue, where this flush parks them under
|
|
674
776
|
// `transition` or runs them at its completion. The outgoing stash is
|
|
675
777
|
// never read again — the transaction is dead (#3310).
|
|
676
|
-
this.restoreQueues(t.
|
|
778
|
+
this.restoreQueues(t.qn);
|
|
677
779
|
transitions.delete(t);
|
|
678
780
|
activeTransition = e;
|
|
679
781
|
}
|
|
680
782
|
transitions.add(activeTransition);
|
|
681
|
-
activeTransition.
|
|
783
|
+
activeTransition.fe = clock;
|
|
682
784
|
const t = this.m;
|
|
683
785
|
if (t !== activeTransition) {
|
|
684
786
|
// Adopt the ambient batch into the transaction, then make the
|
|
@@ -688,28 +790,57 @@ class GlobalQueue extends Queue {
|
|
|
688
790
|
// must not entangle unrelated writes to it; the same rule holds one hop
|
|
689
791
|
// downstream: propagation never queues pended subscribers as pending
|
|
690
792
|
// nodes, see propagateAffectsMark, #2893.
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
793
|
+
// Adopted outside a flush: the staging is still unflushed — the stamp
|
|
794
|
+
// must not make it read as held-and-carried (CONFIG_ADOPTED_UNFLUSHED;
|
|
795
|
+
// the carrying flush clears it in reassignPendingTransition).
|
|
796
|
+
const e = this.Sn ? 0 : CONFIG_ADOPTED_UNFLUSHED;
|
|
797
|
+
for (let n = 0; n < t.sn.length; n++) {
|
|
798
|
+
const i = t.sn[n];
|
|
799
|
+
// A tick that nets to the committed value proposed nothing (A34, #3494):
|
|
800
|
+
// `setShow(false); setShow(true)` beside a write that opens a hold
|
|
801
|
+
// left `show` staged at its own value, stamped, pending to the
|
|
802
|
+
// verdict, and its next mainline write held by a flight it never
|
|
803
|
+
// derived from. Unstage it here — its subscribers were walked at the
|
|
804
|
+
// write and re-derive the same value. Writes only: a signal's staging
|
|
805
|
+
// is always one, a computed's only under REACTIVE_MANUAL_WRITE
|
|
806
|
+
// (`createSignal(fn)`'s setter, #3519 review) — otherwise it is its
|
|
807
|
+
// pass's result, which may equal an uninitialized `undefined` (a
|
|
808
|
+
// born-held first pass). `_equals: false` opts out. The unstaging is
|
|
809
|
+
// the commit's own path (commitPendingNode with nothing staged): the
|
|
810
|
+
// manual-write flag, companions and the rest are cleaned up as a
|
|
811
|
+
// commit would, and the node is stamped nowhere. Unstamped
|
|
812
|
+
// only: a node already a transaction's — arriving here as a parked
|
|
813
|
+
// batch folds into a merge — carries a FLUSHED proposal a later
|
|
814
|
+
// rewrite brought back to the committed value; it is held, not
|
|
815
|
+
// proposal-free. Dropped, it kept the dead stamp, and the next write
|
|
816
|
+
// to it queued under the merged transaction a value the commit then
|
|
817
|
+
// skipped as another's (fuzzer latest-2 #1470, S3).
|
|
818
|
+
if (i.Te === null && i._e !== NOT_PENDING && (!i.ht || i.ft & REACTIVE_MANUAL_WRITE && !(i.S & STATUS_UNINITIALIZED)) && i.Re && i.Re(i.Ae, i._e)) {
|
|
819
|
+
i._e = NOT_PENDING;
|
|
820
|
+
commitPendingNode(i);
|
|
821
|
+
continue;
|
|
822
|
+
}
|
|
823
|
+
i.Te = activeTransition;
|
|
824
|
+
i.C |= e;
|
|
825
|
+
activeTransition.sn.push(i);
|
|
695
826
|
}
|
|
696
|
-
for (let e = 0; e < t.
|
|
697
|
-
const
|
|
698
|
-
|
|
699
|
-
activeTransition.
|
|
827
|
+
for (let e = 0; e < t.tn.length; e++) {
|
|
828
|
+
const n = t.tn[e];
|
|
829
|
+
n.Te = activeTransition;
|
|
830
|
+
activeTransition.tn.push(n);
|
|
700
831
|
}
|
|
701
832
|
if (t.A.length) activeTransition.A.push(...t.A);
|
|
702
|
-
for (const e of t.
|
|
833
|
+
for (const e of t.rn) activeTransition.rn.add(e);
|
|
703
834
|
// Gated readers recorded against the ambient batch move with it: their
|
|
704
835
|
// replay-at-commit now happens at the transaction's completion.
|
|
705
|
-
if (t.
|
|
706
|
-
for (const e of t.
|
|
707
|
-
t.
|
|
836
|
+
if (t.Ht.size) {
|
|
837
|
+
for (const e of t.Ht) activeTransition.Ht.add(e);
|
|
838
|
+
t.Ht.clear();
|
|
708
839
|
}
|
|
709
840
|
currentBatch = this.m = activeTransition;
|
|
710
841
|
}
|
|
711
842
|
for (const e of activeLanes) {
|
|
712
|
-
if (!e.
|
|
843
|
+
if (!e.Te) e.Te = activeTransition;
|
|
713
844
|
}
|
|
714
845
|
// A transaction's ambient window is one flush. Entering must therefore
|
|
715
846
|
// guarantee a flush: a transaction opened with no writes (an action whose
|
|
@@ -724,7 +855,9 @@ class GlobalQueue extends Queue {
|
|
|
724
855
|
}
|
|
725
856
|
|
|
726
857
|
function queuePendingNode(e) {
|
|
727
|
-
currentBatch.
|
|
858
|
+
currentBatch.sn.push(e);
|
|
859
|
+
if (!globalQueue.Sn) markUnflushedStaged();
|
|
860
|
+
// A28
|
|
728
861
|
}
|
|
729
862
|
|
|
730
863
|
// Sticky: flips true on the first refresh() ever (the only setter of
|
|
@@ -768,26 +901,26 @@ function setOrigin(e) {
|
|
|
768
901
|
function insertSubs(e, t = false) {
|
|
769
902
|
// §12d: stamp before walking — setSignal's staged-rewrite fast path skips
|
|
770
903
|
// the next walk for this node while the epoch holds (marking is idempotent).
|
|
771
|
-
e.
|
|
904
|
+
e.Zt = notifyEpoch;
|
|
772
905
|
// Get source lane: prefer node's own lane over current context
|
|
773
906
|
// This is important for isPending signals which need their own lane to flush immediately
|
|
774
907
|
// Presence bits gate the optional-slot probes (see constants.ts): one
|
|
775
908
|
// masked read of the always-present _config instead of missing-property
|
|
776
909
|
// lookups in the hottest notify loop. Bits are sticky — the field read
|
|
777
910
|
// stays authoritative when a bit is set.
|
|
778
|
-
const
|
|
779
|
-
const
|
|
780
|
-
const
|
|
911
|
+
const n = e.C;
|
|
912
|
+
const i = (n & CONFIG_HAS_LANE ? e.o?.de : undefined) || currentOptimisticLane;
|
|
913
|
+
const s = (n & CONFIG_HAS_SNAPSHOT) !== 0 && e.o?.Gt !== undefined;
|
|
781
914
|
const o = reaskArmed;
|
|
782
915
|
// Observe-tier fan-out: this walk visits every subscriber edge anyway, so
|
|
783
916
|
// the graph-size count is one local increment here and no field anywhere.
|
|
784
|
-
let
|
|
785
|
-
for (let
|
|
786
|
-
const e =
|
|
787
|
-
|
|
917
|
+
let r = 0;
|
|
918
|
+
for (let n = e.u; n !== null; n = n.ie) {
|
|
919
|
+
const e = n.se;
|
|
920
|
+
r++;
|
|
788
921
|
// A value-change notification is a new question for the subscriber: any
|
|
789
922
|
// pending re-ask mark (refresh) it carried is superseded.
|
|
790
|
-
if (o) e.
|
|
923
|
+
if (o) e.ft &= ~REACTIVE_REASK;
|
|
791
924
|
// Missed-wake latch (#3037): this write is landing while the subscriber
|
|
792
925
|
// is mid-recompute (a nested pull committing beneath its reads), and the
|
|
793
926
|
// heap refuses RECOMPUTING nodes. A gen-current link means the pass
|
|
@@ -796,58 +929,68 @@ function insertSubs(e, t = false) {
|
|
|
796
929
|
// pass either re-reads them fresh or trims them), and neither does the
|
|
797
930
|
// tail link: it is the read IN FLIGHT — read() links before it pulls, so
|
|
798
931
|
// this very commit is what that read returns.
|
|
799
|
-
if (e.
|
|
800
|
-
if (
|
|
801
|
-
e.
|
|
932
|
+
if (e.ft & REACTIVE_RECOMPUTING_DEPS && n.pe === e.ye && n !== e.Fe) e.ft |= REACTIVE_MISSED_WAKE;
|
|
933
|
+
if (s && e.C & CONFIG_IN_SNAPSHOT_SCOPE) {
|
|
934
|
+
e.ft |= REACTIVE_SNAPSHOT_STALE;
|
|
802
935
|
continue;
|
|
803
936
|
}
|
|
804
|
-
if (t &&
|
|
805
|
-
e.
|
|
806
|
-
assignOrMergeLane(e,
|
|
937
|
+
if (t && i) {
|
|
938
|
+
e.ft |= REACTIVE_OPTIMISTIC_DIRTY;
|
|
939
|
+
assignOrMergeLane(e, i);
|
|
807
940
|
} else if (t) {
|
|
808
|
-
e.
|
|
941
|
+
e.ft |= REACTIVE_OPTIMISTIC_DIRTY;
|
|
809
942
|
// No source lane means reversion - clear subscriber's lane so effects go to regular queue
|
|
810
|
-
if (e.o) e.o.
|
|
943
|
+
if (e.o) e.o.de = undefined;
|
|
811
944
|
}
|
|
812
945
|
enqueueSub(e);
|
|
813
946
|
}
|
|
814
|
-
if (
|
|
947
|
+
if (r >= GRAPH_SIZE_WARN_AT) noteFanOut(e, r);
|
|
815
948
|
}
|
|
816
949
|
|
|
817
950
|
function commitPendingNode(e) {
|
|
818
951
|
const t = e;
|
|
819
|
-
if (!t.
|
|
820
|
-
if (e.
|
|
821
|
-
e.
|
|
822
|
-
e.
|
|
952
|
+
if (!t.ht) {
|
|
953
|
+
if (e._e !== NOT_PENDING) {
|
|
954
|
+
e.Ae = e._e;
|
|
955
|
+
e._e = NOT_PENDING;
|
|
823
956
|
}
|
|
824
|
-
if (e.
|
|
957
|
+
if (e.C & CONFIG_HAS_COMPANIONS) GlobalQueue.Qn(e);
|
|
825
958
|
return;
|
|
826
959
|
}
|
|
827
|
-
if (e.
|
|
828
|
-
e.
|
|
829
|
-
e.
|
|
960
|
+
if (e._e !== NOT_PENDING) {
|
|
961
|
+
e.Ae = e._e;
|
|
962
|
+
e._e = NOT_PENDING;
|
|
963
|
+
// A node born held (recompute) initializes at this commit.
|
|
964
|
+
t.S &= ~STATUS_UNINITIALIZED;
|
|
830
965
|
// Set _modified for effects, but not for tracked effects (they handle their own scheduling)
|
|
831
|
-
if (e.
|
|
966
|
+
if (e.Pe && e.Pe !== EFFECT_TRACKED) e.Nt = true;
|
|
832
967
|
// A quiet re-ask classification preserved through a held landing dies
|
|
833
968
|
// with the value commit — the commit IS the reveal (#3178). Gated on the
|
|
834
969
|
// staged value: status propagation queues pending nodes whose windows
|
|
835
970
|
// are still OPEN (no staged value), and their live classification must
|
|
836
971
|
// survive this sweep.
|
|
837
|
-
if (e.o) e.o.
|
|
972
|
+
if (e.o) e.o.Ee = false;
|
|
838
973
|
}
|
|
839
974
|
// The committed hold is the first observable answer for a loading-window
|
|
840
975
|
// node — the window closes here, not at compute time (#2990). Unconditional
|
|
841
976
|
// store to an always-present computed slot.
|
|
842
|
-
t.
|
|
843
|
-
t.
|
|
977
|
+
t.Se = false;
|
|
978
|
+
t.ft &= ~REACTIVE_MANUAL_WRITE;
|
|
979
|
+
// The children this commit publishes are the frame's now (#3404) — and so
|
|
980
|
+
// are the dependencies of the pass that produced the value: the previous
|
|
981
|
+
// frame's tail goes (A30, #3410; `recompute` left it for a staged pass). Only
|
|
982
|
+
// after a clean pass: `_error` is cleared by a clean pass or by the node's
|
|
983
|
+
// own landing (whose pass was clean), so a set `_error` means the last pass
|
|
984
|
+
// threw, kept its full list, and `_depsTail` marks where it stopped.
|
|
985
|
+
if (t.o?._ == null) trimStaleDeps(t);
|
|
986
|
+
t.C &= ~CONFIG_HELD_CHILDREN;
|
|
844
987
|
if (!(t.S & STATUS_PENDING)) t.S &= ~STATUS_UNINITIALIZED;
|
|
845
988
|
// A flight this commit leaves in the air (unobserved, or observed only by
|
|
846
989
|
// a boundary) now has PUBLISHED inputs: its committed value is stale
|
|
847
990
|
// against the frame. read()'s reveal carve-out keys on the mark (#3305).
|
|
848
|
-
else e.
|
|
849
|
-
if (t.o != null && (t.o.
|
|
850
|
-
if (e.
|
|
991
|
+
else e.C |= CONFIG_INPUTS_PUBLISHED;
|
|
992
|
+
if (t.o != null && (t.o.Pt !== null || t.o.Dt !== null)) GlobalQueue.It(t, false, true);
|
|
993
|
+
if (e.C & CONFIG_HAS_COMPANIONS) GlobalQueue.Qn(e);
|
|
851
994
|
}
|
|
852
995
|
|
|
853
996
|
// Store commit hook (INTERNALS-STORE-STATE.md §3): installed by the store
|
|
@@ -868,11 +1011,21 @@ function setStoreCommitHook(e) {
|
|
|
868
1011
|
* confirming truth is committed and the override still displays, a torn
|
|
869
1012
|
* frame no timeline contains. */ const heldRevealed = [];
|
|
870
1013
|
|
|
1014
|
+
/** Unchanged passes with a stale dependency tail, waiting on this flush's
|
|
1015
|
+
* verdict (A30, #3469). A pass that changed nothing replaced nothing either —
|
|
1016
|
+
* and cannot know at its own tail whether the flush that ran it will park:
|
|
1017
|
+
* parked, its inputs are held and the committed frame still derives from the
|
|
1018
|
+
* tail (`b() ? b() : a()` computed `1` from the held `b`, equal to the `1` it
|
|
1019
|
+
* had from `a` — with `a` trimmed, the mainline `a = 2` never reached it).
|
|
1020
|
+
* Trimmed when the flush commits; dropped with a park, the tail stays linked
|
|
1021
|
+
* until a committing pass trims it (one spurious recompute at most). */ const heldTrims = [];
|
|
1022
|
+
|
|
871
1023
|
function commitPendingNodes() {
|
|
872
|
-
|
|
1024
|
+
while (heldTrims.length) trimStaleDeps(heldTrims.pop());
|
|
1025
|
+
const e = currentBatch.sn;
|
|
873
1026
|
for (let t = 0; t < e.length; t++) {
|
|
874
|
-
const
|
|
875
|
-
commitPendingNode(
|
|
1027
|
+
const n = e[t];
|
|
1028
|
+
commitPendingNode(n);
|
|
876
1029
|
// The stamp dies with the commit (#3143) — symmetric with
|
|
877
1030
|
// resolveOptimisticNodes clearing optimistic stamps. A stamp outliving
|
|
878
1031
|
// its transaction let any later write (even a value-equal no-op, which
|
|
@@ -884,10 +1037,10 @@ function commitPendingNodes() {
|
|
|
884
1037
|
// that old view and cached it), and commits are otherwise silent
|
|
885
1038
|
// (staging already notified), so without a wake they'd hold the old
|
|
886
1039
|
// world forever.
|
|
887
|
-
|
|
888
|
-
if (
|
|
889
|
-
|
|
890
|
-
heldRevealed.push(
|
|
1040
|
+
n.Te = null;
|
|
1041
|
+
if (n.C & CONFIG_HELD_TRUTH) {
|
|
1042
|
+
n.C &= ~CONFIG_HELD_TRUTH;
|
|
1043
|
+
heldRevealed.push(n);
|
|
891
1044
|
}
|
|
892
1045
|
}
|
|
893
1046
|
e.length = 0;
|
|
@@ -897,10 +1050,10 @@ function commitPendingNodes() {
|
|
|
897
1050
|
function finalizePureQueue(e = null, t = false) {
|
|
898
1051
|
// For incomplete transitions, skip pending resolution and optimistic reversion
|
|
899
1052
|
// For completing transitions or no-transition, resolve pending and revert optimistic
|
|
900
|
-
const
|
|
901
|
-
const
|
|
902
|
-
if (
|
|
903
|
-
if (!t && globalQueue.
|
|
1053
|
+
const n = currentBatch;
|
|
1054
|
+
const i = !t;
|
|
1055
|
+
if (i) commitPendingNodes();
|
|
1056
|
+
if (!t && globalQueue.Bn.length) checkBoundaryChildren(globalQueue);
|
|
904
1057
|
// Contested effects (#3322) re-derive from the world this commit just
|
|
905
1058
|
// produced. Ahead of the heap run — not the post-heap gated replay — so
|
|
906
1059
|
// the recompute and the effect phase land in this same pass and the value
|
|
@@ -911,12 +1064,12 @@ function finalizePureQueue(e = null, t = false) {
|
|
|
911
1064
|
// _resolveOptimistic the truth is committed but the overrides still
|
|
912
1065
|
// display, and a re-derive here would compose the two — the #3164 tear,
|
|
913
1066
|
// one window later. The slot meanwhile holds the frame that is on screen.
|
|
914
|
-
const
|
|
915
|
-
const o =
|
|
916
|
-
if (
|
|
917
|
-
const
|
|
918
|
-
if (
|
|
919
|
-
if (
|
|
1067
|
+
const s = e?.xt;
|
|
1068
|
+
const o = i && (e ?? n).tn.length !== 0;
|
|
1069
|
+
if (s && !o) for (const e of s) if (!(e.ft & REACTIVE_DISPOSED)) enqueueSub(e);
|
|
1070
|
+
const r = dirtyQueue.EE >= dirtyQueue.Ct;
|
|
1071
|
+
if (r) runHeap(dirtyQueue, GlobalQueue._t);
|
|
1072
|
+
if (i) {
|
|
920
1073
|
// Boundary checks, commit hooks and recomputes can enter a transaction,
|
|
921
1074
|
// which adopts the batch this finalize was settling: nothing batch-derived
|
|
922
1075
|
// may be committed or reverted here — the entered transaction owns it now
|
|
@@ -925,28 +1078,28 @@ function finalizePureQueue(e = null, t = false) {
|
|
|
925
1078
|
// adoption never touched them and it must still settle them; when the
|
|
926
1079
|
// batch WAS the completing transaction, adoption re-stamped its contents
|
|
927
1080
|
// into the entered one and there is nothing left to settle.
|
|
928
|
-
if (currentBatch !==
|
|
929
|
-
if (e === null || e ===
|
|
930
|
-
} else if (
|
|
1081
|
+
if (currentBatch !== n) {
|
|
1082
|
+
if (e === null || e === n) return;
|
|
1083
|
+
} else if (r) commitPendingNodes();
|
|
931
1084
|
// The settling batch: the completing transaction's, or the ambient one.
|
|
932
|
-
const t = e ??
|
|
1085
|
+
const t = e ?? n;
|
|
933
1086
|
// Optimistic reversion: a non-empty batch means _optimisticWrite ran,
|
|
934
1087
|
// which installed the engine's hooks.
|
|
935
|
-
if (t.
|
|
936
|
-
if (
|
|
937
|
-
for (const e of
|
|
1088
|
+
if (t.tn.length) GlobalQueue.Un(t.tn);
|
|
1089
|
+
if (s && o) {
|
|
1090
|
+
for (const e of s) if (!(e.ft & REACTIVE_DISPOSED)) enqueueSub(e);
|
|
938
1091
|
schedule();
|
|
939
1092
|
}
|
|
940
1093
|
// Replay entanglement: subs recorded by the read-time gate get rescheduled
|
|
941
1094
|
// so they re-run with the now-committed values visible. The ambient batch
|
|
942
1095
|
// replays too — laneReadsCommitted records readers whose committed-view
|
|
943
1096
|
// read hid a same-tick plain write that just committed above (#2963).
|
|
944
|
-
if (t.
|
|
945
|
-
for (const e of t.
|
|
946
|
-
if (e.
|
|
1097
|
+
if (t.Ht.size) {
|
|
1098
|
+
for (const e of t.Ht) {
|
|
1099
|
+
if (e.ft & REACTIVE_DISPOSED) continue;
|
|
947
1100
|
enqueueSub(e);
|
|
948
1101
|
}
|
|
949
|
-
t.
|
|
1102
|
+
t.Ht.clear();
|
|
950
1103
|
// A completing transition keeps the outer flush loop alive by itself;
|
|
951
1104
|
// the ambient batch needs the re-arm or the replay sits in the heap
|
|
952
1105
|
// until the next unrelated write.
|
|
@@ -960,13 +1113,13 @@ function finalizePureQueue(e = null, t = false) {
|
|
|
960
1113
|
// (the earlier sweep above ran while the marks were still live).
|
|
961
1114
|
if (t.A.length) {
|
|
962
1115
|
GlobalQueue.G(t.A);
|
|
963
|
-
if (globalQueue.
|
|
1116
|
+
if (globalQueue.Bn.length) checkBoundaryChildren(globalQueue);
|
|
964
1117
|
}
|
|
965
1118
|
// A non-empty set means trackOptimisticStore ran, which installed the
|
|
966
1119
|
// hook; the hook iterates, clears, and schedules (keeping the loop out of
|
|
967
1120
|
// core lets esbuild shake it — rollup already folds the null guard). The
|
|
968
1121
|
// completing transition scopes the clear to its own layer keys (#2899).
|
|
969
|
-
if (t.
|
|
1122
|
+
if (t.rn.size) GlobalQueue.zn(t.rn, e);
|
|
970
1123
|
// Held-truth reveal wake (#3164), post-revert by construction: this
|
|
971
1124
|
// finalize committed confirming truth whose subscribers were masked all
|
|
972
1125
|
// hold — some re-derived against the committed view (the staging, or a
|
|
@@ -978,20 +1131,20 @@ function finalizePureQueue(e = null, t = false) {
|
|
|
978
1131
|
// committed but the settling transaction's overrides still display.
|
|
979
1132
|
if (heldRevealed.length !== 0) {
|
|
980
1133
|
while (heldRevealed.length) insertSubs(heldRevealed.pop());
|
|
981
|
-
if (dirtyQueue.EE >= dirtyQueue.
|
|
982
|
-
runHeap(dirtyQueue, GlobalQueue.
|
|
1134
|
+
if (dirtyQueue.EE >= dirtyQueue.Ct) {
|
|
1135
|
+
runHeap(dirtyQueue, GlobalQueue._t);
|
|
983
1136
|
commitPendingNodes();
|
|
984
1137
|
}
|
|
985
1138
|
}
|
|
986
1139
|
sweepTransientStoreNodes();
|
|
987
1140
|
// Lanes only enter activeLanes through the engine's getOrCreateLane.
|
|
988
|
-
if (activeLanes.size) GlobalQueue.
|
|
1141
|
+
if (activeLanes.size) GlobalQueue.xn(e);
|
|
989
1142
|
}
|
|
990
1143
|
}
|
|
991
1144
|
|
|
992
1145
|
function checkBoundaryChildren(e) {
|
|
993
|
-
for (const t of e.
|
|
994
|
-
t.
|
|
1146
|
+
for (const t of e.Bn) {
|
|
1147
|
+
t.ct?.();
|
|
995
1148
|
checkBoundaryChildren(t);
|
|
996
1149
|
}
|
|
997
1150
|
}
|
|
@@ -1014,8 +1167,10 @@ function checkBoundaryChildren(e) {
|
|
|
1014
1167
|
|
|
1015
1168
|
function reassignPendingTransition(e) {
|
|
1016
1169
|
for (let t = 0; t < e.length; t++) {
|
|
1017
|
-
e[t].
|
|
1018
|
-
|
|
1170
|
+
e[t].Te = activeTransition;
|
|
1171
|
+
e[t].C &= ~CONFIG_ADOPTED_UNFLUSHED;
|
|
1172
|
+
// this flush carried it
|
|
1173
|
+
}
|
|
1019
1174
|
}
|
|
1020
1175
|
|
|
1021
1176
|
const globalQueue = new GlobalQueue;
|
|
@@ -1052,7 +1207,7 @@ function flush(e) {
|
|
|
1052
1207
|
}
|
|
1053
1208
|
}
|
|
1054
1209
|
}
|
|
1055
|
-
if (globalQueue.
|
|
1210
|
+
if (globalQueue.Sn) {
|
|
1056
1211
|
return;
|
|
1057
1212
|
}
|
|
1058
1213
|
if (halted) return;
|
|
@@ -1077,40 +1232,111 @@ function flush(e) {
|
|
|
1077
1232
|
}
|
|
1078
1233
|
|
|
1079
1234
|
function runQueue(e, t) {
|
|
1080
|
-
for (let
|
|
1235
|
+
for (let n = 0; n < e.length; n++) e[n](t);
|
|
1081
1236
|
}
|
|
1082
1237
|
|
|
1083
|
-
function reporterBlocksSource(e, t) {
|
|
1084
|
-
|
|
1085
|
-
if (
|
|
1086
|
-
|
|
1087
|
-
|
|
1238
|
+
function reporterBlocksSource(e, t, n) {
|
|
1239
|
+
const i = e.ft;
|
|
1240
|
+
if (i & REACTIVE_DISPOSED) return false;
|
|
1241
|
+
// A zombie renders until the commit that disposes it (#3463): while its
|
|
1242
|
+
// removal is staged in a live transaction it is still on screen, and what
|
|
1243
|
+
// it displays must stay consistent with the frame — a held `Show`'s
|
|
1244
|
+
// `Details: 0` beside the lane's `Value: 1` otherwise. Its say is moot for
|
|
1245
|
+
// the verdict of the transaction that stages the removal (`verdict`): done,
|
|
1246
|
+
// and the commit disposes it; not done, and it stays parked regardless. A
|
|
1247
|
+
// zombie whose removal commits this flush (owner's pass not held) is dead.
|
|
1248
|
+
// The owner is stamped when the flush parks; held in this flush, its
|
|
1249
|
+
// staging transaction is the active one.
|
|
1250
|
+
if (i & REACTIVE_ZOMBIE) {
|
|
1251
|
+
let t = e;
|
|
1252
|
+
while (t && t.ft & REACTIVE_ZOMBIE) t = t._parent;
|
|
1253
|
+
let i = t && (t.Te || (t.C & CONFIG_HELD_CHILDREN ? activeTransition : null));
|
|
1254
|
+
if (!i || (i = currentTransition(i)).yt === true || i === n) return false;
|
|
1255
|
+
}
|
|
1256
|
+
// Fallback-caught async holds nothing. A collecting loading boundary
|
|
1257
|
+
// consumes the notification, so a reader under a fallback never registers —
|
|
1258
|
+
// but a reader registered while its boundary showed content stays
|
|
1259
|
+
// registered when the boundary's `on` later changes and it flips to the
|
|
1260
|
+
// fallback. The reader is behind the fallback now; if nothing outside the
|
|
1261
|
+
// boundary consumes the flight, the hold is over (A33, ruled 2026-09-12, #3375).
|
|
1262
|
+
for (let t = e.T; t; t = t._parent) if (t.tt & STATUS_PENDING && !t.H) return false;
|
|
1263
|
+
// "Still derives from the source" is a question about THIS pass's reads:
|
|
1264
|
+
// the deps up to `_depsTail`. Past it lie the committed frame's — kept
|
|
1265
|
+
// linked by A30 until the commit trims them (a staged pass, an errored
|
|
1266
|
+
// one). Reading them here made a reporter whose pass had stopped reading
|
|
1267
|
+
// the source (a gate closed in the same flush as the write) look live, and
|
|
1268
|
+
// the hold it kept was the commit that would have trimmed the dep that
|
|
1269
|
+
// kept it (spec O3, same-flush form; fuzzer #3446 P1 cases 21/79). A
|
|
1270
|
+
// trimmed list ends at `_depsTail`, so the bound is free there; a pass
|
|
1271
|
+
// that read nothing has a null tail and derives from nothing.
|
|
1272
|
+
// The registration is trusted: a pending mark rides only this pass's links
|
|
1273
|
+
// (notifyStatus skips the kept tail), so a registered reporter read the
|
|
1274
|
+
// source, or threw on it.
|
|
1275
|
+
if (e.o?.ut?.has(t)) return true;
|
|
1276
|
+
const s = e.Fe;
|
|
1277
|
+
for (let n = s === null ? null : e.ee; n; n = n === s ? null : n.te) {
|
|
1278
|
+
let e = n.re;
|
|
1088
1279
|
while (e) {
|
|
1089
|
-
|
|
1090
|
-
|
|
1280
|
+
// Or through a memo pending on the flight (#3494): a stale reader
|
|
1281
|
+
// served a held memo's committed value never turned pending itself, so
|
|
1282
|
+
// its only trace of the flight is the memo between them — `copy` of
|
|
1283
|
+
// `details`. Judged dead, its transaction released `count=1` beside
|
|
1284
|
+
// the `Copy: 0` it displays. `_pendingSources` is transitive, so one
|
|
1285
|
+
// hop covers any depth.
|
|
1286
|
+
if (e === t || e.ne === t || e.o?.ut?.has(t)) return true;
|
|
1287
|
+
e = e.o?.nn;
|
|
1091
1288
|
}
|
|
1092
1289
|
}
|
|
1093
1290
|
return !!(e.S & STATUS_PENDING && e.o?._ instanceof NotReadyError && e.o?._.source === t);
|
|
1094
1291
|
}
|
|
1095
1292
|
|
|
1293
|
+
/**
|
|
1294
|
+
* Does a live reporter of `transition` still observe `source` pending? Dead
|
|
1295
|
+
* reporters (disposed, behind a fallback, no longer reading the source) are
|
|
1296
|
+
* pruned as they are found, and the source's entry with them. Shared by the
|
|
1297
|
+
* settle verdict and the lane's hold check (`waitingTransition`): a live
|
|
1298
|
+
* action parks its transaction without a verdict, so this prune is the only
|
|
1299
|
+
* one an optimistic lane whose last async reader unmounted mid-action ever
|
|
1300
|
+
* gets — without it the lane held on the dead reporter's registration until
|
|
1301
|
+
* the flight it no longer observed landed (#3426).
|
|
1302
|
+
*/ function sourceObserved(e, t, n) {
|
|
1303
|
+
const i = e.ot.get(t);
|
|
1304
|
+
let s = false;
|
|
1305
|
+
for (const e of i ?? []) {
|
|
1306
|
+
if (reporterBlocksSource(e, t, n)) return true;
|
|
1307
|
+
// A zombie the verdict passes over is kept, not pruned (#3463): moot for
|
|
1308
|
+
// this verdict, it still holds a lane's reveal while the transaction
|
|
1309
|
+
// stays parked on something else.
|
|
1310
|
+
if (n && e.ft & REACTIVE_ZOMBIE) s = true; else i.delete(e);
|
|
1311
|
+
}
|
|
1312
|
+
if (!s) e.ot.delete(t);
|
|
1313
|
+
return false;
|
|
1314
|
+
}
|
|
1315
|
+
|
|
1096
1316
|
function transitionComplete(e) {
|
|
1097
|
-
if (e.
|
|
1098
|
-
if (e.
|
|
1317
|
+
if (e.yt) return true;
|
|
1318
|
+
if (e.dt.length) {
|
|
1099
1319
|
// A live action parks the transaction regardless of async state.
|
|
1100
1320
|
if (attrHooks !== null) attrHooks.holdStart(e);
|
|
1101
1321
|
return false;
|
|
1102
1322
|
}
|
|
1103
1323
|
let t = true;
|
|
1104
|
-
for (const
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1324
|
+
for (const n of e.ot.keys()) {
|
|
1325
|
+
// The source blocks while its OWN flight is up — the self entry in its
|
|
1326
|
+
// pending sources (added by notifyStatus's source path, with status;
|
|
1327
|
+
// retired by the landing and the supersede sweep, so it implies
|
|
1328
|
+
// STATUS_PENDING). `_error.source` is not that test: propagation from an
|
|
1329
|
+
// input that went pending later overwrites it with the input (#3375 — a
|
|
1330
|
+
// boundary-consumed load re-asked under a held derivation), and the
|
|
1331
|
+
// still-flying source read as settled, committing the writes it was
|
|
1332
|
+
// asked with ahead of its answer.
|
|
1333
|
+
// Not the self entry alone: a source whose own flight an upstream re-ask
|
|
1334
|
+
// superseded is still pending — on that re-ask (#3462). Its reader cannot
|
|
1335
|
+
// render until the chain lands, and the landing folds this transaction in
|
|
1336
|
+
// (enterWaiting). Judged complete instead, a re-entry between the two (a
|
|
1337
|
+
// repeated write to a held signal) committed the held writes beside the
|
|
1338
|
+
// reader's stale frame.
|
|
1339
|
+
if (sourceObserved(e, n, e) && n.o?.ut?.size) {
|
|
1114
1340
|
t = false;
|
|
1115
1341
|
break;
|
|
1116
1342
|
}
|
|
@@ -1118,14 +1344,14 @@ function transitionComplete(e) {
|
|
|
1118
1344
|
// Override blockage lives with the engine (absent hook = "no optimistic
|
|
1119
1345
|
// blockage"); the hook's loops over _optimisticNodes/_optimisticStores are
|
|
1120
1346
|
// no-ops when the transition holds neither, so no pre-check is needed.
|
|
1121
|
-
if (t && GlobalQueue.
|
|
1347
|
+
if (t && GlobalQueue.kn?.(e)) t = false;
|
|
1122
1348
|
// Attribution hook: this verdict is the fork between settling (held writes
|
|
1123
1349
|
// commit next — `_pendingNodes` still lists them) and parking (the flush
|
|
1124
1350
|
// runs the lane effects, then stashes; `holdEnd` fires from stashQueues).
|
|
1125
1351
|
// Fired here rather than at flush()'s call site because that site is inside
|
|
1126
1352
|
// a `try` (see the rule in attribution-hooks.ts).
|
|
1127
1353
|
if (attrHooks !== null) t ? attrHooks.transitionSettled(e) : attrHooks.holdStart(e);
|
|
1128
|
-
t && (e.
|
|
1354
|
+
t && (e.yt = true);
|
|
1129
1355
|
return t;
|
|
1130
1356
|
}
|
|
1131
1357
|
|
|
@@ -1137,7 +1363,7 @@ function transitionComplete(e) {
|
|
|
1137
1363
|
}
|
|
1138
1364
|
|
|
1139
1365
|
function currentTransition(e) {
|
|
1140
|
-
while (e.
|
|
1366
|
+
while (e.yt && typeof e.yt === "object") e = e.yt;
|
|
1141
1367
|
return e;
|
|
1142
1368
|
}
|
|
1143
1369
|
|
|
@@ -1148,19 +1374,31 @@ function currentTransition(e) {
|
|
|
1148
1374
|
* the node, so a hold check must not assume it was recorded in the transaction
|
|
1149
1375
|
* it happens to hold — lanes merge across transactions (#2912), and a merged
|
|
1150
1376
|
* root's transaction knows nothing of the async its members' transactions
|
|
1151
|
-
* observed (#3335). Null when nobody is waiting
|
|
1377
|
+
* observed (#3335). Null when nobody is waiting — a registration whose every
|
|
1378
|
+
* reporter has since died is nobody (#3426).
|
|
1152
1379
|
*/ function waitingTransition(e) {
|
|
1153
|
-
for (const t of transitions) if (t
|
|
1380
|
+
for (const t of transitions) if (sourceObserved(t, e)) return t;
|
|
1154
1381
|
return null;
|
|
1155
1382
|
}
|
|
1156
1383
|
|
|
1384
|
+
/** A landing enters EVERY parked transaction still waiting on `source`, folding
|
|
1385
|
+
* them into the active one (A15: each reveal that discovered the flight
|
|
1386
|
+
* completes at its landing). The fold used to happen as the waiters' stamped
|
|
1387
|
+
* readers recomputed under the landing — recompute re-entering an effect's
|
|
1388
|
+
* stamp — which also folded in writes those readers merely shared a hole
|
|
1389
|
+
* with (#3407); effects no longer re-enter, so the landing folds explicitly.
|
|
1390
|
+
* Live iteration is safe: a merge deletes the outgoing (active) entry and
|
|
1391
|
+
* re-adds the visited one. */ function enterWaiting(e) {
|
|
1392
|
+
for (const t of transitions) if (sourceObserved(t, e)) globalQueue.initTransition(t);
|
|
1393
|
+
}
|
|
1394
|
+
|
|
1157
1395
|
function runInTransition(e, t) {
|
|
1158
|
-
const
|
|
1396
|
+
const n = activeTransition;
|
|
1159
1397
|
try {
|
|
1160
1398
|
activeTransition = currentTransition(e);
|
|
1161
1399
|
return t();
|
|
1162
1400
|
} finally {
|
|
1163
|
-
activeTransition =
|
|
1401
|
+
activeTransition = n;
|
|
1164
1402
|
}
|
|
1165
1403
|
}
|
|
1166
1404
|
|
|
@@ -1175,16 +1413,16 @@ function runInTransition(e, t) {
|
|
|
1175
1413
|
* initTransition is the wrong tool here: it MERGES the currently ambient
|
|
1176
1414
|
* transaction into the target, entangling whatever the interrupted window
|
|
1177
1415
|
* belonged to. */ function runAsTransitionBatch(e, t) {
|
|
1178
|
-
const
|
|
1179
|
-
const
|
|
1416
|
+
const n = activeTransition;
|
|
1417
|
+
const i = globalQueue.m;
|
|
1180
1418
|
try {
|
|
1181
1419
|
activeTransition = currentTransition(e);
|
|
1182
1420
|
currentBatch = globalQueue.m = activeTransition;
|
|
1183
1421
|
return t();
|
|
1184
1422
|
} finally {
|
|
1185
|
-
activeTransition =
|
|
1186
|
-
currentBatch = globalQueue.m =
|
|
1423
|
+
activeTransition = n;
|
|
1424
|
+
currentBatch = globalQueue.m = i;
|
|
1187
1425
|
}
|
|
1188
1426
|
}
|
|
1189
1427
|
|
|
1190
|
-
export { GlobalQueue, Queue, actionStepDepth, activeAffectsMarks, activeLanes, activeTransition, armReaskClear, assignOrMergeLane, bumpNotifyEpoch, clock, createTransition, currentTransition, dirtyQueue, enforceLoadingBoundary, entangleConfirmingTransitions, enterActionStep, exitActionStep, finalizePureQueue, findLane, flush, globalQueue, haltReactivity, hasActiveOverride, insertSubs, notifyEpoch, origin, projectionWriteActive, queuePendingNode, reaskArmed, resetErrorHalt, runAsTransitionBatch, runInTransition, schedule, setOrigin, setProjectionWriteActive, setStoreCommitHook, shiftAffectsMarks, storeCommitHook, waitingTransition, zombieQueue };
|
|
1428
|
+
export { GlobalQueue, Queue, ROOT_ERROR_HOOK, actionStepDepth, activeAffectsMarks, activeLanes, activeTransition, armReaskClear, assignOrMergeLane, batchJoins, bumpNotifyEpoch, clock, createTransition, currentTransition, deferSlotRelease, dirtyQueue, enforceLoadingBoundary, entangleConfirmingTransitions, enterActionStep, enterWaiting, exitActionStep, finalizePureQueue, findLane, flush, globalQueue, haltReactivity, hasActiveOverride, heldTrims, insertSubs, notifyEpoch, origin, projectionWriteActive, queuePendingNode, reaskArmed, resetErrorHalt, runAsTransitionBatch, runInTransition, schedule, setOrigin, setProjectionWriteActive, setStoreCommitHook, shiftAffectsMarks, sourceObserved, storeCommitHook, transitions, waitingTransition, wakeParked, wokenTransitions, zombieQueue };
|