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