@solidjs/signals 2.0.0-rc.13 → 2.0.0-rc.15
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 +2059 -3768
- package/dist/dev.attribution.js +237 -101
- package/dist/dev.js +7466 -8497
- package/dist/observe/affects.js +128 -104
- package/dist/observe/boundaries.js +492 -665
- package/dist/observe/core/action.js +63 -66
- package/dist/observe/core/async.js +243 -379
- package/dist/observe/core/attribution-queries.js +1 -1
- package/dist/observe/core/attribution.js +394 -253
- package/dist/observe/core/constants.js +120 -187
- package/dist/observe/core/context.js +3 -3
- package/dist/observe/core/core.js +874 -1440
- package/dist/observe/core/dev.js +3 -3
- package/dist/observe/core/effect.js +73 -84
- package/dist/observe/core/external.js +5 -5
- package/dist/observe/core/graph.js +62 -71
- package/dist/observe/core/heap.js +75 -98
- package/dist/observe/core/invariants.js +1 -34
- package/dist/observe/core/lanes.js +750 -181
- package/dist/observe/core/owner.js +80 -101
- package/dist/observe/core/scheduler.js +719 -1356
- package/dist/observe/core/verdict.js +314 -621
- package/dist/observe/flatten.js +79 -0
- package/dist/observe/index.js +15 -13
- package/dist/observe/map.js +232 -235
- package/dist/observe/reveal.js +212 -0
- package/dist/observe/signals.js +144 -216
- package/dist/observe/store/affects.js +142 -0
- package/dist/observe/store/hooks.js +13 -0
- package/dist/observe/store/index.js +27 -24
- package/dist/observe/store/optimistic.js +571 -0
- package/dist/observe/store/projection.js +308 -0
- package/dist/observe/store/reconcile.js +259 -0
- package/dist/observe/store/store.js +1988 -277
- package/dist/observe/store/storePath.js +1 -1
- package/dist/{prod/store/next → observe/store}/target.js +17 -18
- package/dist/observe/store/types.js +73 -0
- package/dist/observe/store/utils.js +1 -13
- package/dist/prod/affects.js +130 -106
- package/dist/prod/boundaries.js +476 -654
- package/dist/prod/core/action.js +71 -74
- package/dist/prod/core/async.js +306 -444
- package/dist/prod/core/constants.js +117 -186
- package/dist/prod/core/context.js +3 -3
- package/dist/prod/core/core.js +888 -1462
- package/dist/prod/core/effect.js +88 -99
- package/dist/prod/core/external.js +5 -5
- package/dist/prod/core/graph.js +62 -71
- package/dist/prod/core/heap.js +75 -98
- package/dist/prod/core/invariants.js +1 -32
- package/dist/prod/core/lanes.js +712 -181
- package/dist/prod/core/owner.js +75 -96
- package/dist/prod/core/scheduler.js +686 -1335
- package/dist/prod/core/verdict.js +309 -621
- package/dist/prod/flatten.js +77 -0
- package/dist/prod/index.js +15 -13
- package/dist/prod/map.js +142 -145
- package/dist/prod/reveal.js +212 -0
- package/dist/prod/signals.js +155 -227
- package/dist/prod/store/affects.js +142 -0
- package/dist/prod/store/hooks.js +13 -0
- package/dist/prod/store/index.js +26 -19
- package/dist/prod/store/optimistic.js +566 -0
- package/dist/prod/store/projection.js +301 -0
- package/dist/prod/store/reconcile.js +259 -0
- package/dist/prod/store/store.js +1883 -272
- package/dist/prod/store/storePath.js +1 -1
- package/dist/{observe/store/next → prod/store}/target.js +13 -17
- package/dist/prod/store/types.js +73 -0
- package/dist/prod/store/utils.js +1 -11
- package/dist/types/affects.d.ts +11 -30
- package/dist/types/boundaries.d.ts +66 -176
- package/dist/types/carved.d.ts +1 -0
- package/dist/types/core/action.d.ts +5 -6
- package/dist/types/core/async.d.ts +10 -5
- package/dist/types/core/attribution-hooks.d.ts +20 -1
- package/dist/types/core/attribution.d.ts +26 -11
- package/dist/types/core/constants.d.ts +130 -180
- package/dist/types/core/core.d.ts +134 -264
- package/dist/types/core/dev.d.ts +2 -2
- package/dist/types/core/effect.d.ts +0 -7
- package/dist/types/core/graph.d.ts +7 -1
- package/dist/types/core/heap.d.ts +5 -7
- package/dist/types/core/index.d.ts +5 -5
- package/dist/types/core/invariants.d.ts +0 -29
- package/dist/types/core/lanes.d.ts +74 -86
- package/dist/types/core/owner.d.ts +3 -3
- package/dist/types/core/scheduler.d.ts +255 -331
- package/dist/types/core/types.d.ts +45 -85
- package/dist/types/core/verdict.d.ts +3 -0
- package/dist/types/flatten.d.ts +8 -0
- package/dist/types/index.d.ts +6 -4
- package/dist/types/reveal.d.ts +19 -0
- package/dist/types/signals.d.ts +35 -91
- package/dist/types/store/affects.d.ts +12 -0
- package/dist/types/store/hooks.d.ts +48 -0
- package/dist/types/store/index.d.ts +34 -15
- package/dist/types/store/optimistic.d.ts +23 -0
- package/dist/types/store/projection.d.ts +18 -0
- package/dist/types/store/reconcile.d.ts +3 -0
- package/dist/types/store/store.d.ts +132 -145
- package/dist/types/store/storePath.d.ts +1 -1
- package/dist/types/store/target.d.ts +151 -0
- package/dist/types/store/types.d.ts +84 -0
- package/package.json +1 -1
- package/dist/observe/core/optimistic.js +0 -669
- package/dist/observe/store/next/optimistic.js +0 -717
- package/dist/observe/store/next/projection.js +0 -266
- package/dist/observe/store/next/reconcile.js +0 -375
- package/dist/observe/store/next/store.js +0 -2608
- package/dist/prod/core/optimistic.js +0 -658
- package/dist/prod/store/next/optimistic.js +0 -710
- package/dist/prod/store/next/projection.js +0 -257
- package/dist/prod/store/next/reconcile.js +0 -373
- package/dist/prod/store/next/store.js +0 -2484
- package/dist/types/core/optimistic.d.ts +0 -6
- package/dist/types/store/next/optimistic.d.ts +0 -25
- package/dist/types/store/next/projection.d.ts +0 -27
- package/dist/types/store/next/reconcile.d.ts +0 -10
- package/dist/types/store/next/store.d.ts +0 -145
- package/dist/types/store/next/target.d.ts +0 -196
|
@@ -1,69 +1,313 @@
|
|
|
1
|
-
import { EFFECT_RENDER, EFFECT_USER,
|
|
1
|
+
import { EFFECT_RENDER, EFFECT_USER, CONFIG_HELD, CONFIG_OVERRIDE, NOT_PENDING, STATUS_UNINITIALIZED, STATUS_PENDING, CONFIG_STAGED, REACTIVE_DISPOSED, REACTIVE_ZOMBIE, CONFIG_VERDICT, CONFIG_HAS_SNAPSHOT, REACTIVE_RECOMPUTING_DEPS, REACTIVE_MISSED_WAKE, CONFIG_IN_SNAPSHOT_SCOPE, REACTIVE_SNAPSHOT_STALE, CONFIG_HELD_FRAME, REACTIVE_LANE_DIRTY, CONFIG_INPUTS_PUBLISHED, EFFECT_TRACKED, REACTIVE_MANUAL_WRITE, CONFIG_GUESS, REACTIVE_FRAME_READ, CONFIG_REDERIVE, STATUS_ERROR } from "./constants.js";
|
|
2
2
|
|
|
3
3
|
import { attrHooks } from "./attribution-hooks.js";
|
|
4
4
|
|
|
5
|
-
import { resyncUnflushedCompanions,
|
|
5
|
+
import { resyncUnflushedCompanions, recompute, markUnflushedStaged, ext } from "./core.js";
|
|
6
6
|
|
|
7
7
|
import { DEV, GRAPH_SIZE_WARN_AT, noteFanOut } from "./dev.js";
|
|
8
8
|
|
|
9
|
-
import { NotReadyError } from "./error.js";
|
|
10
|
-
|
|
11
9
|
import { sweepDormant, trimStaleDeps } from "./graph.js";
|
|
12
10
|
|
|
13
|
-
import { runHeap,
|
|
11
|
+
import { runHeap, enqueueSub, deleteFromHeap } from "./heap.js";
|
|
12
|
+
|
|
13
|
+
import { devCheckQuiescent } from "./invariants.js";
|
|
14
|
+
|
|
15
|
+
/** Live transactions — opened, unmerged, not landed. Scanned at every seam
|
|
16
|
+
* while non-empty; nothing on the plain path. */ const transactions = [];
|
|
17
|
+
|
|
18
|
+
/** Observe tier: this flush parked its transaction (`holdStart` fired). */ let holding = false;
|
|
19
|
+
|
|
20
|
+
/** A transaction (listed here) or a lane (lanes.ts lists its own). */ function newTransaction(e, n = null) {
|
|
21
|
+
const t = {
|
|
22
|
+
Je: [],
|
|
23
|
+
En: [ [], [] ],
|
|
24
|
+
an: [],
|
|
25
|
+
Sn: null,
|
|
26
|
+
_parent: n,
|
|
27
|
+
B: e,
|
|
28
|
+
rt: null,
|
|
29
|
+
tt: 0,
|
|
30
|
+
nt: false,
|
|
31
|
+
un: null,
|
|
32
|
+
Tt: false,
|
|
33
|
+
fn: false,
|
|
34
|
+
T: null
|
|
35
|
+
};
|
|
36
|
+
if (!e) transactions.push(t);
|
|
37
|
+
return t;
|
|
38
|
+
}
|
|
14
39
|
|
|
15
|
-
|
|
40
|
+
// Lanes (plan sec. 28, 2026-10-02; lanes.ts). A lane is "a new base of a transition":
|
|
41
|
+
// the sub-frame an optimistic write opens in the transition of the frame it
|
|
42
|
+
// is made in. It sees the screen plus its own guesses (plan sec. 19), breaks out of
|
|
43
|
+
// the parent's hold — its effects run now — and holds itself if its own
|
|
44
|
+
// derivations hit async. It ends when the parent lands: a guess reverts to
|
|
45
|
+
// the truth it covered, or lands as the truth that superseded it. A frame
|
|
46
|
+
// that does not park has nothing to be optimistic over: the write is void.
|
|
47
|
+
|
|
48
|
+
// The seat of a pass is its node's: a node carrying a lane's value runs as
|
|
49
|
+
// the lane's whoever dirtied it; a derivation's tracked read of a lane's
|
|
50
|
+
// value moves its pass into the lane; a leaf's never does (it reads the
|
|
51
|
+
// screen). Lane work's values go to the lane slot, its runs to the lane's
|
|
52
|
+
// queues; the rest of the frame is the flush's.
|
|
53
|
+
/** The lane the running pass is work of; set at `recompute`'s head from the
|
|
54
|
+
* node, moved by a derivation's lane read, saved and restored around the
|
|
55
|
+
* pass. */ let passLane = null;
|
|
56
|
+
|
|
57
|
+
function setPassLane(e) {
|
|
58
|
+
passLane = e;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/** The transaction this flush parks into: set by the first join, merged
|
|
62
|
+
* with every further one. Consumed at the seam. A join outside a flush (a
|
|
63
|
+
* mount's pending observer, a write to a held node, an action opening)
|
|
64
|
+
* schedules the seam that consumes it: membership is the tick's — the same
|
|
65
|
+
* synchronous frame — and the next tick's writes are not this one's. */ let flushTransaction = null;
|
|
66
|
+
|
|
67
|
+
/** The transaction the passes running OUTSIDE a flush this tick derive from
|
|
68
|
+
* (a mainline mount, a mainline re-pass): the passes' own (A29, creation-
|
|
69
|
+
* time form — "the entry is the pass's alone"): their results are staged
|
|
70
|
+
* into it, and nothing else in the tick is — a write made after the mount
|
|
71
|
+
* is a mainline write, a render effect mounted beside it a stale reader.
|
|
72
|
+
* Inside a flush, or in a tick that already has its transaction (an
|
|
73
|
+
* action's body), the frame joins instead (`flushTransaction`) — except a
|
|
74
|
+
* first pass a loading boundary that has not shown content catches (`own`,
|
|
75
|
+
* A29's boundary exemption, #3540): it is the boundary's, not the tick's,
|
|
76
|
+
* and a flush that has joined nothing keeps it pass-scoped as outside one.
|
|
77
|
+
* Cleared by the flush the join schedules, and at the end of a flush that
|
|
78
|
+
* set it. */ let passTx = null;
|
|
79
|
+
|
|
80
|
+
function joinPassTx(e, n) {
|
|
81
|
+
if (globalQueue.X && !n || flushTransaction !== null) return joinFuture(e);
|
|
82
|
+
if (passTx === null) passTx = resolveTx(e); else merge(resolveTx(e), passTx);
|
|
83
|
+
schedule();
|
|
84
|
+
}
|
|
16
85
|
|
|
17
|
-
|
|
86
|
+
/** The transaction holding a node (CONFIG_HELD set), resolved through
|
|
87
|
+
* merges; the path is compressed. */ function txOf(e) {
|
|
88
|
+
return e.m._t = resolveTx(e.m._t);
|
|
89
|
+
}
|
|
18
90
|
|
|
19
|
-
|
|
91
|
+
/** `t` resolved through merges while it is still parked or open; null once
|
|
92
|
+
* it has landed. */ function liveTx(e) {
|
|
93
|
+
e = resolveTx(e);
|
|
94
|
+
return transactions.indexOf(e) !== -1 ? e : null;
|
|
95
|
+
}
|
|
20
96
|
|
|
21
|
-
|
|
97
|
+
function resolveTx(e) {
|
|
98
|
+
while (e.Sn !== null) e = e.Sn;
|
|
99
|
+
return e;
|
|
100
|
+
}
|
|
22
101
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
102
|
+
/** The flush joins transaction `t` — or needs one of its own (`null`: a pass
|
|
103
|
+
* went pending with nothing held). Joining a second one merges it into the
|
|
104
|
+
* flush's: the work that touched both entangles them (A15). */ function joinFuture(e) {
|
|
105
|
+
const n = flushTransaction;
|
|
106
|
+
if (n === null) {
|
|
107
|
+
flushTransaction = e === null ? newTransaction(false) : resolveTx(e);
|
|
108
|
+
} else if (e !== null) merge(resolveTx(e), n);
|
|
109
|
+
if (!globalQueue.X) schedule();
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/** `t` merges into `f`: the work that touched both entangles them (A15).
|
|
113
|
+
* A lane never merges: it is a sub-frame, not a peer — its work is routed
|
|
114
|
+
* per pass (`passLane`). */ function merge(e, n) {
|
|
115
|
+
// Both ends resolved: a stale pointer to a transaction already merged
|
|
116
|
+
// into the other would close a cycle `resolveTx` never leaves.
|
|
117
|
+
e = resolveTx(e);
|
|
118
|
+
n = resolveTx(n);
|
|
119
|
+
if (e === n || e.B || n.B) return;
|
|
120
|
+
if (attrHooks !== null) attrHooks.transitionMerged(n, e);
|
|
121
|
+
e.Sn = n;
|
|
122
|
+
n.tt += e.tt;
|
|
123
|
+
n.nt ||= e.nt;
|
|
124
|
+
append(n.Je, e.Je);
|
|
125
|
+
append(n.En[0], e.En[0]);
|
|
126
|
+
append(n.En[1], e.En[1]);
|
|
127
|
+
append(n.an, e.an);
|
|
128
|
+
if (e.T !== null) append(n.T ??= [], e.T);
|
|
129
|
+
transactions.splice(transactions.indexOf(e), 1);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/** A15: a hold is a property of the async node — observed pending by a
|
|
133
|
+
* render reader. A transaction is blocked while a node it holds, in flight,
|
|
134
|
+
* has one: a pending render effect (the frame itself), or a pending held
|
|
135
|
+
* node a render effect reads. A stale reader served the committed value
|
|
136
|
+
* never went pending, but derives from the flight all the same (#3494) and
|
|
137
|
+
* is still linked to it; a memo between them is pending, and held, too — so
|
|
138
|
+
* one hop of `_subs` is the chain. Reads of the reader's LAST pass only (the
|
|
139
|
+
* link's generation is the pass's): a tail kept for A30 — the committed
|
|
140
|
+
* frame's dependency, awaiting the run that retires it — is not a read of
|
|
141
|
+
* the flight, and a reader that stopped reading releases it (O3, #3494).
|
|
142
|
+
* An errored pass (NotReady included) did not stop: it never got there, and
|
|
143
|
+
* its full list stands (A30) — the reads it did not reach still observe. A
|
|
144
|
+
* flight nobody renders holds nothing (A29). Only the nodes `t` still owns
|
|
145
|
+
* count: one a lane took over since is the lane's to wait on. A guess whose
|
|
146
|
+
* own truth is in flight blocks the lane's parent, not the lane — it stands
|
|
147
|
+
* in for the flight (A17: "visible until its own fetch settles"). */ function blocked(e) {
|
|
148
|
+
// The transaction being judged (a lane consulted on its behalf judges for
|
|
149
|
+
// it): a zombie whose removal it stages is moot for it alone (`onScreen`).
|
|
150
|
+
const n = judge;
|
|
151
|
+
judge ??= e;
|
|
152
|
+
// An action still running in it holds it open (action.ts). Its lanes
|
|
153
|
+
// (lanes.ts): a lane under `t` blocks it while blocked itself.
|
|
154
|
+
const t = e.tt !== 0 || blockedBy(e.Je, e) || !!GlobalQueue.dn && GlobalQueue.dn(e);
|
|
155
|
+
judge = n;
|
|
156
|
+
return t;
|
|
157
|
+
}
|
|
29
158
|
|
|
30
|
-
|
|
31
|
-
eE: new Array(2e3).fill(undefined),
|
|
32
|
-
tE: false,
|
|
33
|
-
st: 0,
|
|
34
|
-
EE: 0
|
|
35
|
-
};
|
|
159
|
+
let judge = null;
|
|
36
160
|
|
|
37
|
-
/**
|
|
38
|
-
*
|
|
39
|
-
*
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
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.oe & REACTIVE_OPTIMISTIC_DIRTY && !laneZombie(e)) return GlobalQueue.nt(e);
|
|
48
|
-
if (e.oe & REACTIVE_IN_HEAP_HEIGHT) e.oe &= -140; else {
|
|
49
|
-
deleteFromHeap(e, zombieQueue);
|
|
50
|
-
e.oe &= -132;
|
|
161
|
+
/** Observe tier: the flights `t` waits on — its pending held sources a
|
|
162
|
+
* frame reader observes (`blockedBy`'s predicate, collected; the readers
|
|
163
|
+
* themselves are not sources). */ function blockersOf(e) {
|
|
164
|
+
const n = [];
|
|
165
|
+
for (let t = 0; t < e.Je.length; t++) {
|
|
166
|
+
const l = e.Je[t];
|
|
167
|
+
if (!l.u && l.S & STATUS_PENDING && !(l.R & REACTIVE_DISPOSED) && (l.m._t === null || txOf(l) === e) && blockedBy([ l ], e)) n.push(l);
|
|
51
168
|
}
|
|
169
|
+
return n;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/** `t`'s own flights only — not its lanes': a flight the frame asked for
|
|
173
|
+
* (a refetch, a plain load) is authoritative; a lane's derivation flight is
|
|
174
|
+
* not (lanes.ts, the body-end corollary). A pending reader counts only if
|
|
175
|
+
* one of the sources it waits on is `t`'s (a render effect marked pending
|
|
176
|
+
* by a lane flight, never re-run, is the lane's wait, not the frame's). */ function ownFlights(e) {
|
|
177
|
+
return blockedBy(e.Je, e, true);
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
/** A source the pending node waits on that is `t`'s (held by it, or by no
|
|
181
|
+
* one) — not another transaction's or lane's work. Itself, when it is the
|
|
182
|
+
* source. */ function ownSource(e, n) {
|
|
183
|
+
const t = e.m?.Se;
|
|
184
|
+
if (t === undefined) return true;
|
|
185
|
+
for (const e of t) {
|
|
186
|
+
const t = e.m?._t;
|
|
187
|
+
if (t == null || resolveTx(t) === n) return true;
|
|
188
|
+
}
|
|
189
|
+
return false;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
function blockedBy(e, n, t = false) {
|
|
193
|
+
for (let l = 0; l < e.length; l++) {
|
|
194
|
+
const o = e[l];
|
|
195
|
+
if (!(o.S & STATUS_PENDING) || o.R & REACTIVE_DISPOSED || o.i & CONFIG_GUESS || o.m._t !== null && txOf(o) !== n || t && !ownSource(o, n)) continue;
|
|
196
|
+
// The frame's own pending observer (off screen: this one holds nothing;
|
|
197
|
+
// the next listed node may).
|
|
198
|
+
if (o.u === EFFECT_RENDER) {
|
|
199
|
+
if (onScreen(o, judge ?? n)) return true;
|
|
200
|
+
continue;
|
|
201
|
+
}
|
|
202
|
+
for (let e = o.t; e !== null; e = e.o) {
|
|
203
|
+
const t = e.l;
|
|
204
|
+
// A frame reader: a render effect, or a stale reader of the flight
|
|
205
|
+
// (REACTIVE_FRAME_READ — a verdict reader, `observeFlight`; lane work,
|
|
206
|
+
// `frameRead`): served committed instead of going pending, it derives
|
|
207
|
+
// from the flight all the same and is re-derived at the landing — the
|
|
208
|
+
// frame's observation survives its reader becoming a lane's (a guess
|
|
209
|
+
// over a held window, V5/A17). A probe alone does not hold. A
|
|
210
|
+
// boundary showing content forwards the pending (its output pending,
|
|
211
|
+
// CONFIG_REDERIVE): transparent, its readers hold through it (A33; the
|
|
212
|
+
// lane-membership ruling, 2026-10-05). An output that never committed
|
|
213
|
+
// shows nothing — a mount the frame has not revealed — and holds
|
|
214
|
+
// nothing.
|
|
215
|
+
if ((t.u === EFFECT_RENDER && t.S & STATUS_PENDING || t.R & REACTIVE_FRAME_READ || t.i & CONFIG_REDERIVE && t.S === STATUS_PENDING) && (e.Pe === t.pe || t.m?.F != null) && !(t.R & REACTIVE_DISPOSED) && onScreen(t, judge ?? n)) return true;
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
return false;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
/** A frame reader whose say counts for `t`'s hold — `t` the transaction
|
|
222
|
+
* being judged: not a zombie whose removal `t` stages (A15 #3463: a re-ask
|
|
223
|
+
* in the unmount frame lands its pending on the zombie it unmounts — the
|
|
224
|
+
* compiled <Show> of #3372; a zombie blocks every other judgment while it
|
|
225
|
+
* is visible, a lane's reveal included: only the commit that disposes it
|
|
226
|
+
* makes its say moot), and not behind a fallback (A33: a boundary showing its fallback is the display of
|
|
227
|
+
* everything under it, so a reader there is not on screen and holds
|
|
228
|
+
* nothing — boundaries.ts). */ function onScreen(e, n) {
|
|
229
|
+
return !(e.R & REACTIVE_ZOMBIE && removalStagedBy(e, n)) && !(GlobalQueue.se && GlobalQueue.se(e));
|
|
52
230
|
}
|
|
53
231
|
|
|
54
|
-
/**
|
|
55
|
-
*
|
|
56
|
-
*
|
|
57
|
-
*
|
|
58
|
-
*
|
|
232
|
+
/** A15 (#3463): a reader whose removal is staged — a zombie, parked by a
|
|
233
|
+
* pass awaiting its commit — is still on screen and live for every hold,
|
|
234
|
+
* except the transaction staging its removal: for that one its say is moot
|
|
235
|
+
* (done, and the commit disposes it; not done, and it stays parked). The
|
|
236
|
+
* staging transaction is the one holding the pass that parked the frame:
|
|
237
|
+
* the nearest non-zombie ancestor. */ function removalStagedBy(e, n) {
|
|
59
238
|
let t = e;
|
|
60
|
-
while (t !== null && t.
|
|
61
|
-
return t !== null && (t.
|
|
239
|
+
while (t !== null && t.R & REACTIVE_ZOMBIE) t = t._parent;
|
|
240
|
+
return t !== null && (t.i & CONFIG_HELD) !== 0 && t.m?._t != null && txOf(t) === resolveTx(n);
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
/** Unchanged passes this flush whose stale dependency tail awaits the
|
|
244
|
+
* flush's verdict (A30, #3469): trimmed when the flush commits, kept when
|
|
245
|
+
* it parks — the committed frame still derives from the previous pass's
|
|
246
|
+
* dependencies, and a write to one of them must reach the node until a
|
|
247
|
+
* committing pass replaces it. */ const heldTrims = [];
|
|
248
|
+
|
|
249
|
+
function heldTrim(e) {
|
|
250
|
+
heldTrims.push(e);
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
/** Zombies popped from the dirty heap this flush (`deferZombie`). The seam
|
|
254
|
+
* decides their fate: a park cancels them (the writes that dirtied them are
|
|
255
|
+
* held with it); a commit runs the survivors — the ones whose owner is still
|
|
256
|
+
* awaiting its commit — in height order, after the commits that would have
|
|
257
|
+
* disposed them (#3546). No second heap: the list is the deferral. */ const deferredZombies = [];
|
|
258
|
+
|
|
259
|
+
function deferZombie(e) {
|
|
260
|
+
deleteFromHeap(e, dirtyQueue);
|
|
261
|
+
e.R &= -4;
|
|
262
|
+
deferredZombies.push(e);
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
/** Put a staged node in a transaction: flagged, stamped, on its landing
|
|
266
|
+
* list. */ function holdNode(e, n) {
|
|
267
|
+
e.i |= CONFIG_HELD;
|
|
268
|
+
list(e, n);
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
/** Put a node on a transaction's list, once: a node's `_transaction` is the
|
|
272
|
+
* transaction whose `_nodes` it is on (every site that sets one does the
|
|
273
|
+
* other), so a re-pass, a re-landing or a re-staging adds nothing. */ function list(e, n) {
|
|
274
|
+
const t = ext(e);
|
|
275
|
+
if (t._t === n) return;
|
|
276
|
+
t._t = n;
|
|
277
|
+
n.Je.push(e);
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
/** Hold the (uncommitted) frame under a held pass: every computed in the
|
|
281
|
+
* live child chain, recursively. Plain owners are walked through, not held
|
|
282
|
+
* — nothing reads or re-passes them. Once per park, over the parked frames
|
|
283
|
+
* only; recomputes pay nothing for it. A lane's node in the frame (listed on
|
|
284
|
+
* a lane: a verdict reader broken out of this very hold — display-ahead — or
|
|
285
|
+
* a derivation of a guess, CONFIG_OVERRIDE; a lane pass's effect) is the
|
|
286
|
+
* lane's, as the seam's own loop has it: "lane work never makes its node
|
|
287
|
+
* transaction work" (A15, #3698). Re-listed here it would carry the lane's
|
|
288
|
+
* value on the transaction's list, where the landing nulls its transaction
|
|
289
|
+
* and `dissolveLane` no longer finds it — a lane node with no lane, which the
|
|
290
|
+
* next read of it trips over (`laneRead` → `txOf`). Its frame is the lane's
|
|
291
|
+
* too (ruling A: a lane pass's children are the lane's), revealed and
|
|
292
|
+
* retired at the lane's seam. */ function holdFrame(e, n) {
|
|
293
|
+
for (let t = e.nn; t !== null; t = t.tn) {
|
|
294
|
+
if (t.m?._t?.B) continue;
|
|
295
|
+
if (t.ee !== undefined) {
|
|
296
|
+
if (!(t.i & CONFIG_HELD)) holdNode(t, n);
|
|
297
|
+
t.i |= CONFIG_HELD_FRAME;
|
|
298
|
+
}
|
|
299
|
+
holdFrame(t, n);
|
|
300
|
+
}
|
|
62
301
|
}
|
|
63
302
|
|
|
64
|
-
|
|
303
|
+
const dirtyQueue = {
|
|
304
|
+
jt: new Array(2e3).fill(undefined),
|
|
305
|
+
N: false,
|
|
306
|
+
ut: 0,
|
|
307
|
+
Kt: 0
|
|
308
|
+
};
|
|
65
309
|
|
|
66
|
-
let
|
|
310
|
+
let clock = 0;
|
|
67
311
|
|
|
68
312
|
let scheduled = false;
|
|
69
313
|
|
|
@@ -73,56 +317,6 @@ let haltNotified = false;
|
|
|
73
317
|
|
|
74
318
|
let syncDepth = 0;
|
|
75
319
|
|
|
76
|
-
let projectionWriteActive = false;
|
|
77
|
-
|
|
78
|
-
/** > 0 while an action's generator body is on the stack (the synchronous
|
|
79
|
-
* slice between yields). Maintained by action.ts around `it.next()`. */ let actionStepDepth = 0;
|
|
80
|
-
|
|
81
|
-
function enterActionStep() {
|
|
82
|
-
actionStepDepth++;
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
function exitActionStep() {
|
|
86
|
-
actionStepDepth--;
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
// Store property nodes whose last subscriber left while they carried state
|
|
90
|
-
// the backing cannot reconstruct — an optimistic override (overrides live on
|
|
91
|
-
// nodes, over a clone the setter discards) or a staged write. Releasing the
|
|
92
|
-
// slot then would drop the override: an optimistic store key read `0` the
|
|
93
|
-
// moment its only reader gated away while the action was live (S7). Swept
|
|
94
|
-
// after each flush — a node still without subs whose override and staging
|
|
95
|
-
// have resolved is released through the slot hook; one that regained a
|
|
96
|
-
// subscriber leaves the set.
|
|
97
|
-
const transientStoreNodes = new Set;
|
|
98
|
-
|
|
99
|
-
/** Slot hook's deferral: release this node when its carried state resolves. */ function deferSlotRelease(e) {
|
|
100
|
-
transientStoreNodes.add(e);
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
function canUseSimpleSyncFlush(e) {
|
|
104
|
-
const t = e.m;
|
|
105
|
-
return transitions.size === 0 && activeLanes.size === 0 && e.Xn.length === 0 && t.bn.length === 0 && t.A.length === 0 && t.En.size === 0 && transientStoreNodes.size === 0 && pendingRearms.size === 0;
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
function sweepTransientStoreNodes() {
|
|
109
|
-
if (transientStoreNodes.size === 0) return;
|
|
110
|
-
for (const e of transientStoreNodes) {
|
|
111
|
-
if (e.u !== null) {
|
|
112
|
-
transientStoreNodes.delete(e);
|
|
113
|
-
continue;
|
|
114
|
-
}
|
|
115
|
-
if (e._e !== NOT_PENDING) continue;
|
|
116
|
-
if (e.o?.we !== undefined && e.o?.we !== NOT_PENDING) continue;
|
|
117
|
-
// A live affects() mark keeps the node addressable: sweeping it would
|
|
118
|
-
// detach the refcount from the slot (a fresh probe would upsert a new,
|
|
119
|
-
// unmarked node for the same property).
|
|
120
|
-
if (e.o?.t) continue;
|
|
121
|
-
transientStoreNodes.delete(e);
|
|
122
|
-
if (e.C & CONFIG_SLOT_NODE) slotUnobservedHook(e); else e.o?.bt?.();
|
|
123
|
-
}
|
|
124
|
-
}
|
|
125
|
-
|
|
126
320
|
/**
|
|
127
321
|
* Toggles the dev-mode "must be inside a `<Loading>` boundary" enforcement
|
|
128
322
|
* window. Only `render()` calls this — wrapping the initial mount so that a
|
|
@@ -132,170 +326,39 @@ function sweepTransientStoreNodes() {
|
|
|
132
326
|
* @internal
|
|
133
327
|
*/ function enforceLoadingBoundary(e) {}
|
|
134
328
|
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
329
|
+
// Dev-only marker for the effect half of createEffect/createRenderEffect, so
|
|
330
|
+
// flush() can report the no-op instead of failing silently (React parity).
|
|
331
|
+
/** Action bodies on the stack (action.ts): `flush()` is refused inside one —
|
|
332
|
+
* the body's writes are its transaction's and commit when it settles; a
|
|
333
|
+
* drain mid-step cannot reveal them, and dev says so. */ let actionDepth = 0;
|
|
138
334
|
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
* current-transaction-shaped batch (`globalQueue._batch`). With no transition
|
|
142
|
-
* active, registrations (pending commits, optimistic nodes, affects marks,
|
|
143
|
-
* optimistic stores) land in a plain ambient batch that the plain flush
|
|
144
|
-
* finalizes; when a transition initializes it adopts the ambient batch's
|
|
145
|
-
* contents and `_batch` becomes the transition itself, so later registrations
|
|
146
|
-
* land there directly — no per-field aliasing.
|
|
147
|
-
*/ function createBatch() {
|
|
148
|
-
return {
|
|
149
|
-
Ge: clock,
|
|
150
|
-
In: [],
|
|
151
|
-
ae: new Map,
|
|
152
|
-
bn: [],
|
|
153
|
-
A: [],
|
|
154
|
-
En: new Set,
|
|
155
|
-
tt: [],
|
|
156
|
-
ei: {
|
|
157
|
-
ti: [ [], [] ],
|
|
158
|
-
Xn: []
|
|
159
|
-
},
|
|
160
|
-
Dt: false,
|
|
161
|
-
Tt: new Set,
|
|
162
|
-
ht: null
|
|
163
|
-
};
|
|
335
|
+
function enterAction() {
|
|
336
|
+
actionDepth++;
|
|
164
337
|
}
|
|
165
338
|
|
|
166
|
-
function
|
|
167
|
-
|
|
168
|
-
t.Dt = e;
|
|
169
|
-
e.tt.push(...t.tt);
|
|
170
|
-
e.et ||= t.et;
|
|
171
|
-
for (const n of activeLanes) if (n.me === t) n.me = e;
|
|
172
|
-
if (t.bn.length) {
|
|
173
|
-
// Move (don't copy): the global queue's batch may still be the outgoing
|
|
174
|
-
// transition, and the adoption pass in initTransition would re-push its
|
|
175
|
-
// contents into the target — duplicating every entry.
|
|
176
|
-
e.bn.push(...t.bn);
|
|
177
|
-
t.bn.length = 0;
|
|
178
|
-
}
|
|
179
|
-
if (t.A.length) {
|
|
180
|
-
// Move (don't copy): the global queue's batch may still be the outgoing
|
|
181
|
-
// transition, and the adoption pass in initTransition would re-push its
|
|
182
|
-
// contents into the target — double-releasing every mark.
|
|
183
|
-
e.A.push(...t.A);
|
|
184
|
-
t.A.length = 0;
|
|
185
|
-
}
|
|
186
|
-
for (const n of t.En) e.En.add(n);
|
|
187
|
-
for (const [n, i] of t.ae) {
|
|
188
|
-
let t = e.ae.get(n);
|
|
189
|
-
if (!t) e.ae.set(n, t = new Set);
|
|
190
|
-
for (const e of i) t.add(e);
|
|
191
|
-
}
|
|
192
|
-
for (const n of t.Tt) e.Tt.add(n);
|
|
193
|
-
if (t.ht) (e.ht ??= []).push(...t.ht);
|
|
339
|
+
function exitAction() {
|
|
340
|
+
actionDepth--;
|
|
194
341
|
}
|
|
195
342
|
|
|
196
|
-
/**
|
|
197
|
-
*
|
|
198
|
-
*
|
|
199
|
-
*
|
|
200
|
-
*
|
|
201
|
-
*
|
|
202
|
-
*
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
* Runs inside the predicate's compute (pure phase) — the confirming
|
|
209
|
-
* transition's stamps are still live and its commit decision hasn't run, so
|
|
210
|
-
* the merge lands before any reveal. Only the tree-shaken graphs that call
|
|
211
|
-
* `until()` retain this.
|
|
212
|
-
*/ function entangleConfirmingTransitions(e, t) {
|
|
213
|
-
t = currentTransition(t);
|
|
214
|
-
if (t.Dt === true) return;
|
|
215
|
-
// The confirming evidence is a dep whose value is STAGED (pending,
|
|
216
|
-
// uncommitted) at flip evaluation — committed deps are public already and
|
|
217
|
-
// carry nothing to entangle. A staged dep lives in one of two carriers: a
|
|
218
|
-
// stamped transition, or the queue's current batch (ambient registrations
|
|
219
|
-
// don't stamp; "ambient work IS a transaction" — the batch is the
|
|
220
|
-
// carrier). The entangle STEALS the carrier's staged cargo — its pending
|
|
221
|
-
// nodes move (re-stamped) into the awaiting transaction and reveal at its
|
|
222
|
-
// settle — but never the carrier itself: its async reporters, actions,
|
|
223
|
-
// and stashes are its own future (a live stream's flight must not chain
|
|
224
|
-
// the awaiting transaction to landings that haven't happened; a merged
|
|
225
|
-
// reporter deadlocked exactly that way).
|
|
226
|
-
let n = false;
|
|
227
|
-
for (let i = e.Ee; i !== null; i = i.Ie) {
|
|
228
|
-
const r = i.De;
|
|
229
|
-
if (r._e !== NOT_PENDING) {
|
|
230
|
-
const e = r.me;
|
|
231
|
-
const i = e != null ? currentTransition(e) : null;
|
|
232
|
-
// Skip the awaiting transaction's own cargo (t === target: a
|
|
233
|
-
// fold-staged landing or a write the action itself issued — hold and
|
|
234
|
-
// reveal already correct) and dead carriers. Ambient-batch staging
|
|
235
|
-
// (t === null) must leave the batch NOW — it commits at this flush's
|
|
236
|
-
// end, which would reveal the confirmation under the live optimism
|
|
237
|
-
// it just confirmed.
|
|
238
|
-
const s = i === null ? currentBatch.In : i !== t && i.Dt !== true ? i.In : null;
|
|
239
|
-
if (s !== null) n = stealEntangledCargo(s, t) || n;
|
|
240
|
-
}
|
|
241
|
-
if (i === e.Je) break;
|
|
242
|
-
}
|
|
243
|
-
// The steal never activates the awaiting transaction: the predicate can
|
|
244
|
-
// flip inside another transaction's finalize heap, and adopting the queue
|
|
245
|
-
// batch there hands the stolen cargo to that finalize's commit sweep — a
|
|
246
|
-
// premature reveal at a foreign settle. Subscribers that computed against
|
|
247
|
-
// the pre-steal world were re-dirtied by the steal itself, so this
|
|
248
|
-
// flush's applies paint the mid-hold view (committed for lane and stale
|
|
249
|
-
// readers; a deriving reader is held with the cargo); the cargo commits
|
|
250
|
-
// at the awaiting transaction's own settle.
|
|
343
|
+
/** Provenance (A18, #3331): the question being asked — an action's
|
|
344
|
+
* sequence while a slice of its body and the flush that carries it run, 0
|
|
345
|
+
* otherwise. A guess is stamped with it (lanes.ts; a mainline guess takes a
|
|
346
|
+
* fresh sequence: the newest intent), a flight with the question that
|
|
347
|
+
* started it (async.ts; mainline is always the current question). An
|
|
348
|
+
* answer from an older question than the guess it lands on is stale: held
|
|
349
|
+
* silently, it does not move the graph. */ let question = 0;
|
|
350
|
+
|
|
351
|
+
let questions = 0;
|
|
352
|
+
|
|
353
|
+
function setQuestion(e) {
|
|
354
|
+
question = e;
|
|
251
355
|
}
|
|
252
356
|
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
* the override already hides their staged value per A17, and is usually
|
|
256
|
-
* the very optimism this confirmation settles); the mask's commit
|
|
257
|
-
* registers the settle-side post-revert wake. The carrier's array is
|
|
258
|
-
* emptied so its own commit point commits none of the stolen cargo.
|
|
259
|
-
*
|
|
260
|
-
* EFFECT subs of stolen nodes re-run: any that recomputed against the
|
|
261
|
-
* staging BEFORE the steal (the carrier's landing notified them as a plain
|
|
262
|
-
* write) hold a private result derived from the carrier's world — one
|
|
263
|
-
* that the next paint gate (stash-point lane run, a foreign flush's
|
|
264
|
-
* completion drain) would apply as the carrier's. Re-running them now that
|
|
265
|
-
* the cargo is the awaiting transaction's re-routes each by its posture in
|
|
266
|
-
* this same heap pass: a lane pass keeps committed (the mask), a stale
|
|
267
|
-
* render effect keeps committed and registers for the reveal's replay
|
|
268
|
-
* (stale-of-foreign), and a deriving user effect is served the truth and
|
|
269
|
-
* held with the transaction (A29). PURE computeds are deliberately NOT
|
|
270
|
-
* re-run: a staged value of theirs is itself stolen cargo — held with the
|
|
271
|
-
* transaction, so ordinary readers already serve their committed value —
|
|
272
|
-
* while re-running them would re-derive the OLD world and re-stage it over
|
|
273
|
-
* the held truth. The reveal re-notifies (commitPendingNodes), which is
|
|
274
|
-
* when they re-derive for real. */ function stealEntangledCargo(e, t) {
|
|
275
|
-
if (e === t.In || e.length === 0) return false;
|
|
276
|
-
for (let n = 0; n < e.length; n++) {
|
|
277
|
-
const i = e[n];
|
|
278
|
-
i.me = t;
|
|
279
|
-
t.In.push(i);
|
|
280
|
-
// Override-covered nodes stay silent AND unmasked: the override is the
|
|
281
|
-
// display (A17 — its staging never notified, its revert will), so their
|
|
282
|
-
// subs saw nothing and re-running one would break the silence with a
|
|
283
|
-
// duplicate fire of an unchanged view.
|
|
284
|
-
if (!hasActiveOverride(i)) {
|
|
285
|
-
i.C |= CONFIG_HELD_TRUTH;
|
|
286
|
-
for (let e = i.u; e !== null; e = e.Pe) {
|
|
287
|
-
const t = e.Ae;
|
|
288
|
-
if (t.He && !(t.C & CONFIG_AUTHORITATIVE_READ)) enqueueSub(t);
|
|
289
|
-
}
|
|
290
|
-
}
|
|
291
|
-
}
|
|
292
|
-
e.length = 0;
|
|
293
|
-
transitions.add(t);
|
|
294
|
-
return true;
|
|
357
|
+
function nextQuestion() {
|
|
358
|
+
return ++questions;
|
|
295
359
|
}
|
|
296
360
|
|
|
297
|
-
|
|
298
|
-
* projection draft was writing (see below). Consumed by `scheduleWithheld`. */ let withheld = false;
|
|
361
|
+
const MAINLINE_QUESTION = 2147483647;
|
|
299
362
|
|
|
300
363
|
function schedule() {
|
|
301
364
|
if (halted) {
|
|
@@ -304,83 +367,11 @@ function schedule() {
|
|
|
304
367
|
}
|
|
305
368
|
if (scheduled) return;
|
|
306
369
|
scheduled = true;
|
|
307
|
-
if (!syncDepth && !globalQueue
|
|
308
|
-
// A projection draft's writes withhold the microtask: a flight's landing
|
|
309
|
-
// drains them (asyncWrite's flush), and a drain BEFORE the landing tears
|
|
310
|
-
// the pre- and post-await halves of the run apart (createProjection.async
|
|
311
|
-
// "notifies only changed paths"). A draft write that no landing will
|
|
312
|
-
// follow re-arms through scheduleWithheld (proj R37).
|
|
313
|
-
withheld = projectionWriteActive;
|
|
314
|
-
if (!withheld) queueMicrotask(flush);
|
|
315
|
-
}
|
|
316
|
-
}
|
|
317
|
-
|
|
318
|
-
/** Arm the microtask `schedule()` withheld under projectionWriteActive. The
|
|
319
|
-
* projection draft calls this after a write made outside its run with no
|
|
320
|
-
* flight up (proj R37): nothing else will drain, and leaving `scheduled`
|
|
321
|
-
* armed with no microtask strands the whole scheduler — every later
|
|
322
|
-
* `schedule()` early-returns — until something calls `flush()` by hand.
|
|
323
|
-
* (`withheld` was set in the same synchronous slice as this call, so the
|
|
324
|
-
* syncDepth/_running gates it passed still hold; a stale mark left by a
|
|
325
|
-
* landing's own flush arms at worst one no-op drain.) */ function scheduleWithheld() {
|
|
326
|
-
if (withheld) {
|
|
327
|
-
withheld = false;
|
|
370
|
+
if (!syncDepth && !globalQueue.X) {
|
|
328
371
|
queueMicrotask(flush);
|
|
329
372
|
}
|
|
330
373
|
}
|
|
331
374
|
|
|
332
|
-
/**
|
|
333
|
-
* Parked transactions whose reporter set changed without a write. A
|
|
334
|
-
* transaction completes when nothing live reports a flight it waits on, but
|
|
335
|
-
* the flush only judges the ACTIVE transaction: a parked one is re-entered by
|
|
336
|
-
* a stamped node's landing or an action's resume. A reporter that stops
|
|
337
|
-
* counting for another reason — its loading boundary flipped to the fallback
|
|
338
|
-
* (#3375), or it was disposed by ambient work (#3372) — is neither: the
|
|
339
|
-
* pruning in `reporterBlocksSource` would drop it at the next check, but no
|
|
340
|
-
* check comes, and the writes held with it stay staged. Such sites record the
|
|
341
|
-
* transaction here (deduped: one idle pass per transaction, however many
|
|
342
|
-
* reporters changed); the flush re-enters it on an otherwise idle pass, so
|
|
343
|
-
* the re-evaluation adopts no unrelated ambient work.
|
|
344
|
-
*/ const wokenTransitions = [];
|
|
345
|
-
|
|
346
|
-
/** Wake every parked transaction — for a site that knows a reporter stopped
|
|
347
|
-
* counting but not whose (a boundary reset). */ function wakeParked() {
|
|
348
|
-
for (const e of transitions) wokenTransitions.includes(e) || wokenTransitions.push(e);
|
|
349
|
-
schedule();
|
|
350
|
-
}
|
|
351
|
-
|
|
352
|
-
/**
|
|
353
|
-
* Boundaries whose `on` dependencies notified this flush (#3540; a Set:
|
|
354
|
-
* many notifications, one re-arm). The notification arrives inside a pass,
|
|
355
|
-
* at the height of the `on` reads — before the readers the write put in
|
|
356
|
-
* flight are registered — so the re-arm waits for the heap and runs before
|
|
357
|
-
* the verdict (GlobalQueue.run → drainRearms), still under the write's
|
|
358
|
-
* transaction: the release is seen by the verdict that follows, and the
|
|
359
|
-
* staged fallback swap lands with the write's frame.
|
|
360
|
-
*/ const pendingRearms = new Set;
|
|
361
|
-
|
|
362
|
-
function queueRearm(e) {
|
|
363
|
-
pendingRearms.add(e);
|
|
364
|
-
schedule();
|
|
365
|
-
}
|
|
366
|
-
|
|
367
|
-
/** The drain (see pendingRearms): each boundary decides for itself, from
|
|
368
|
-
* what is pending under it now, whether re-arming means its fallback or
|
|
369
|
-
* nothing. Snapshot first: a re-arm can queue another — a write it makes
|
|
370
|
-
* notifying an `on` that reads it — which is the next pass's. */ function drainRearms() {
|
|
371
|
-
const e = Array.from(pendingRearms);
|
|
372
|
-
pendingRearms.clear();
|
|
373
|
-
for (let t = 0; t < e.length; t++) e[t].se();
|
|
374
|
-
}
|
|
375
|
-
|
|
376
|
-
/** Transactions a mainline tick has PROPOSED against (A34, #3494): a write to a
|
|
377
|
-
* node one of them holds — the same value or another — is a second proposal
|
|
378
|
-
* on a contested node, and the tick reveals with the hold ("both are
|
|
379
|
-
* suggesting a value; if one finished before the other that would be odd").
|
|
380
|
-
* Entered at the next flush's start, where the ambient batch is adopted;
|
|
381
|
-
* never from the write itself, which left `activeTransition` set across the
|
|
382
|
-
* caller's block and made creation after the write the transaction's (A29). */ const batchJoins = [];
|
|
383
|
-
|
|
384
375
|
/**
|
|
385
376
|
* Permanently halts the reactive system. Called when a user error escapes
|
|
386
377
|
* every boundary — app state is undefined at that point, so scheduling stops
|
|
@@ -397,7 +388,7 @@ function queueRearm(e) {
|
|
|
397
388
|
function haltReactivity(e) {
|
|
398
389
|
if (halted) return;
|
|
399
390
|
halted = true;
|
|
400
|
-
let
|
|
391
|
+
let n = "[REACTIVITY_HALTED]";
|
|
401
392
|
// Surface the cause here too: callers rethrow it, but a creation-time throw
|
|
402
393
|
// unwinds through ancestor recomputes that convert it to status instead of
|
|
403
394
|
// surfacing it (#2884), so the rethrow alone cannot guarantee visibility.
|
|
@@ -407,9 +398,9 @@ function haltReactivity(e) {
|
|
|
407
398
|
// nothing an app or its telemetry can act on (#3338 — an uncaught throw
|
|
408
399
|
// during the hydration render). The rethrow may reach the top as well in
|
|
409
400
|
// the non-swallowed cases; a duplicate report beats a silent one.
|
|
410
|
-
const
|
|
411
|
-
|
|
412
|
-
|
|
401
|
+
const t = e !== undefined && globalThis.reportError;
|
|
402
|
+
t || e === undefined ? console.error(n) : console.error(n, e);
|
|
403
|
+
t && t(e);
|
|
413
404
|
}
|
|
414
405
|
|
|
415
406
|
// Logs on the first write after a halt so a frozen interaction is traceable.
|
|
@@ -424,561 +415,339 @@ function notifyHalted() {
|
|
|
424
415
|
haltNotified = false;
|
|
425
416
|
}
|
|
426
417
|
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
class Queue {
|
|
432
|
-
_parent=null;
|
|
433
|
-
ti=[ [], [] ];
|
|
434
|
-
Xn=[];
|
|
435
|
-
ni=0;
|
|
436
|
-
created=clock;
|
|
437
|
-
addChild(e) {
|
|
438
|
-
this.Xn.push(e);
|
|
439
|
-
e._parent = this;
|
|
440
|
-
}
|
|
441
|
-
removeChild(e) {
|
|
442
|
-
const t = this.Xn.indexOf(e);
|
|
443
|
-
if (t >= 0) {
|
|
444
|
-
this.Xn.splice(t, 1);
|
|
445
|
-
e._parent = null;
|
|
446
|
-
}
|
|
447
|
-
}
|
|
448
|
-
notify(e, t, n, i) {
|
|
449
|
-
if (this._parent) return this._parent.notify(e, t, n, i);
|
|
450
|
-
return false;
|
|
451
|
-
}
|
|
452
|
-
run(e) {
|
|
453
|
-
if (this.ti[e - 1].length) {
|
|
454
|
-
const t = this.ti[e - 1];
|
|
455
|
-
this.ti[e - 1] = [];
|
|
456
|
-
runQueue(t, e);
|
|
457
|
-
}
|
|
458
|
-
// Effects run here can dispose owners, and disposal removes queues from
|
|
459
|
-
// this list — the running child itself, an earlier sibling, or several at
|
|
460
|
-
// once. A plain index walk then skips whatever shifted into the cursor.
|
|
461
|
-
// Stamping each child before it runs makes the pass idempotent, so a shift
|
|
462
|
-
// can be recovered by rescanning from the front and every child still runs
|
|
463
|
-
// exactly once. Children appended mid-pass carry a stale stamp and run,
|
|
464
|
-
// matching the previous live-array behaviour.
|
|
465
|
-
const t = this.Xn;
|
|
466
|
-
const n = ++queueRunToken;
|
|
467
|
-
for (let i = 0; i < t.length; ) {
|
|
468
|
-
const r = t[i];
|
|
469
|
-
if (r.ni !== n) {
|
|
470
|
-
r.ni = n;
|
|
471
|
-
r.run?.(e);
|
|
472
|
-
if (t[i] !== r) {
|
|
473
|
-
i = 0;
|
|
474
|
-
continue;
|
|
475
|
-
}
|
|
476
|
-
}
|
|
477
|
-
i++;
|
|
478
|
-
}
|
|
479
|
-
}
|
|
480
|
-
enqueue(e, t) {
|
|
481
|
-
if (e) {
|
|
482
|
-
// Route to lane's effect queue if we're in an optimistic recomputation
|
|
483
|
-
if (currentOptimisticLane) {
|
|
484
|
-
const n = findLane(currentOptimisticLane);
|
|
485
|
-
n.It[e - 1].push(t);
|
|
486
|
-
} else {
|
|
487
|
-
this.ti[e - 1].push(t);
|
|
488
|
-
}
|
|
489
|
-
}
|
|
490
|
-
schedule();
|
|
491
|
-
}
|
|
492
|
-
stashQueues(e) {
|
|
493
|
-
// Attribution hook: the parking transition's lane effects have run; its
|
|
494
|
-
// queues are being stashed. Root call only (children recurse below).
|
|
495
|
-
if (attrHooks !== null && this === globalQueue) attrHooks.holdEnd();
|
|
496
|
-
e.ti[0].push(...this.ti[0]);
|
|
497
|
-
e.ti[1].push(...this.ti[1]);
|
|
498
|
-
this.ti = [ [], [] ];
|
|
499
|
-
for (let t = 0; t < this.Xn.length; t++) {
|
|
500
|
-
let n = this.Xn[t];
|
|
501
|
-
let i = e.Xn[t];
|
|
502
|
-
if (!i) {
|
|
503
|
-
i = {
|
|
504
|
-
ti: [ [], [] ],
|
|
505
|
-
Xn: []
|
|
506
|
-
};
|
|
507
|
-
e.Xn[t] = i;
|
|
508
|
-
}
|
|
509
|
-
n.stashQueues(i);
|
|
510
|
-
}
|
|
511
|
-
}
|
|
512
|
-
restoreQueues(e) {
|
|
513
|
-
this.ti[0].push(...e.ti[0]);
|
|
514
|
-
this.ti[1].push(...e.ti[1]);
|
|
515
|
-
for (let t = 0; t < e.Xn.length; t++) {
|
|
516
|
-
const n = e.Xn[t];
|
|
517
|
-
let i = this.Xn[t];
|
|
518
|
-
if (i) i.restoreQueues(n);
|
|
519
|
-
}
|
|
520
|
-
}
|
|
521
|
-
}
|
|
522
|
-
|
|
523
|
-
class GlobalQueue extends Queue {
|
|
524
|
-
$t=false;
|
|
525
|
-
// The current transaction-shaped batch: a plain ambient batch while no
|
|
526
|
-
// transition is active, the active transition itself after initTransition.
|
|
527
|
-
m=createBatch();
|
|
528
|
-
static nt;
|
|
529
|
-
static ut;
|
|
530
|
-
static Rt;
|
|
531
|
-
static ii=null;
|
|
532
|
-
// Store-side hook: drops a keyless affects() mark's identity scope when the
|
|
533
|
-
// carrier node's last registration releases (wired by store.ts, mirroring
|
|
534
|
-
// _clearOptimisticStore).
|
|
535
|
-
static p=null;
|
|
536
|
-
// affects()-side hooks (wired by affects.ts, mirroring _update): the mark
|
|
537
|
-
// engine — count/register/release — lives with the feature. Every call site
|
|
538
|
-
// is gated by state only that module creates, so `!` invocations are safe
|
|
539
|
-
// once the gate holds.
|
|
540
|
-
static G=null;
|
|
541
|
-
static M=null;
|
|
542
|
-
static j=null;
|
|
543
|
-
// External-source bridge (wired by enableExternalSource(); null while no
|
|
544
|
-
// config is active — including after _resetExternalSourceConfig()).
|
|
545
|
-
static Bt=null;
|
|
546
|
-
static Jt=null;
|
|
547
|
-
// Verdict-layer hooks (wired by verdict.ts when isPending()/latest() are
|
|
548
|
-
// imported; null in apps that never use them). Call sites either guard for
|
|
549
|
-
// null or sit behind state only the verdict layer can create (`!` is safe
|
|
550
|
-
// there: `_pendingSignal`/`_latestValueComputed` are only ever assigned by
|
|
551
|
-
// verdict.ts, and `pendingCheckActive`/`latestReadActive` only flip inside
|
|
552
|
-
// isPending()/latest()).
|
|
553
|
-
static Le=null;
|
|
554
|
-
static Ue=null;
|
|
555
|
-
static qe=null;
|
|
556
|
-
static Qn=null;
|
|
557
|
-
static Cn=null;
|
|
558
|
-
static Rn=null;
|
|
559
|
-
static Hn=null;
|
|
560
|
-
static At=null;
|
|
561
|
-
static k=null;
|
|
562
|
-
static ri=null;
|
|
563
|
-
// Re-asks probes whose verdict was provisionally suppressed by a fresh read
|
|
564
|
-
// of a held value, once the transaction gains an async blocker (#3028).
|
|
565
|
-
static si=null;
|
|
566
|
-
// Optimistic-engine hooks (wired by core/optimistic.ts via
|
|
567
|
-
// installOptimisticEngine(), called from verdict.ts / createOptimistic /
|
|
568
|
-
// createOptimisticStore — every module that can create optimistic state).
|
|
569
|
-
// Call sites are gated by state only the engine can create: an
|
|
570
|
-
// `_overrideValue` slot, a lane in `activeLanes`, an `_optimisticNodes`
|
|
571
|
-
// entry, or a non-null `currentOptimisticLane`, so `!` invocations are safe
|
|
572
|
-
// once the gate holds.
|
|
573
|
-
static Ln=null;
|
|
574
|
-
static Bn=null;
|
|
575
|
-
static Yn=null;
|
|
576
|
-
static Wn=null;
|
|
577
|
-
static Kn=null;
|
|
578
|
-
/** Patch-channel optimistic drain (next/patch.ts): optimistic emissions
|
|
579
|
-
* apply at lane-effect timing — visible in flight, unlike the regular
|
|
580
|
-
* effect queues an action stashes. Injected; null when unused. */
|
|
581
|
-
static Zn=null;
|
|
582
|
-
static Fn=null;
|
|
583
|
-
static Dn=null;
|
|
584
|
-
/** Is the node routed through a LIVE lane (`resolveLane`)? read()'s reveal
|
|
585
|
-
* carve-out asks before showing a foreign-held pending node's committed
|
|
586
|
-
* value: a lane-derived flight's inputs are already revealed through the
|
|
587
|
-
* lane (#3334). Gated on CONFIG_HAS_LANE, which only the engine sets. */
|
|
588
|
-
static Gn=null;
|
|
589
|
-
static An=null;
|
|
590
|
-
static _t=null;
|
|
591
|
-
static Ot=null;
|
|
592
|
-
/** Authoritative-view reader wakeup: installed by until() and refresh() before
|
|
593
|
-
* their first read. Call sites are gated by CONFIG_AUTHORITATIVE_OBSERVED, which
|
|
594
|
-
* only such a reader's carve-out read can set, so `!` invocations are safe once
|
|
595
|
-
* the gate holds (#3303). */
|
|
596
|
-
static Xt=null;
|
|
597
|
-
static St=null;
|
|
598
|
-
/** A18 supersession (#3331): own-source truth `value` landed under an active
|
|
599
|
-
* override. The engine decides whether the graph re-derives — the value
|
|
600
|
-
* differs from the override and is not a stale (older-action) answer (mark
|
|
601
|
-
* the node, demote its lane cascade, notify), or returns to it after an
|
|
602
|
-
* earlier differing arrival (clear the mark, notify) — and owns the
|
|
603
|
-
* authoritative-observer wake for a silent confirm. Installed with the
|
|
604
|
-
* optimistic engine; only reachable on a node that has an override. */
|
|
605
|
-
static Qe=null;
|
|
606
|
-
/** The flush's pre-verdict step (#3427): once the transaction's action
|
|
607
|
-
* bodies have all ended and nothing authoritative is left in flight, the
|
|
608
|
-
* engine supersedes every override still in force with the truth it
|
|
609
|
-
* reverts to, so the graph re-derives from it now, as the transaction's
|
|
610
|
-
* held work, instead of after the flights the overrides fed have landed.
|
|
611
|
-
* True when it superseded something: the caller re-runs the heap ahead of
|
|
612
|
-
* the verdict. The engine owns every gate (acted, actions drained, has
|
|
613
|
-
* overrides, no store edits, no authoritative flight); null without it. */
|
|
614
|
-
static zn=null;
|
|
615
|
-
/** read()'s value for a TRACKED reader of a superseded node (#3331): the
|
|
616
|
-
* staged truth, unless the reader is a stale (render) reader of another
|
|
617
|
-
* transaction — then the displayed override, as it keeps a foreign
|
|
618
|
-
* transaction's committed value over its staged write. */
|
|
619
|
-
/** A tracked read of an active override: the lane outside-view rule
|
|
620
|
-
* (#3460) and the A18 supersession selection (#3331) — see optimistic.ts. */
|
|
621
|
-
static Pn=null;
|
|
622
|
-
/** A lane pass's publish for a memo (#3479, lanes stage): the speculative
|
|
623
|
-
* result becomes a DERIVED override, `_value` stays committed — see
|
|
624
|
-
* optimistic.ts laneOverride. Set with the engine, which a lane implies. */
|
|
625
|
-
static je=null;
|
|
626
|
-
/** Verdict-layer recompute in progress (companion creation, latest()/
|
|
627
|
-
* isPending() pulls): never born held — see core.ts enterStagedRead. */
|
|
628
|
-
static Tn=false;
|
|
629
|
-
/** setSignal's authoritative (projection-write) landing on an override-
|
|
630
|
-
* covered node (#3331 store twin): stage the truth for its transaction's
|
|
631
|
-
* commit whatever its relation to the committed value — a landing equal to
|
|
632
|
-
* committed still differs from the override — then _supersedeOverride
|
|
633
|
-
* decides. Installed with the optimistic engine; only reachable on a node
|
|
634
|
-
* that has an override — or, from `mapArray` once a lane pass has run over
|
|
635
|
-
* the map, on a per-slot signal (never CONFIG_OPTIMISTIC): the slot arm
|
|
636
|
-
* publishes a lane pass's write as the slot's derived override, lands a
|
|
637
|
-
* plain pass's write over one, and is the plain `setSignal` otherwise (F1,
|
|
638
|
-
* see optimistic.ts landOnOverride). */
|
|
639
|
-
static Vn=null;
|
|
640
|
-
static Jn=null;
|
|
418
|
+
class GlobalQueue {
|
|
419
|
+
En=[ [], [] ];
|
|
420
|
+
X=false;
|
|
641
421
|
flush() {
|
|
642
|
-
if (this
|
|
643
|
-
|
|
644
|
-
//
|
|
645
|
-
//
|
|
646
|
-
//
|
|
647
|
-
// below and the outer drain loop takes the full spine next round.
|
|
648
|
-
if (activeTransition === null && dirtyQueue.EE < dirtyQueue.st && this.ti[0].length === 0 && this.ti[1].length === 0 && this.Xn.length === 0 && !wokenTransitions.length && !batchJoins.length && // a join must drain in its own tick (#3519 review)
|
|
649
|
-
canUseSimpleSyncFlush(this)) {
|
|
650
|
-
this.$t = true;
|
|
651
|
-
try {
|
|
652
|
-
// A28: companions of nodes written since the last flush mirror the
|
|
653
|
-
// flushed world — re-synced inside the running window (the write is
|
|
654
|
-
// "flushed" from here on).
|
|
422
|
+
if (this.X) return;
|
|
423
|
+
this.X = true;
|
|
424
|
+
// A28: companions of nodes written since the last flush mirror the
|
|
425
|
+
// flushed world — re-synced inside the running window (the write is
|
|
426
|
+
// "flushed" from here on).
|
|
655
427
|
resyncUnflushedCompanions();
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
return;
|
|
667
|
-
}
|
|
668
|
-
this.$t = true;
|
|
669
|
-
resyncUnflushedCompanions();
|
|
670
|
-
// A28, see above
|
|
671
|
-
try {
|
|
672
|
-
// The tick proposed against a hold (#3494): adopt its batch into it.
|
|
673
|
-
// Inside the try: the adoption runs user comparators (the no-proposal
|
|
674
|
-
// drop), and a throw there must not leave `_running` set.
|
|
675
|
-
while (batchJoins.length) this.initTransition(batchJoins.pop());
|
|
676
|
-
if (false) ;
|
|
677
|
-
// Before runHeap for the same reason as the fast drain above; late
|
|
678
|
-
// subscribers (an effect reading a swept memo this flush) revive it,
|
|
679
|
-
// which is the pay-for-use contract.
|
|
680
|
-
sweepDormant();
|
|
681
|
-
runHeap(dirtyQueue, GlobalQueue.nt);
|
|
682
|
-
// The action bodies are over: the overrides they leave in force
|
|
683
|
-
// revert at this settle, and the correction is this transaction's
|
|
684
|
-
// held work — re-derived here, under it, ahead of the verdict — not a
|
|
685
|
-
// waterfall after the flights the overrides fed (#3427). After the
|
|
686
|
-
// heap, not before: a synchronous body's own writes (a refresh that
|
|
687
|
-
// puts an override node's source in flight) are judged applied.
|
|
688
|
-
if (activeTransition && GlobalQueue.zn?.(activeTransition)) runHeap(dirtyQueue, GlobalQueue.nt);
|
|
689
|
-
// Re-arm the boundaries whose `on` notified this pass (pendingRearms):
|
|
690
|
-
// after the heap — what the notification put in flight is registered —
|
|
691
|
-
// and before the verdict, under the transaction that carried it. The
|
|
692
|
-
// boundary releases its hold and stages its fallback swap with the
|
|
693
|
-
// frame; the heap re-runs so its output pass is staged too, ahead of
|
|
694
|
-
// the verdict that commits or parks it (#3540).
|
|
695
|
-
if (pendingRearms.size) {
|
|
696
|
-
drainRearms();
|
|
697
|
-
runHeap(dirtyQueue, GlobalQueue.nt);
|
|
698
|
-
}
|
|
699
|
-
if (activeTransition) {
|
|
700
|
-
// A boundary whose fallback read something not ready is judged before
|
|
701
|
-
// the verdict, under the transaction (boundaries.ts `_judgeHeld`,
|
|
702
|
-
// #3540): its output is pending on that read and holds the frame,
|
|
703
|
-
// and only a sweep re-runs it — the commit sweep, after the verdict
|
|
704
|
-
// its own read keeps parking. Ready, it stages `_disabled` false with
|
|
705
|
-
// the frame; the heap re-runs so the output drops the read ahead of
|
|
706
|
-
// the verdict.
|
|
707
|
-
if (this.Xn.length) {
|
|
708
|
-
checkBoundaryChildren(this, true);
|
|
709
|
-
if (dirtyQueue.EE >= dirtyQueue.st) runHeap(dirtyQueue, GlobalQueue.nt);
|
|
710
|
-
}
|
|
711
|
-
const e = transitionComplete(activeTransition);
|
|
712
|
-
if (!e) {
|
|
713
|
-
const e = activeTransition;
|
|
714
|
-
// Parked: the unchanged passes' inputs are held; their tails stay (A30).
|
|
715
|
-
heldTrims.length = 0;
|
|
716
|
-
// When the parking batch IS the transition, all of its writes commit
|
|
717
|
-
// only with it — every zombie recompute they queued would run against
|
|
718
|
-
// a world the zombie never displays (zombies render mainline until
|
|
719
|
-
// commit), so cancel them instead of running them. Only an ambient
|
|
720
|
-
// batch's mainline writes (the #2916 shape below) legitimately reach
|
|
721
|
-
// zombies here. Height-adjust entries still process normally: a
|
|
722
|
-
// dirtied one keeps its height flag and falls through to runHeap's
|
|
723
|
-
// adjustHeight path on the next pass of the bucket.
|
|
724
|
-
runHeap(zombieQueue, this.m === e ? cancelZombieRecompute : GlobalQueue.nt);
|
|
725
|
-
// Detach: the stashed transition keeps its batch; ambient work that
|
|
726
|
-
// follows lands in a fresh one. If the batch is already a separate
|
|
727
|
-
// ambient one — action done() restored activeTransition without
|
|
728
|
-
// adopting the batch, and an ordinary write landed there before
|
|
729
|
-
// the scheduled flush (#2916) — keep it: replacing it would strand
|
|
730
|
-
// its queued pending nodes with held _pendingValues forever.
|
|
731
|
-
if (this.m === e) currentBatch = this.m = createBatch();
|
|
732
|
-
// Run lane effects immediately (before stashing) - lanes with no pending async
|
|
733
|
-
if (activeLanes.size) {
|
|
734
|
-
GlobalQueue.Kn(EFFECT_RENDER);
|
|
735
|
-
GlobalQueue.Kn(EFFECT_USER);
|
|
736
|
-
}
|
|
737
|
-
this.stashQueues(e.ei);
|
|
738
|
-
clock++;
|
|
739
|
-
// A kept ambient batch may hold pending nodes (#2916): stay
|
|
740
|
-
// scheduled so the outer drain loop commits them via the plain
|
|
741
|
-
// flush path instead of leaving them until the next natural flush.
|
|
742
|
-
scheduled = dirtyQueue.EE >= dirtyQueue.st || this.m.In.length > 0;
|
|
743
|
-
reassignPendingTransition(e.In);
|
|
744
|
-
activeTransition = null;
|
|
745
|
-
finalizePureQueue(null, true);
|
|
746
|
-
return;
|
|
747
|
-
}
|
|
748
|
-
const t = activeTransition;
|
|
749
|
-
const n = this.m;
|
|
750
|
-
n !== t && n.In.push(...t.In);
|
|
751
|
-
this.restoreQueues(t.ei);
|
|
752
|
-
transitions.delete(t);
|
|
753
|
-
activeTransition = null;
|
|
754
|
-
reassignPendingTransition(n.In);
|
|
755
|
-
finalizePureQueue(t);
|
|
756
|
-
if (n === t) {
|
|
757
|
-
// Drop the dead Transition wrapper but keep its (drained) containers
|
|
758
|
-
// as the ambient batch — late registrations during finalization live
|
|
759
|
-
// there and must survive to the next flush.
|
|
760
|
-
const e = createBatch();
|
|
761
|
-
e.In = n.In;
|
|
762
|
-
e.bn = n.bn;
|
|
763
|
-
e.A = n.A;
|
|
764
|
-
e.En = n.En;
|
|
765
|
-
currentBatch = this.m = e;
|
|
766
|
-
}
|
|
767
|
-
} else {
|
|
768
|
-
if (canUseSimpleSyncFlush(this)) {
|
|
769
|
-
commitPendingNodes();
|
|
770
|
-
if (dirtyQueue.EE >= dirtyQueue.st) {
|
|
771
|
-
runHeap(dirtyQueue, GlobalQueue.nt);
|
|
772
|
-
commitPendingNodes();
|
|
773
|
-
}
|
|
774
|
-
} else {
|
|
775
|
-
// Parked transactions elsewhere: their owners' zombies render
|
|
776
|
-
// mainline until the commit that disposes them, so a mainline write
|
|
777
|
-
// reaches them here (#2916, #3463). Commit THIS flush's pending nodes
|
|
778
|
-
// first (#3546): a zombie whose owner commits now is disposed by that
|
|
779
|
-
// commit and never reruns — the same fate it has when no transaction
|
|
780
|
-
// is parked, where this queue is not run at all. Run before the
|
|
781
|
-
// commit, it reran and notified its owner through the previous
|
|
782
|
-
// pass's dependency tail (kept by A30 until the commit trims it),
|
|
783
|
-
// and the owner recomputed a second time with identical inputs,
|
|
784
|
-
// creating and disposing one more child per write. Only the zombies
|
|
785
|
-
// of actually parked owners survive the commit and rerun; the
|
|
786
|
-
// finalize's own commit picks up whatever those reruns stage.
|
|
787
|
-
if (transitions.size) {
|
|
788
|
-
commitPendingNodes();
|
|
789
|
-
runHeap(zombieQueue, GlobalQueue.nt);
|
|
790
|
-
}
|
|
791
|
-
finalizePureQueue();
|
|
792
|
-
}
|
|
793
|
-
}
|
|
428
|
+
passLane = null;
|
|
429
|
+
passTx = null;
|
|
430
|
+
try {
|
|
431
|
+
// Sweep before the heap: unobserved() pulls swept nodes out of it, so
|
|
432
|
+
// a dormant memo dirtied in the same tick is reclaimed instead of
|
|
433
|
+
// recomputed; late subscribers (an effect reading a swept memo this
|
|
434
|
+
// flush) revive it, which is the pay-for-use contract.
|
|
435
|
+
sweepDormant();
|
|
436
|
+
runHeap(dirtyQueue, GlobalQueue.et);
|
|
437
|
+
this.settle();
|
|
794
438
|
clock++;
|
|
795
|
-
//
|
|
796
|
-
//
|
|
797
|
-
|
|
798
|
-
// since are its to apply, not this flush's — runEffect leaves them queued
|
|
799
|
-
// and the next pass parks them with it (#3319). Everything computed
|
|
800
|
-
// mainline applies now. A write the finalize staged in the ambient
|
|
801
|
-
// batch with no subscriber to dirty (an optimistic store settle's
|
|
802
|
-
// keyset bump under a reader that never tracks the key set) is work
|
|
803
|
-
// too — the fast drain and the park exit already count it — so the
|
|
804
|
-
// next round commits it and the woken re-entry below does not adopt
|
|
805
|
-
// it into a parked transaction it never belonged to (matrix F6).
|
|
806
|
-
scheduled = dirtyQueue.EE >= dirtyQueue.st || activeTransition !== null || this.m.In.length !== 0;
|
|
807
|
-
// Run lane effects first (for ready lanes), then regular effects
|
|
808
|
-
activeLanes.size && GlobalQueue.Kn(EFFECT_RENDER);
|
|
439
|
+
// A write the commit sweep staged with no subscriber to dirty is work
|
|
440
|
+
// too — the next round commits it.
|
|
441
|
+
scheduled = dirtyQueue.Kt >= dirtyQueue.ut || pendingNodes.length !== 0;
|
|
809
442
|
this.run(EFFECT_RENDER);
|
|
810
|
-
activeLanes.size && GlobalQueue.Kn(EFFECT_USER);
|
|
811
443
|
this.run(EFFECT_USER);
|
|
812
|
-
if (
|
|
813
|
-
|
|
444
|
+
if (true && holding) {
|
|
445
|
+
holding = false;
|
|
446
|
+
attrHooks.holdEnd();
|
|
447
|
+
}
|
|
448
|
+
if (false && !scheduled) ;
|
|
814
449
|
if (false) ;
|
|
815
450
|
} finally {
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
// ambient work would be held behind flights it never read. `scheduled`
|
|
819
|
-
// is that test here — after the park exit as well as the normal one:
|
|
820
|
-
// it was recomputed from the heap and the ambient batch's staged nodes
|
|
821
|
-
// this pass, every write since re-armed it, and optimistic ambient
|
|
822
|
-
// nodes reverted with the finalize — so a wake in a pass with work
|
|
823
|
-
// simply falls to the next. (A staged node with no subscriber — the
|
|
824
|
-
// finalize's keyset bump under a length-only reader — used to be
|
|
825
|
-
// missed here: the wake adopted it, stamped it, and a later ambient
|
|
826
|
-
// write to the same node joined the parked transaction and never
|
|
827
|
-
// reverted; matrix F6.) Entering re-arms
|
|
828
|
-
// it itself; a dead (completed) wake is a bare return in
|
|
829
|
-
// initTransition, and the loop moves on to the next.
|
|
830
|
-
while (!scheduled && !activeTransition && wokenTransitions.length) this.initTransition(wokenTransitions.pop());
|
|
831
|
-
this.$t = false;
|
|
451
|
+
this.X = false;
|
|
452
|
+
passTx = null;
|
|
832
453
|
}
|
|
833
454
|
}
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
455
|
+
/** L2 — the seam: end of the pure phase. Commit this flush's staged nodes,
|
|
456
|
+
* or park them with the transaction the flush joined; then land every
|
|
457
|
+
* transaction no frame is waiting on. The landing flush is an ordinary
|
|
458
|
+
* flush: the settle walk (async.ts) re-ran the parked readers in its pure
|
|
459
|
+
* phase, and here no flight the transaction holds has a frame deriving
|
|
460
|
+
* from it (`blocked`). */ settle() {
|
|
461
|
+
// Lanes: an ended action body judges its guesses now that their passes
|
|
462
|
+
// have run (lanes.ts); a correction's re-derivations run in one more
|
|
463
|
+
// pure round — the parent's flights before the parent is judged below.
|
|
464
|
+
if (GlobalQueue.On?.()) runHeap(dirtyQueue, GlobalQueue.et);
|
|
465
|
+
const e = flushTransaction;
|
|
466
|
+
flushTransaction = null;
|
|
467
|
+
// The frame's verdict is in: the guesses written since the last seam
|
|
468
|
+
// open their lane under the frame's transaction (a lane's own flush
|
|
469
|
+
// parents a nested lane to it), or are dropped — the frame commits, and
|
|
470
|
+
// there is nothing to be optimistic over. Their passes run next round;
|
|
471
|
+
// a parent landing at this very seam ends the lane before they do.
|
|
472
|
+
GlobalQueue.Gn?.(e);
|
|
473
|
+
// A lane's own flush (its flight landing, its reveal) parks nothing: the
|
|
474
|
+
// lane's work reached it per pass; the frame's plain writes commit.
|
|
475
|
+
// (Resolved: a guess over another transaction's guess merged them.)
|
|
476
|
+
const n = e !== null && !e.B ? resolveTx(e) : null;
|
|
477
|
+
if (n !== null) {
|
|
478
|
+
// `t._nodes` grows under the walk (holdFrame pushes); the bound is
|
|
479
|
+
// this flush's list, which does not.
|
|
480
|
+
const e = pendingNodes.length;
|
|
481
|
+
for (let t = 0; t < e; t++) {
|
|
482
|
+
const e = pendingNodes[t];
|
|
483
|
+
// Already held: the pass that re-staged it joined its transaction,
|
|
484
|
+
// which is `t` (or merged into it). Lane work is the lane's (a
|
|
485
|
+
// pending mark propagated onto it queued it here too).
|
|
486
|
+
if (!(e.i & (CONFIG_HELD | CONFIG_OVERRIDE)) && (e.m === null || e.m._t === null || !e.m._t.B)) {
|
|
487
|
+
// A loading source — pending with nothing staged and no committed
|
|
488
|
+
// value (A16, A19 exc. 1: uninitialized is loading, not pending)
|
|
489
|
+
// that is not an observer — is nobody's frame: not held. Its
|
|
490
|
+
// first landing is commit #0, not a resumption of this frame, and
|
|
491
|
+
// a write that reaches it later overlaps nothing (#2937: the
|
|
492
|
+
// stale stamp was the bridge that entangled unrelated updates
|
|
493
|
+
// with a never-resolving flight). Its observers are held and
|
|
494
|
+
// wait on its flight (`blocked`).
|
|
495
|
+
if (e.Oe === NOT_PENDING && e.S & STATUS_UNINITIALIZED && e.u !== EFFECT_RENDER) continue;
|
|
496
|
+
// A34 (2): a tick whose writes net to the committed value made no
|
|
497
|
+
// proposal (`setShow(false); setShow(true)` on a committed true) —
|
|
498
|
+
// not staged, not held, pends nothing; a later write to it is
|
|
499
|
+
// plain mainline. (A node already held stays held when written
|
|
500
|
+
// back: A34 (1).)
|
|
501
|
+
if (e.Oe !== NOT_PENDING && !(e.S & (STATUS_PENDING | STATUS_UNINITIALIZED)) && e.en && e.en(e.Ae, e.Oe) && e.m?.St == null && e.m?.ct == null) {
|
|
502
|
+
e.Oe = NOT_PENDING;
|
|
503
|
+
e.i &= ~CONFIG_STAGED;
|
|
504
|
+
continue;
|
|
863
505
|
}
|
|
506
|
+
holdNode(e, n);
|
|
864
507
|
}
|
|
508
|
+
// A held pass's frame is held with it (ruling A, O2): the children it
|
|
509
|
+
// created are the transaction's — they compute, and a pass over them
|
|
510
|
+
// or a write that reaches them joins; they effect at the landing.
|
|
511
|
+
if (e.i & CONFIG_STAGED) holdFrame(e, n);
|
|
512
|
+
}
|
|
513
|
+
pendingNodes.length = 0;
|
|
514
|
+
GlobalQueue.An?.(n);
|
|
515
|
+
// (This flush's runs are stashed with `t` below — after the landings,
|
|
516
|
+
// so a `t` that lands at this very seam runs them first, ahead of
|
|
517
|
+
// the runs it held from earlier flushes.)
|
|
518
|
+
// The batch joins: the writes that dirtied these are held with it — a
|
|
519
|
+
// zombie never displays that world. A verdict reader among them does
|
|
520
|
+
// (#3444: `latest()` inside a branch a held Show is removing peers
|
|
521
|
+
// through like the same read outside): it re-runs now, as the
|
|
522
|
+
// holder's verdict lane's work — before the lane seams, which reveal
|
|
523
|
+
// it (nothing commits this seam that could dispose it).
|
|
524
|
+
const t = deferredZombies.splice(0).sort((e, n) => e.it - n.it);
|
|
525
|
+
for (let e = 0; e < t.length; e++) if ((t[e].R & (REACTIVE_DISPOSED | REACTIVE_ZOMBIE)) === REACTIVE_ZOMBIE && t[e].i & CONFIG_VERDICT) recompute(t[e]);
|
|
526
|
+
// The unchanged passes' tails stay linked (A30, #3469): their inputs
|
|
527
|
+
// are held, and the committed frame still derives from them — no trim.
|
|
528
|
+
} else {
|
|
529
|
+
commitPendingNodes();
|
|
530
|
+
// The flush committed: an unchanged pass's stale tail goes now. Not
|
|
531
|
+
// after a later pass this flush that threw (NotReady included — it
|
|
532
|
+
// keeps its full list, `_depsTail` marking where it stopped) or that
|
|
533
|
+
// left an effect owing a run (`runEffect` trims when the run applies):
|
|
534
|
+
// `recompute`'s own gates.
|
|
535
|
+
for (let e = 0; e < heldTrims.length; e++) {
|
|
536
|
+
const n = heldTrims[e];
|
|
537
|
+
if (n.m?.F == null && !n.lt) trimStaleDeps(n);
|
|
865
538
|
}
|
|
866
|
-
return true;
|
|
867
539
|
}
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
540
|
+
// The list is the flush's either way. Reset only when there is something
|
|
541
|
+
// to reset (`stagedReaders` below likewise): `length = 0` is a runtime
|
|
542
|
+
// call even on an empty array, and the plain flush — no unchanged pass,
|
|
543
|
+
// no lane work — has nothing in either.
|
|
544
|
+
if (heldTrims.length) heldTrims.length = 0;
|
|
545
|
+
// Lanes: a blocked one parks its frame (its own flight is up); an
|
|
546
|
+
// unblocked one reveals this round's work. Then land every transaction
|
|
547
|
+
// no frame is waiting on — its lanes end with it. Backwards: a landing
|
|
548
|
+
// removes its entry.
|
|
549
|
+
// This flush's own runs, the lanes' reveals, and the landings' held runs
|
|
550
|
+
// are collected apart and ordered below.
|
|
551
|
+
const t = this.En;
|
|
552
|
+
const l = this.En = [ [], [] ];
|
|
553
|
+
// The passes that read this frame's stagings as the screen read held
|
|
554
|
+
// writes if it parked: the lane seam re-derives them on the committed
|
|
555
|
+
// world (a lane's runs wait this round).
|
|
556
|
+
GlobalQueue.cn?.(n !== null ? stagedReaders : null);
|
|
557
|
+
if (stagedReaders.length) stagedReaders.length = 0;
|
|
558
|
+
this.En = [ [], [] ];
|
|
559
|
+
for (let e = transactions.length - 1; e >= 0; e--) {
|
|
560
|
+
const n = transactions[e];
|
|
561
|
+
if (blocked(n)) continue;
|
|
562
|
+
transactions.splice(e, 1);
|
|
563
|
+
GlobalQueue.Tn?.(n);
|
|
564
|
+
land(n);
|
|
565
|
+
// A landing's commits can dispose a zombie a transaction judged above
|
|
566
|
+
// was blocked on (#3463: live "until the commit that disposes it") —
|
|
567
|
+
// judge them again.
|
|
568
|
+
e = transactions.length;
|
|
881
569
|
}
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
570
|
+
// The store folds the pending backings whose container nodes committed
|
|
571
|
+
// — this flush's, or a landing's (store/store.ts installs it).
|
|
572
|
+
GlobalQueue.Rn?.();
|
|
573
|
+
// Marks declared outside a transaction: the flush that carried them
|
|
574
|
+
// parked (something async below) — the window is the frame's, the marks
|
|
575
|
+
// are its transaction's; else they release at the seam, verdict-only,
|
|
576
|
+
// nothing to show (affects.ts).
|
|
577
|
+
const o = n && liveTx(n);
|
|
578
|
+
GlobalQueue.A?.(o);
|
|
579
|
+
// The effect phase: lane work first (displayed ahead of the frame), then
|
|
580
|
+
// this flush's runs, then the runs the landings held from earlier
|
|
581
|
+
// flushes — as one pass would have queued them (#3540: a shell
|
|
582
|
+
// re-derived at the landing before a boundary swap held since the
|
|
583
|
+
// write; #3528: a display-ahead swap before the count it is shown
|
|
584
|
+
// beside). A parked frame's runs wait with its transaction instead: the
|
|
585
|
+
// effect phase applies a committed frame.
|
|
586
|
+
const u = o !== null;
|
|
587
|
+
// Attribution hook: this flush found its transaction incomplete — its
|
|
588
|
+
// writes stay staged, its runs are stashed below. Before the effect
|
|
589
|
+
// phase (the lanes' display-ahead runs are the visible acknowledgers);
|
|
590
|
+
// `holdEnd` fires after it (`flush`).
|
|
591
|
+
if (u && attrHooks !== null) {
|
|
592
|
+
attrHooks.holdStart(o);
|
|
593
|
+
holding = true;
|
|
895
594
|
}
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
//
|
|
901
|
-
//
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
//
|
|
906
|
-
//
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
const e = this.$t ? 0 : CONFIG_ADOPTED_UNFLUSHED;
|
|
911
|
-
for (let n = 0; n < t.In.length; n++) {
|
|
912
|
-
const i = t.In[n];
|
|
913
|
-
// A tick that nets to the committed value proposed nothing (A34, #3494):
|
|
914
|
-
// `setShow(false); setShow(true)` beside a write that opens a hold
|
|
915
|
-
// left `show` staged at its own value, stamped, pending to the
|
|
916
|
-
// verdict, and its next mainline write held by a flight it never
|
|
917
|
-
// derived from. Unstage it here — its subscribers were walked at the
|
|
918
|
-
// write and re-derive the same value. Writes only: a signal's staging
|
|
919
|
-
// is always one, a computed's only under REACTIVE_MANUAL_WRITE
|
|
920
|
-
// (`createSignal(fn)`'s setter, #3519 review) — otherwise it is its
|
|
921
|
-
// pass's result, which may equal an uninitialized `undefined` (a
|
|
922
|
-
// born-held first pass). `_equals: false` opts out. The unstaging is
|
|
923
|
-
// the commit's own path (commitPendingNode with nothing staged): the
|
|
924
|
-
// manual-write flag, companions and the rest are cleaned up as a
|
|
925
|
-
// commit would, and the node is stamped nowhere. Unstamped
|
|
926
|
-
// only: a node already a transaction's — arriving here as a parked
|
|
927
|
-
// batch folds into a merge — carries a FLUSHED proposal a later
|
|
928
|
-
// rewrite brought back to the committed value; it is held, not
|
|
929
|
-
// proposal-free. Dropped, it kept the dead stamp, and the next write
|
|
930
|
-
// to it queued under the merged transaction a value the commit then
|
|
931
|
-
// skipped as another's (fuzzer latest-2 #1470, S3).
|
|
932
|
-
if (i.me === null && i._e !== NOT_PENDING && (!i.Se || i.oe & REACTIVE_MANUAL_WRITE && !(i.S & STATUS_UNINITIALIZED)) && i.Fe && i.Fe(i.ce, i._e)) {
|
|
933
|
-
i._e = NOT_PENDING;
|
|
934
|
-
commitPendingNode(i);
|
|
935
|
-
continue;
|
|
936
|
-
}
|
|
937
|
-
i.me = activeTransition;
|
|
938
|
-
i.C |= e;
|
|
939
|
-
activeTransition.In.push(i);
|
|
940
|
-
}
|
|
941
|
-
for (let e = 0; e < t.bn.length; e++) {
|
|
942
|
-
const n = t.bn[e];
|
|
943
|
-
n.me = activeTransition;
|
|
944
|
-
activeTransition.bn.push(n);
|
|
595
|
+
for (let e = 0; e < 2; e++) {
|
|
596
|
+
if (u) append(o.En[e], t[e]);
|
|
597
|
+
// The plain flush — no reveal, no landing, not parked — has nothing to
|
|
598
|
+
// order around its own runs: they are the queue as they stand (the
|
|
599
|
+
// same array, not a copy; `run` takes it whole). Every other seam
|
|
600
|
+
// builds the ordered queue above.
|
|
601
|
+
this.En[e] = u || l[e].length || this.En[e].length ? l[e].concat(u ? [] : t[e], this.En[e]) : t[e];
|
|
602
|
+
}
|
|
603
|
+
if (deferredZombies.length !== 0) {
|
|
604
|
+
// Survivors are the displayed frame of a held pass: they rerun for the
|
|
605
|
+
// live write, parents before children.
|
|
606
|
+
const e = deferredZombies.splice(0).sort((e, n) => e.it - n.it);
|
|
607
|
+
for (let n = 0; n < e.length; n++) {
|
|
608
|
+
if (!(e[n].R & REACTIVE_DISPOSED)) recompute(e[n]);
|
|
945
609
|
}
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
610
|
+
}
|
|
611
|
+
// Boundaries: readers that settled, landed or died this flush are
|
|
612
|
+
// dropped; a fallback with none left reveals next round.
|
|
613
|
+
GlobalQueue.ce?.();
|
|
614
|
+
}
|
|
615
|
+
run(e) {
|
|
616
|
+
const n = this.En[e - 1];
|
|
617
|
+
if (n.length) {
|
|
618
|
+
this.En[e - 1] = [];
|
|
619
|
+
runQueue(n, e);
|
|
620
|
+
}
|
|
621
|
+
}
|
|
622
|
+
enqueue(e, n, t = passLane) {
|
|
623
|
+
// Lane work's runs are the lane's: they run at its reveal, not with
|
|
624
|
+
// the frame (which may park). A lane pass that read the frame's own
|
|
625
|
+
// staging is the frame's (`recompute` passes null): its run waits with
|
|
626
|
+
// the frame.
|
|
627
|
+
if (e) (t !== null ? t : this).En[e - 1].push(n);
|
|
628
|
+
schedule();
|
|
629
|
+
}
|
|
630
|
+
/** Status reaching the root: pending is absorbed (the root mount defers —
|
|
631
|
+
* dev records it for the ASYNC_OUTSIDE_LOADING_BOUNDARY FYI); an error is
|
|
632
|
+
* unhandled (`false` → the caller halts).
|
|
633
|
+
*
|
|
634
|
+
* L2: this is where a transaction is registered. Pending that reaches an
|
|
635
|
+
* observer (a render effect: the frame) is a frame that cannot show — the
|
|
636
|
+
* flush parks, and the transaction waits on the flight while a frame
|
|
637
|
+
* derives from it (`blocked`). Pending nobody observes holds nothing: a
|
|
638
|
+
* memo in flight that no frame reads is not a frame waiting. (Base: the
|
|
639
|
+
* queue's `_asyncReporters`, INV-3's one registration site.)
|
|
640
|
+
*
|
|
641
|
+
* A boundary between the observer and the root (boundaries.ts) is asked
|
|
642
|
+
* first: a fallback catches what is not ready under it, and the root
|
|
643
|
+
* never hears of it. */ notify(e, n, t, l) {
|
|
644
|
+
if (GlobalQueue.ie && GlobalQueue.ie(e, t & n, l)) return true;
|
|
645
|
+
if (n & STATUS_PENDING) {
|
|
646
|
+
if (t & STATUS_PENDING) {
|
|
647
|
+
// A pass that went pending holds its frame: the async's own
|
|
648
|
+
// synchronous frame is not ready (A15 reveal corollary: a reveal
|
|
649
|
+
// that discovers a flight whose inputs are visible holds and joins
|
|
650
|
+
// it; one whose inputs are held is a stale reader and never gets
|
|
651
|
+
// here — `read` serves it the committed value). The frame's, not the
|
|
652
|
+
// observer's: a render effect belongs to whoever dirtied its pass
|
|
653
|
+
// (A15 shared-hole, #3407) — one held by T1 and re-run by T2's write
|
|
654
|
+
// pends for T2's flight, and the two stay parallel; a frame that is
|
|
655
|
+
// T1's already joined T1 through the write or the memo that made it
|
|
656
|
+
// so. Outside a flush — root setup, a mount — the frame is the
|
|
657
|
+
// mount's: its pending observers are one transaction, and a flight
|
|
658
|
+
// started in it resumes it when it lands (maintainer, #3461: "part
|
|
659
|
+
// of the same one"); what the mount published synchronously stays
|
|
660
|
+
// published. Lane work going pending holds the lane, not the frame:
|
|
661
|
+
// `blocked(lane)` finds it in the lane's staging ("if further async
|
|
662
|
+
// downstream is hit it holds like its own transition").
|
|
663
|
+
if (passLane === null) joinFuture(null);
|
|
953
664
|
}
|
|
954
|
-
|
|
665
|
+
return true;
|
|
955
666
|
}
|
|
956
|
-
|
|
957
|
-
|
|
667
|
+
return false;
|
|
668
|
+
}
|
|
669
|
+
}
|
|
670
|
+
|
|
671
|
+
/** A15's stale readers: a frame whose LAST pass read a held node as
|
|
672
|
+
* committed re-derives on the landed world — next round, after the commits.
|
|
673
|
+
* One re-derived since (any later pass cleared the bit: in the transaction,
|
|
674
|
+
* or without the held read) owes nothing. A run this transaction stashed
|
|
675
|
+
* for it is void (#3322: an effect has one value slot, and the stale pass
|
|
676
|
+
* overwrote the value that run was for) — the re-derivation's run replaces
|
|
677
|
+
* it. */ function reruns(e) {
|
|
678
|
+
for (let n = 0; n < e.an.length; n++) {
|
|
679
|
+
const t = e.an[n];
|
|
680
|
+
// An errored reader's display is its error (D-39): the landing gives it
|
|
681
|
+
// nothing to re-derive, and a re-run would be a read that retries the
|
|
682
|
+
// errored source (#3945).
|
|
683
|
+
if (t.R & REACTIVE_DISPOSED || t.S & STATUS_ERROR) continue;
|
|
684
|
+
if (t.R & REACTIVE_FRAME_READ || t.i & CONFIG_VERDICT) {
|
|
685
|
+
t.R &= ~REACTIVE_FRAME_READ;
|
|
686
|
+
t.lt = false;
|
|
687
|
+
// Its frame is replaced by the re-derivation: a run one of its
|
|
688
|
+
// children queued this flush (re-run by the same landing) is for the
|
|
689
|
+
// frame being replaced — void too (#3404).
|
|
690
|
+
for (let e = t.nn; e !== null; e = e.tn) if (e.u) e.lt = false;
|
|
691
|
+
enqueueSub(t);
|
|
958
692
|
}
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
693
|
+
}
|
|
694
|
+
e.an.length = 0;
|
|
695
|
+
}
|
|
696
|
+
|
|
697
|
+
/** The landing: the held frame becomes the frame. */ function land(e) {
|
|
698
|
+
// Attribution hook: judged complete — its held writes commit next
|
|
699
|
+
// (`_nodes` still lists them).
|
|
700
|
+
if (attrHooks !== null) attrHooks.transitionSettled(e);
|
|
701
|
+
reruns(e);
|
|
702
|
+
if (e.T !== null) GlobalQueue.k(e.T);
|
|
703
|
+
// Old children die in the commits (cleanups first), then the stashed
|
|
704
|
+
// effects run ahead of this flush's own. A node a lane took over since
|
|
705
|
+
// (a guess written over the staged truth) is the lane's to land.
|
|
706
|
+
for (let n = 0; n < e.Je.length; n++) {
|
|
707
|
+
const t = e.Je[n];
|
|
708
|
+
if (t.m._t === null || txOf(t) !== e) continue;
|
|
709
|
+
t.m._t = null;
|
|
710
|
+
commitPendingNode(t);
|
|
711
|
+
}
|
|
712
|
+
releaseQueues(e);
|
|
713
|
+
}
|
|
714
|
+
|
|
715
|
+
/** The held runs join this flush's effect phase after its own runs (the
|
|
716
|
+
* landing's re-passes, in height order): the frame's effects run as one
|
|
717
|
+
* pass would have queued them — a shell re-derived at the landing before a
|
|
718
|
+
* boundary swap held since the write (#3540's log order). */ function releaseQueues(e) {
|
|
719
|
+
for (let n = 0; n < 2; n++) {
|
|
720
|
+
append(globalQueue.En[n], e.En[n]);
|
|
721
|
+
e.En[n] = [];
|
|
722
|
+
}
|
|
723
|
+
}
|
|
724
|
+
|
|
725
|
+
/** `a.push(...b)` without the argument-count limit. */ function append(e, n) {
|
|
726
|
+
for (let t = 0; t < n.length; t++) e.push(n[t]);
|
|
727
|
+
}
|
|
728
|
+
|
|
729
|
+
/** Nodes staged this flush (`_pendingValue` set), committed at its end. */ const pendingNodes = [];
|
|
730
|
+
|
|
731
|
+
/** Passes that read a staging of this flush as the screen — lane work
|
|
732
|
+
* (core.ts `stagedRead`), a verdict reader before the frame's verdict
|
|
733
|
+
* (verdict.ts): if the frame parks they read a held write — re-derived next
|
|
734
|
+
* round (lanes.ts; a lane's runs wait that round). */ const stagedReaders = [];
|
|
735
|
+
|
|
736
|
+
/** A15's stale reader: `c` was served the committed value of a node `t`
|
|
737
|
+
* holds (a render effect, a verdict reader, lane work) and is re-derived
|
|
738
|
+
* after `t`'s landing (`_reruns`). Once per pass. */ function staleReader(e, n) {
|
|
739
|
+
if (!(e.R & REACTIVE_FRAME_READ)) {
|
|
740
|
+
e.R |= REACTIVE_FRAME_READ;
|
|
741
|
+
n.an.push(e);
|
|
968
742
|
}
|
|
969
743
|
}
|
|
970
744
|
|
|
971
745
|
function queuePendingNode(e) {
|
|
972
|
-
|
|
973
|
-
if (!globalQueue
|
|
746
|
+
pendingNodes.push(e);
|
|
747
|
+
if (!globalQueue.X) markUnflushedStaged();
|
|
974
748
|
// A28
|
|
975
749
|
}
|
|
976
750
|
|
|
977
|
-
// Sticky: flips true on the first refresh() ever (the only setter of
|
|
978
|
-
// REACTIVE_REASK) so the hot notification loop skips the per-subscriber flag
|
|
979
|
-
// clear entirely in apps that never refresh.
|
|
980
|
-
let reaskArmed = false;
|
|
981
|
-
|
|
982
751
|
/** §12d: bumped by every recompute and every new subscriber edge. A node's
|
|
983
752
|
* staged-rewrite skip is sound only while NOTHING recomputed or linked since
|
|
984
753
|
* its last notify — a mid-batch pull can clean a marked subscriber, and a
|
|
@@ -988,53 +757,21 @@ function bumpNotifyEpoch() {
|
|
|
988
757
|
notifyEpoch++;
|
|
989
758
|
}
|
|
990
759
|
|
|
991
|
-
function
|
|
992
|
-
reaskArmed = true;
|
|
993
|
-
}
|
|
994
|
-
|
|
995
|
-
/** Provenance of the work currently running (A18 supersession, #3331): the
|
|
996
|
-
* invocation sequence of the action whose ambient window this is — set by
|
|
997
|
-
* action() for each slice; the flush that ends the window clears it — or,
|
|
998
|
-
* inside an async landing, the sequence captured when that flight was
|
|
999
|
-
* registered (asyncWrite sets it for the landing's synchronous propagation,
|
|
1000
|
-
* so a sync recompute downstream of the landing — an optimistic wrapper over
|
|
1001
|
-
* the async source — derives under the flight's provenance, and flights it
|
|
1002
|
-
* registers inherit it). 0 is mainline: no action, always the current
|
|
1003
|
-
* question. An override stamps this at its write (`_overrideStamp`); an
|
|
1004
|
-
* answer whose flight an OLDER action issued is a stale question the user
|
|
1005
|
-
* has since changed — it holds silently to commit instead of superseding. A
|
|
1006
|
-
* slow source must not leak back in over a newer intent. Transactions merge,
|
|
1007
|
-
* so the transition object cannot say WHICH action asked; this can. */ let origin = 0;
|
|
1008
|
-
|
|
1009
|
-
function setOrigin(e) {
|
|
1010
|
-
const t = origin;
|
|
1011
|
-
origin = e;
|
|
1012
|
-
return t;
|
|
1013
|
-
}
|
|
1014
|
-
|
|
1015
|
-
function insertSubs(e, t = false) {
|
|
760
|
+
function insertSubs(e) {
|
|
1016
761
|
// §12d: stamp before walking — setSignal's staged-rewrite fast path skips
|
|
1017
762
|
// the next walk for this node while the epoch holds (marking is idempotent).
|
|
1018
|
-
e.
|
|
1019
|
-
// Get source lane: prefer node's own lane over current context
|
|
1020
|
-
// This is important for isPending signals which need their own lane to flush immediately
|
|
763
|
+
e.Pt = notifyEpoch;
|
|
1021
764
|
// Presence bits gate the optional-slot probes (see constants.ts): one
|
|
1022
765
|
// masked read of the always-present _config instead of missing-property
|
|
1023
|
-
// lookups in the hottest notify loop.
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
const i = (n & CONFIG_HAS_LANE ? e.o?.Ce : undefined) || currentOptimisticLane;
|
|
1027
|
-
const r = (n & CONFIG_HAS_SNAPSHOT) !== 0 && e.o?.ct !== undefined;
|
|
1028
|
-
const s = reaskArmed;
|
|
766
|
+
// lookups in the hottest notify loop.
|
|
767
|
+
const n = e.i;
|
|
768
|
+
const t = (n & CONFIG_HAS_SNAPSHOT) !== 0 && e.m?.ot !== undefined;
|
|
1029
769
|
// Observe-tier fan-out: this walk visits every subscriber edge anyway, so
|
|
1030
770
|
// the graph-size count is one local increment here and no field anywhere.
|
|
1031
|
-
let
|
|
1032
|
-
for (let n = e.
|
|
1033
|
-
const e = n.
|
|
1034
|
-
|
|
1035
|
-
// A value-change notification is a new question for the subscriber: any
|
|
1036
|
-
// pending re-ask mark (refresh) it carried is superseded.
|
|
1037
|
-
if (s) e.oe &= ~REACTIVE_REASK;
|
|
771
|
+
let l = 0;
|
|
772
|
+
for (let n = e.t; n !== null; n = n.o) {
|
|
773
|
+
const e = n.l;
|
|
774
|
+
l++;
|
|
1038
775
|
// Missed-wake latch (#3037): this write is landing while the subscriber
|
|
1039
776
|
// is mid-recompute (a nested pull committing beneath its reads), and the
|
|
1040
777
|
// heap refuses RECOMPUTING nodes. A gen-current link means the pass
|
|
@@ -1043,275 +780,104 @@ function insertSubs(e, t = false) {
|
|
|
1043
780
|
// pass either re-reads them fresh or trims them), and neither does the
|
|
1044
781
|
// tail link: it is the read IN FLIGHT — read() links before it pulls, so
|
|
1045
782
|
// this very commit is what that read returns.
|
|
1046
|
-
if (e.
|
|
1047
|
-
if (
|
|
1048
|
-
e.
|
|
783
|
+
if (e.R & REACTIVE_RECOMPUTING_DEPS && n.Pe === e.pe && n !== e.p) e.R |= REACTIVE_MISSED_WAKE;
|
|
784
|
+
if (t && e.i & CONFIG_IN_SNAPSHOT_SCOPE) {
|
|
785
|
+
e.R |= REACTIVE_SNAPSHOT_STALE;
|
|
1049
786
|
continue;
|
|
1050
787
|
}
|
|
1051
|
-
if (t && i) {
|
|
1052
|
-
e.oe |= REACTIVE_OPTIMISTIC_DIRTY;
|
|
1053
|
-
assignOrMergeLane(e, i);
|
|
1054
|
-
} else if (t) {
|
|
1055
|
-
e.oe |= REACTIVE_OPTIMISTIC_DIRTY;
|
|
1056
|
-
// No source lane means reversion - clear subscriber's lane so effects go to regular queue
|
|
1057
|
-
if (e.o) e.o.Ce = undefined;
|
|
1058
|
-
}
|
|
1059
788
|
enqueueSub(e);
|
|
1060
789
|
}
|
|
1061
|
-
if (
|
|
790
|
+
if (l >= GRAPH_SIZE_WARN_AT) noteFanOut(e, l);
|
|
1062
791
|
}
|
|
1063
792
|
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
e.ce = e._e;
|
|
1076
|
-
e._e = NOT_PENDING;
|
|
1077
|
-
// A node born held (recompute) initializes at this commit.
|
|
1078
|
-
t.S &= ~STATUS_UNINITIALIZED;
|
|
1079
|
-
// Set _modified for effects, but not for tracked effects (they handle their own scheduling)
|
|
1080
|
-
if (e.He && e.He !== EFFECT_TRACKED) e.it = true;
|
|
1081
|
-
// A quiet re-ask classification preserved through a held landing dies
|
|
1082
|
-
// with the value commit — the commit IS the reveal (#3178). Gated on the
|
|
1083
|
-
// staged value: status propagation queues pending nodes whose windows
|
|
1084
|
-
// are still OPEN (no staged value), and their live classification must
|
|
1085
|
-
// survive this sweep.
|
|
1086
|
-
if (e.o) e.o.ve = false;
|
|
793
|
+
/** Lane work re-staged (lanes.ts; `recompute`): the lane's members among
|
|
794
|
+
* its subscribers re-derive as the lane's work (REACTIVE_LANE_DIRTY), their
|
|
795
|
+
* runs held with it — not as stale readers republishing the committed view
|
|
796
|
+
* (#3460). After `insertSubs`. A render effect on no lane is marked too:
|
|
797
|
+
* inert for its pass (`recompute` seats a lane only from its own `_x`), it
|
|
798
|
+
* is a live write if the effect becomes a zombie later this flush — an
|
|
799
|
+
* on-screen reader whose removal an adopted write stages follows the lane
|
|
800
|
+
* (the lane-membership ruling, 2026-10-05; `_update`). */ function laneDirty(e, n) {
|
|
801
|
+
for (let t = e.t; t !== null; t = t.o) {
|
|
802
|
+
const e = t.l.m?._t;
|
|
803
|
+
if (e?.B ? sameLane(e, n) : t.l.u) t.l.R |= REACTIVE_LANE_DIRTY;
|
|
1087
804
|
}
|
|
1088
|
-
// The committed hold is the first observable answer for a loading-window
|
|
1089
|
-
// node — the window closes here, not at compute time (#2990). Unconditional
|
|
1090
|
-
// store to an always-present computed slot.
|
|
1091
|
-
t.ye = false;
|
|
1092
|
-
t.oe &= ~REACTIVE_MANUAL_WRITE;
|
|
1093
|
-
// The children this commit publishes are the frame's now (#3404) — and so
|
|
1094
|
-
// are the dependencies of the pass that produced the value: the previous
|
|
1095
|
-
// frame's tail goes (A30, #3410; `recompute` left it for a staged pass). Only
|
|
1096
|
-
// after a clean pass: `_error` is cleared by a clean pass or by the node's
|
|
1097
|
-
// own landing (whose pass was clean), so a set `_error` means the last pass
|
|
1098
|
-
// threw, kept its full list, and `_depsTail` marks where it stopped.
|
|
1099
|
-
if (t.o?._ == null) trimStaleDeps(t);
|
|
1100
|
-
// A LANE frame still parked here (#3662) rode a hold that stashed the run
|
|
1101
|
-
// that would have retired it: this commit applies that run, so it goes too.
|
|
1102
|
-
t.C &= -68157441;
|
|
1103
|
-
if (!(t.S & STATUS_PENDING)) t.S &= ~STATUS_UNINITIALIZED;
|
|
1104
|
-
// A flight this commit leaves in the air (unobserved, or observed only by
|
|
1105
|
-
// a boundary) now has PUBLISHED inputs: its committed value is stale
|
|
1106
|
-
// against the frame. read()'s reveal carve-out keys on the mark (#3305).
|
|
1107
|
-
else e.C |= CONFIG_INPUTS_PUBLISHED;
|
|
1108
|
-
if (t.o != null && (t.o.dt !== null || t.o.Et !== null)) GlobalQueue.ut(t, false, true);
|
|
1109
|
-
if (e.C & CONFIG_HAS_COMPANIONS) GlobalQueue.Qn(e);
|
|
1110
805
|
}
|
|
1111
806
|
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
// _clearOptimisticStores). Folds committed store-node values into their
|
|
1115
|
-
// backing objects at the same moment pending values commit — the single
|
|
1116
|
-
// mutation point of the owned-raw model.
|
|
1117
|
-
let storeCommitHook = null;
|
|
1118
|
-
|
|
1119
|
-
function setStoreCommitHook(e) {
|
|
1120
|
-
storeCommitHook = e;
|
|
807
|
+
/** One lane, or two of one link group (#3335): one reveal unit. */ function sameLane(e, n) {
|
|
808
|
+
return e === n || e.un !== null && e.un === n.un;
|
|
1121
809
|
}
|
|
1122
810
|
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
* had from `a` — with `a` trimmed, the mainline `a = 2` never reached it).
|
|
1136
|
-
* Trimmed when the flush commits; dropped with a park, the tail stays linked
|
|
1137
|
-
* until a committing pass trims it (one spurious recompute at most). */ const heldTrims = [];
|
|
1138
|
-
|
|
1139
|
-
function commitPendingNodes() {
|
|
1140
|
-
while (heldTrims.length) trimStaleDeps(heldTrims.pop());
|
|
1141
|
-
const e = currentBatch.In;
|
|
1142
|
-
for (let t = 0; t < e.length; t++) {
|
|
1143
|
-
const n = e[t];
|
|
1144
|
-
commitPendingNode(n);
|
|
1145
|
-
// The stamp dies with the commit (#3143) — symmetric with
|
|
1146
|
-
// resolveOptimisticNodes clearing optimistic stamps. A stamp outliving
|
|
1147
|
-
// its transaction let any later write (even a value-equal no-op, which
|
|
1148
|
-
// re-opens before the equality bail) resurrect the finished transaction;
|
|
1149
|
-
// a boundary flag rewritten every finalize pass then spun the drain loop
|
|
1150
|
-
// forever (#3140). The held-truth mark dies the same death — the commit
|
|
1151
|
-
// IS the reveal — but its wake defers to the post-revert pass: ordinary
|
|
1152
|
-
// subscribers were masked to committed all hold (some re-derived against
|
|
1153
|
-
// that old view and cached it), and commits are otherwise silent
|
|
1154
|
-
// (staging already notified), so without a wake they'd hold the old
|
|
1155
|
-
// world forever.
|
|
1156
|
-
n.me = null;
|
|
1157
|
-
if (n.C & CONFIG_HELD_TRUTH) {
|
|
1158
|
-
n.C &= ~CONFIG_HELD_TRUTH;
|
|
1159
|
-
heldRevealed.push(n);
|
|
811
|
+
function commitPendingNode(e) {
|
|
812
|
+
const n = e;
|
|
813
|
+
// L2: the commit is where a pass, held or not, stops being uncommitted —
|
|
814
|
+
// unless it has no answer yet. A pass still in flight (pending, nothing
|
|
815
|
+
// staged) stays uncommitted: a re-pass disposes its children on the spot,
|
|
816
|
+
// and the frame it parked stays the frame until a pass of this node
|
|
817
|
+
// commits a value.
|
|
818
|
+
if (!n.ee) {
|
|
819
|
+
e.i &= -385;
|
|
820
|
+
if (e.Oe !== NOT_PENDING) {
|
|
821
|
+
e.Ae = e.Oe;
|
|
822
|
+
e.Oe = NOT_PENDING;
|
|
1160
823
|
}
|
|
824
|
+
return;
|
|
1161
825
|
}
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
// instead (below, with the gated replay): between commitPendingNodes and
|
|
1183
|
-
// _resolveOptimistic the truth is committed but the overrides still
|
|
1184
|
-
// display, and a re-derive here would compose the two — the #3164 tear,
|
|
1185
|
-
// one window later. The slot meanwhile holds the frame that is on screen.
|
|
1186
|
-
const r = e?.ht;
|
|
1187
|
-
const s = i && (e ?? n).bn.length !== 0;
|
|
1188
|
-
if (r && !s) for (const e of r) if (!(e.oe & REACTIVE_DISPOSED)) enqueueSub(e);
|
|
1189
|
-
const o = dirtyQueue.EE >= dirtyQueue.st;
|
|
1190
|
-
if (o) runHeap(dirtyQueue, GlobalQueue.nt);
|
|
1191
|
-
if (i) {
|
|
1192
|
-
// Boundary checks, commit hooks and recomputes can enter a transaction,
|
|
1193
|
-
// which adopts the batch this finalize was settling: nothing batch-derived
|
|
1194
|
-
// may be committed or reverted here — the entered transaction owns it now
|
|
1195
|
-
// (#3319). A completing transaction's OWN containers are a different
|
|
1196
|
-
// matter: when the ambient batch was separate from it (the #2916 shape),
|
|
1197
|
-
// adoption never touched them and it must still settle them; when the
|
|
1198
|
-
// batch WAS the completing transaction, adoption re-stamped its contents
|
|
1199
|
-
// into the entered one and there is nothing left to settle.
|
|
1200
|
-
if (currentBatch !== n) {
|
|
1201
|
-
if (e === null || e === n) return;
|
|
1202
|
-
} else if (o) commitPendingNodes();
|
|
1203
|
-
// The settling batch: the completing transaction's, or the ambient one.
|
|
1204
|
-
const t = e ?? n;
|
|
1205
|
-
// Optimistic reversion: a non-empty batch means _optimisticWrite ran,
|
|
1206
|
-
// which installed the engine's hooks.
|
|
1207
|
-
if (t.bn.length) GlobalQueue.Bn(t.bn);
|
|
1208
|
-
if (r && s) {
|
|
1209
|
-
for (const e of r) if (!(e.oe & REACTIVE_DISPOSED)) enqueueSub(e);
|
|
1210
|
-
schedule();
|
|
826
|
+
// Computeds only from here (`_statusFlags` is not a signal field). A
|
|
827
|
+
// commit beneath a flight publishes its inputs (A15 reveal corollary,
|
|
828
|
+
// #3305): a reveal of it must observe, not show the pre-flight value.
|
|
829
|
+
const t = e.Oe === NOT_PENDING && (n.S & STATUS_PENDING) !== 0;
|
|
830
|
+
e.i = t ? e.i & -1048833 | CONFIG_INPUTS_PUBLISHED : e.i & -1049985;
|
|
831
|
+
if (e.Oe !== NOT_PENDING) {
|
|
832
|
+
e.Ae = e.Oe;
|
|
833
|
+
e.Oe = NOT_PENDING;
|
|
834
|
+
// A node born staged (recompute) initializes at this commit.
|
|
835
|
+
n.S &= ~STATUS_UNINITIALIZED;
|
|
836
|
+
// A quiet re-ask's classification survives its landing and dies with
|
|
837
|
+
// the landed value's commit (A19 exc. 2, #3178): verdict-quiet through
|
|
838
|
+
// the reveal. (The sweep of a node still in flight is not a landing.)
|
|
839
|
+
// The A28 stash goes with it.
|
|
840
|
+
if (n.m != null) n.m.It = false, n.m.ht = NOT_PENDING;
|
|
841
|
+
// An effect with a staged value was born held (L2): its first run is
|
|
842
|
+
// this commit's, not its creation's (A29) — queue it now.
|
|
843
|
+
if (e.u && e.u !== EFFECT_TRACKED) {
|
|
844
|
+
e.lt = true;
|
|
845
|
+
globalQueue.enqueue(e.u, e.Y ??= GlobalQueue.q.bind(null, n));
|
|
1211
846
|
}
|
|
1212
|
-
// Replay entanglement: subs recorded by the read-time gate get rescheduled
|
|
1213
|
-
// so they re-run with the now-committed values visible. The ambient batch
|
|
1214
|
-
// replays too — laneReadsCommitted records readers whose committed-view
|
|
1215
|
-
// read hid a same-tick plain write that just committed above (#2963).
|
|
1216
|
-
if (t.Tt.size) {
|
|
1217
|
-
for (const e of t.Tt) {
|
|
1218
|
-
if (e.oe & REACTIVE_DISPOSED) continue;
|
|
1219
|
-
enqueueSub(e);
|
|
1220
|
-
}
|
|
1221
|
-
t.Tt.clear();
|
|
1222
|
-
// A completing transition keeps the outer flush loop alive by itself;
|
|
1223
|
-
// the ambient batch needs the re-arm or the replay sits in the heap
|
|
1224
|
-
// until the next unrelated write.
|
|
1225
|
-
schedule();
|
|
1226
|
-
}
|
|
1227
|
-
// Declared motion ends with the transaction: settle (or plain flush end
|
|
1228
|
-
// for ambient marks) releases each registration's refcount. A non-empty
|
|
1229
|
-
// batch means registerAffectsMark ran, which installed the hook. Marks
|
|
1230
|
-
// held boundary display state through the visual channel, and their
|
|
1231
|
-
// release is the display-state update point — re-run the boundary sweep
|
|
1232
|
-
// (the earlier sweep above ran while the marks were still live).
|
|
1233
|
-
if (t.A.length) {
|
|
1234
|
-
GlobalQueue.G(t.A);
|
|
1235
|
-
if (globalQueue.Xn.length) checkBoundaryChildren(globalQueue);
|
|
1236
|
-
}
|
|
1237
|
-
// A non-empty set means trackOptimisticStore ran, which installed the
|
|
1238
|
-
// hook; the hook iterates, clears, and schedules (keeping the loop out of
|
|
1239
|
-
// core lets esbuild shake it — rollup already folds the null guard). The
|
|
1240
|
-
// completing transition scopes the clear to its own layer keys (#2899).
|
|
1241
|
-
if (t.En.size) GlobalQueue.ii(t.En, e);
|
|
1242
|
-
// Held-truth reveal wake (#3164), post-revert by construction: this
|
|
1243
|
-
// finalize committed confirming truth whose subscribers were masked all
|
|
1244
|
-
// hold — some re-derived against the committed view (the staging, or a
|
|
1245
|
-
// confirming carrier's landing, notified them as a plain write) and
|
|
1246
|
-
// cached it, and stash-restored applies may carry those torn values.
|
|
1247
|
-
// Waking and recomputing HERE — after _resolveOptimistic and the store
|
|
1248
|
-
// clears above — means every apply paints the settled view; a wake at
|
|
1249
|
-
// commit time would recompute them in the window where truth is
|
|
1250
|
-
// committed but the settling transaction's overrides still display.
|
|
1251
|
-
if (heldRevealed.length !== 0) {
|
|
1252
|
-
while (heldRevealed.length) insertSubs(heldRevealed.pop());
|
|
1253
|
-
if (dirtyQueue.EE >= dirtyQueue.st) {
|
|
1254
|
-
runHeap(dirtyQueue, GlobalQueue.nt);
|
|
1255
|
-
commitPendingNodes();
|
|
1256
|
-
}
|
|
1257
|
-
}
|
|
1258
|
-
sweepTransientStoreNodes();
|
|
1259
|
-
// Lanes only enter activeLanes through the engine's getOrCreateLane.
|
|
1260
|
-
if (activeLanes.size) GlobalQueue.Wn(e);
|
|
1261
847
|
}
|
|
848
|
+
// The committed value is the first observable answer for a loading-window
|
|
849
|
+
// node — the window closes here, not at compute time (#2990). Unconditional
|
|
850
|
+
// store to an always-present computed slot.
|
|
851
|
+
n.De = false;
|
|
852
|
+
n.R &= ~REACTIVE_MANUAL_WRITE;
|
|
853
|
+
// The dependencies of the pass that produced the value are the frame's now:
|
|
854
|
+
// the previous frame's tail goes (A30, #3410; `recompute` left it for a
|
|
855
|
+
// staged pass). Only after a clean pass: `_error` is cleared by a clean
|
|
856
|
+
// pass or by the node's own landing (whose pass was clean), so a set
|
|
857
|
+
// `_error` means the last pass threw, kept its full list, and `_depsTail`
|
|
858
|
+
// marks where it stopped.
|
|
859
|
+
if (n.m?.F == null) trimStaleDeps(n);
|
|
860
|
+
if (!(n.S & STATUS_PENDING)) n.S &= ~STATUS_UNINITIALIZED;
|
|
861
|
+
// L2: the children this commit publishes are the frame's now — the frame
|
|
862
|
+
// they replace, parked by the pass (`recompute`), goes.
|
|
863
|
+
if (!t && n.m != null && (n.m.St !== null || n.m.ct !== null)) GlobalQueue.Et(n, false, true);
|
|
1262
864
|
}
|
|
1263
865
|
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
|
|
866
|
+
function commitPendingNodes() {
|
|
867
|
+
for (let e = 0; e < pendingNodes.length; e++) {
|
|
868
|
+
const n = pendingNodes[e];
|
|
869
|
+
// A node a lane pass re-staged this round is the lane's to reveal; one
|
|
870
|
+
// a transaction holds (born held by a mainline pass, `passTx`) is that
|
|
871
|
+
// transaction's to commit, at its landing.
|
|
872
|
+
if (!(n.i & (CONFIG_OVERRIDE | CONFIG_HELD))) commitPendingNode(n);
|
|
1270
873
|
}
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
/**
|
|
1274
|
-
* Count of live `affects()` registrations across the system (including
|
|
1275
|
-
* store-scope inherited marks). Gates the read-path mark check in `read()` so
|
|
1276
|
-
* graphs that never use the feature pay one integer compare.
|
|
1277
|
-
*/ let activeAffectsMarks = 0;
|
|
1278
|
-
|
|
1279
|
-
/**
|
|
1280
|
-
* Counter mutation seam for the mark engine in affects.ts: an imported `let`
|
|
1281
|
-
* binding is read-only, and the read-path gate above must stay a plain module
|
|
1282
|
-
* variable so `read()` pays one integer compare, not a function call.
|
|
1283
|
-
*
|
|
1284
|
-
* @internal
|
|
1285
|
-
*/ function shiftAffectsMarks(e) {
|
|
1286
|
-
activeAffectsMarks += e;
|
|
1287
|
-
}
|
|
1288
|
-
|
|
1289
|
-
function reassignPendingTransition(e) {
|
|
1290
|
-
for (let t = 0; t < e.length; t++) {
|
|
1291
|
-
e[t].me = activeTransition;
|
|
1292
|
-
e[t].C &= ~CONFIG_ADOPTED_UNFLUSHED;
|
|
1293
|
-
// this flush carried it
|
|
1294
|
-
}
|
|
874
|
+
pendingNodes.length = 0;
|
|
1295
875
|
}
|
|
1296
876
|
|
|
1297
877
|
const globalQueue = new GlobalQueue;
|
|
1298
878
|
|
|
1299
|
-
// Hot-path mirror of `globalQueue._batch`: `queuePendingNode` runs once per
|
|
1300
|
-
// staged write and `commitPendingNodes` once per flush, and the extra
|
|
1301
|
-
// property hop through `_batch` was a measured instruction-count regression
|
|
1302
|
-
// (CodSpeed update1to1, PR #2905). The field stays authoritative for
|
|
1303
|
-
// cross-module readers; every `_batch` assignment updates both.
|
|
1304
|
-
let currentBatch = globalQueue.m;
|
|
1305
|
-
|
|
1306
879
|
function flush(e) {
|
|
1307
|
-
|
|
1308
|
-
// writes are held by its transaction until it settles, so a drain can't
|
|
1309
|
-
// reveal them — and the loop below only exits once `activeTransition` is
|
|
1310
|
-
// null, so it would PARK the transaction mid-slice and every write after it
|
|
1311
|
-
// in the body would land as a plain, committed write. The reporter's
|
|
1312
|
-
// "leading flush()" workaround was exactly that leak. Prod: run `fn` if
|
|
1313
|
-
// given (its writes stay in the transaction) and skip the drain.
|
|
1314
|
-
if (actionStepDepth > 0) {
|
|
880
|
+
if (actionDepth > 0) {
|
|
1315
881
|
return e ? e() : undefined;
|
|
1316
882
|
}
|
|
1317
883
|
if (e) {
|
|
@@ -1328,234 +894,31 @@ function flush(e) {
|
|
|
1328
894
|
}
|
|
1329
895
|
}
|
|
1330
896
|
}
|
|
1331
|
-
if (globalQueue
|
|
897
|
+
if (globalQueue.X) {
|
|
1332
898
|
return;
|
|
1333
899
|
}
|
|
1334
900
|
if (halted) return;
|
|
1335
901
|
// Attribution: whether this call drained anything, so `flushEnd` fires once
|
|
1336
902
|
// per real drain and never for a no-op call. The declaration is dead in
|
|
1337
903
|
// prod (its only write is behind true) and rollup drops it.
|
|
1338
|
-
let
|
|
904
|
+
let n = false;
|
|
1339
905
|
// The drain's opening instant, under the loop's own condition so it fires
|
|
1340
906
|
// exactly when `flushEnd` below will. Outside every try (see the rule in
|
|
1341
907
|
// attribution-hooks.ts).
|
|
1342
|
-
if (attrHooks !== null &&
|
|
1343
|
-
|
|
1344
|
-
// transition even if no microtask was scheduled for it yet.
|
|
1345
|
-
while (scheduled || activeTransition) {
|
|
908
|
+
if (attrHooks !== null && scheduled) attrHooks.flushStart();
|
|
909
|
+
while (scheduled) {
|
|
1346
910
|
globalQueue.flush();
|
|
1347
|
-
|
|
911
|
+
n = true;
|
|
1348
912
|
}
|
|
1349
|
-
// Provenance ends with the drain: every ambient window (an action's first
|
|
1350
|
-
// slice, a landing's propagation) runs to this flush.
|
|
1351
|
-
origin = 0;
|
|
1352
913
|
// Outside every try in this function (see the rule in attribution-hooks.ts):
|
|
1353
914
|
// the drain loop above is the one place all scheduled work funnels through,
|
|
1354
|
-
// so this is the "committed and effects ran
|
|
1355
|
-
//
|
|
1356
|
-
if (
|
|
1357
|
-
}
|
|
1358
|
-
|
|
1359
|
-
function runQueue(e, t) {
|
|
1360
|
-
for (let n = 0; n < e.length; n++) e[n](t);
|
|
915
|
+
// so this is the "committed and effects ran" instant for everything the
|
|
916
|
+
// loop processed.
|
|
917
|
+
if (n && attrHooks !== null) attrHooks.flushEnd();
|
|
1361
918
|
}
|
|
1362
919
|
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
* its current pass? The verdict's per-reporter test (sourceObserved), also
|
|
1366
|
-
* a boundary re-arm's (boundaries.ts `_rearm` runs before the verdict prunes
|
|
1367
|
-
* registrations that stopped counting). */ function reporterBlocksSource(e, t, n) {
|
|
1368
|
-
const i = e.oe;
|
|
1369
|
-
if (i & REACTIVE_DISPOSED) return false;
|
|
1370
|
-
// A zombie renders until the commit that disposes it (#3463): while its
|
|
1371
|
-
// removal is staged in a live transaction it is still on screen, and what
|
|
1372
|
-
// it displays must stay consistent with the frame — a held `Show`'s
|
|
1373
|
-
// `Details: 0` beside the lane's `Value: 1` otherwise. Its say is moot for
|
|
1374
|
-
// the verdict of the transaction that stages the removal (`verdict`): done,
|
|
1375
|
-
// and the commit disposes it; not done, and it stays parked regardless. A
|
|
1376
|
-
// zombie whose removal commits this flush (owner's pass not held) is dead.
|
|
1377
|
-
// The owner is stamped when the flush parks; held in this flush, its
|
|
1378
|
-
// staging transaction is the active one.
|
|
1379
|
-
if (i & REACTIVE_ZOMBIE) {
|
|
1380
|
-
let t = e;
|
|
1381
|
-
while (t && t.oe & REACTIVE_ZOMBIE) t = t._parent;
|
|
1382
|
-
let i = t && (t.me || (t.C & CONFIG_HELD_CHILDREN ? activeTransition : null));
|
|
1383
|
-
// A LANE frame's member (#3662) is displayed until its owner's run
|
|
1384
|
-
// applies, and the lane's transaction is what applies it (its completion
|
|
1385
|
-
// runs the lane's queue): moot for that verdict, live for every other.
|
|
1386
|
-
if (!i && t && t.C & CONFIG_LANE_FRAME && t.o?.Ce) i = findLane(t.o.Ce).me;
|
|
1387
|
-
if (!i || (i = currentTransition(i)).Dt === true || i === n) return false;
|
|
1388
|
-
}
|
|
1389
|
-
// Fallback-caught async holds nothing. A collecting loading boundary
|
|
1390
|
-
// consumes the notification, so a reader under a fallback never registers —
|
|
1391
|
-
// but a reader registered while its boundary showed content stays
|
|
1392
|
-
// registered when the boundary's `on` later changes and it flips to the
|
|
1393
|
-
// fallback. The reader is behind the fallback now; if nothing outside the
|
|
1394
|
-
// boundary consumes the flight, the hold is over (A33, ruled 2026-09-12, #3375).
|
|
1395
|
-
for (let t = e.T; t; t = t._parent) if (t.te & STATUS_PENDING && !t.H) return false;
|
|
1396
|
-
// "Still derives from the source" is a question about THIS pass's reads:
|
|
1397
|
-
// the deps up to `_depsTail`. Past it lie the committed frame's — kept
|
|
1398
|
-
// linked by A30 until the commit trims them (a staged pass, an errored
|
|
1399
|
-
// one). Reading them here made a reporter whose pass had stopped reading
|
|
1400
|
-
// the source (a gate closed in the same flush as the write) look live, and
|
|
1401
|
-
// the hold it kept was the commit that would have trimmed the dep that
|
|
1402
|
-
// kept it (spec O3, same-flush form; fuzzer #3446 P1 cases 21/79). A
|
|
1403
|
-
// trimmed list ends at `_depsTail`, so the bound is free there; a pass
|
|
1404
|
-
// that read nothing has a null tail and derives from nothing.
|
|
1405
|
-
// The registration is trusted: a pending mark rides only this pass's links
|
|
1406
|
-
// (notifyStatus skips the kept tail), so a registered reporter read the
|
|
1407
|
-
// source, or threw on it.
|
|
1408
|
-
if (e.o?.ue?.has(t)) return true;
|
|
1409
|
-
const r = e.Je;
|
|
1410
|
-
for (let n = r === null ? null : e.Ee; n; n = n === r ? null : n.Ie) {
|
|
1411
|
-
let e = n.De;
|
|
1412
|
-
while (e) {
|
|
1413
|
-
// Or through a memo pending on the flight (#3494): a stale reader
|
|
1414
|
-
// served a held memo's committed value never turned pending itself, so
|
|
1415
|
-
// its only trace of the flight is the memo between them — `copy` of
|
|
1416
|
-
// `details`. Judged dead, its transaction released `count=1` beside
|
|
1417
|
-
// the `Copy: 0` it displays. `_pendingSources` is transitive, so one
|
|
1418
|
-
// hop covers any depth.
|
|
1419
|
-
if (e === t || e.Ne === t || e.o?.ue?.has(t)) return true;
|
|
1420
|
-
e = e.o?.$n;
|
|
1421
|
-
}
|
|
1422
|
-
}
|
|
1423
|
-
return !!(e.S & STATUS_PENDING && e.o?._ instanceof NotReadyError && e.o?._.source === t);
|
|
1424
|
-
}
|
|
1425
|
-
|
|
1426
|
-
/**
|
|
1427
|
-
* Does a live reporter of `transition` still observe `source` pending? Dead
|
|
1428
|
-
* reporters (disposed, behind a fallback, no longer reading the source) are
|
|
1429
|
-
* pruned as they are found, and the source's entry with them. Shared by the
|
|
1430
|
-
* settle verdict and the lane's hold check (`waitingTransition`): a live
|
|
1431
|
-
* action parks its transaction without a verdict, so this prune is the only
|
|
1432
|
-
* one an optimistic lane whose last async reader unmounted mid-action ever
|
|
1433
|
-
* gets — without it the lane held on the dead reporter's registration until
|
|
1434
|
-
* the flight it no longer observed landed (#3426).
|
|
1435
|
-
*/ function sourceObserved(e, t, n) {
|
|
1436
|
-
const i = e.ae.get(t);
|
|
1437
|
-
let r = false;
|
|
1438
|
-
for (const e of i ?? []) {
|
|
1439
|
-
if (reporterBlocksSource(e, t, n)) return true;
|
|
1440
|
-
// A zombie the verdict passes over is kept, not pruned (#3463): moot for
|
|
1441
|
-
// this verdict, it still holds a lane's reveal while the transaction
|
|
1442
|
-
// stays parked on something else.
|
|
1443
|
-
if (n && e.oe & REACTIVE_ZOMBIE) r = true; else i.delete(e);
|
|
1444
|
-
}
|
|
1445
|
-
if (!r) e.ae.delete(t);
|
|
1446
|
-
return false;
|
|
1447
|
-
}
|
|
1448
|
-
|
|
1449
|
-
function transitionComplete(e) {
|
|
1450
|
-
if (e.Dt) return true;
|
|
1451
|
-
if (e.tt.length) {
|
|
1452
|
-
// A live action parks the transaction regardless of async state.
|
|
1453
|
-
if (attrHooks !== null) attrHooks.holdStart(e);
|
|
1454
|
-
return false;
|
|
1455
|
-
}
|
|
1456
|
-
let t = true;
|
|
1457
|
-
for (const n of e.ae.keys()) {
|
|
1458
|
-
// The source blocks while its OWN flight is up — the self entry in its
|
|
1459
|
-
// pending sources (added by notifyStatus's source path, with status;
|
|
1460
|
-
// retired by the landing and the supersede sweep, so it implies
|
|
1461
|
-
// STATUS_PENDING). `_error.source` is not that test: propagation from an
|
|
1462
|
-
// input that went pending later overwrites it with the input (#3375 — a
|
|
1463
|
-
// boundary-consumed load re-asked under a held derivation), and the
|
|
1464
|
-
// still-flying source read as settled, committing the writes it was
|
|
1465
|
-
// asked with ahead of its answer.
|
|
1466
|
-
// Not the self entry alone: a source whose own flight an upstream re-ask
|
|
1467
|
-
// superseded is still pending — on that re-ask (#3462). Its reader cannot
|
|
1468
|
-
// render until the chain lands, and the landing folds this transaction in
|
|
1469
|
-
// (enterWaiting). Judged complete instead, a re-entry between the two (a
|
|
1470
|
-
// repeated write to a held signal) committed the held writes beside the
|
|
1471
|
-
// reader's stale frame.
|
|
1472
|
-
if (sourceObserved(e, n, e) && n.o?.ue?.size) {
|
|
1473
|
-
t = false;
|
|
1474
|
-
break;
|
|
1475
|
-
}
|
|
1476
|
-
}
|
|
1477
|
-
// Override blockage lives with the engine (absent hook = "no optimistic
|
|
1478
|
-
// blockage"); the hook's loops over _optimisticNodes/_optimisticStores are
|
|
1479
|
-
// no-ops when the transition holds neither, so no pre-check is needed.
|
|
1480
|
-
if (t && GlobalQueue.Yn?.(e)) t = false;
|
|
1481
|
-
// Attribution hook: this verdict is the fork between settling (held writes
|
|
1482
|
-
// commit next — `_pendingNodes` still lists them) and parking (the flush
|
|
1483
|
-
// runs the lane effects, then stashes; `holdEnd` fires from stashQueues).
|
|
1484
|
-
// Fired here rather than at flush()'s call site because that site is inside
|
|
1485
|
-
// a `try` (see the rule in attribution-hooks.ts).
|
|
1486
|
-
if (attrHooks !== null) t ? attrHooks.transitionSettled(e) : attrHooks.holdStart(e);
|
|
1487
|
-
t && (e.Dt = true);
|
|
1488
|
-
return t;
|
|
1489
|
-
}
|
|
1490
|
-
|
|
1491
|
-
/** A fresh, unentered transaction (#3146): the optimistic store's truth
|
|
1492
|
-
* flight DECLARES an owned transaction instead of relying on whatever the
|
|
1493
|
-
* ambient adoption machinery stamped on its firewall. Activate it with
|
|
1494
|
-
* initTransition; it is a plain batch until then. */ function createTransition() {
|
|
1495
|
-
return createBatch();
|
|
1496
|
-
}
|
|
1497
|
-
|
|
1498
|
-
function currentTransition(e) {
|
|
1499
|
-
while (e.Dt && typeof e.Dt === "object") e = e.Dt;
|
|
1500
|
-
return e;
|
|
1501
|
-
}
|
|
1502
|
-
|
|
1503
|
-
/**
|
|
1504
|
-
* The live transition blocked on `source` — the one whose render reader
|
|
1505
|
-
* observed it pending (INV-3 records the observation in whichever transaction
|
|
1506
|
-
* was active when the reader was notified). The observation is a fact about
|
|
1507
|
-
* the node, so a hold check must not assume it was recorded in the transaction
|
|
1508
|
-
* it happens to hold — lanes merge across transactions (#2912), and a merged
|
|
1509
|
-
* root's transaction knows nothing of the async its members' transactions
|
|
1510
|
-
* observed (#3335). Null when nobody is waiting — a registration whose every
|
|
1511
|
-
* reporter has since died is nobody (#3426).
|
|
1512
|
-
*/ function waitingTransition(e) {
|
|
1513
|
-
for (const t of transitions) if (sourceObserved(t, e)) return t;
|
|
1514
|
-
return null;
|
|
1515
|
-
}
|
|
1516
|
-
|
|
1517
|
-
/** A landing enters EVERY parked transaction still waiting on `source`, folding
|
|
1518
|
-
* them into the active one (A15: each reveal that discovered the flight
|
|
1519
|
-
* completes at its landing). The fold used to happen as the waiters' stamped
|
|
1520
|
-
* readers recomputed under the landing — recompute re-entering an effect's
|
|
1521
|
-
* stamp — which also folded in writes those readers merely shared a hole
|
|
1522
|
-
* with (#3407); effects no longer re-enter, so the landing folds explicitly.
|
|
1523
|
-
* Live iteration is safe: a merge deletes the outgoing (active) entry and
|
|
1524
|
-
* re-adds the visited one. */ function enterWaiting(e) {
|
|
1525
|
-
for (const t of transitions) if (sourceObserved(t, e)) globalQueue.initTransition(t);
|
|
1526
|
-
}
|
|
1527
|
-
|
|
1528
|
-
function runInTransition(e, t) {
|
|
1529
|
-
const n = activeTransition;
|
|
1530
|
-
try {
|
|
1531
|
-
activeTransition = currentTransition(e);
|
|
1532
|
-
return t();
|
|
1533
|
-
} finally {
|
|
1534
|
-
activeTransition = n;
|
|
1535
|
-
}
|
|
1536
|
-
}
|
|
1537
|
-
|
|
1538
|
-
/** Run `fn` with `transition` as BOTH the ambient transaction and the
|
|
1539
|
-
* registration batch, restoring both after. runInTransition alone is not
|
|
1540
|
-
* enough for code that WRITES on behalf of a transaction from inside someone
|
|
1541
|
-
* else's window (optimistic replay re-arming a still-open action's edits
|
|
1542
|
-
* during a landing commit, #3123): registrations route through the queue's
|
|
1543
|
-
* batch pointer, and a bare activeTransition swap leaves them in the ambient
|
|
1544
|
-
* batch — a plain batch "completes" at the next flush and reverts optimistic
|
|
1545
|
-
* registrations that were supposed to live with the transaction.
|
|
1546
|
-
* initTransition is the wrong tool here: it MERGES the currently ambient
|
|
1547
|
-
* transaction into the target, entangling whatever the interrupted window
|
|
1548
|
-
* belonged to. */ function runAsTransitionBatch(e, t) {
|
|
1549
|
-
const n = activeTransition;
|
|
1550
|
-
const i = globalQueue.m;
|
|
1551
|
-
try {
|
|
1552
|
-
activeTransition = currentTransition(e);
|
|
1553
|
-
currentBatch = globalQueue.m = activeTransition;
|
|
1554
|
-
return t();
|
|
1555
|
-
} finally {
|
|
1556
|
-
activeTransition = n;
|
|
1557
|
-
currentBatch = globalQueue.m = i;
|
|
1558
|
-
}
|
|
920
|
+
function runQueue(e, n) {
|
|
921
|
+
for (let t = 0; t < e.length; t++) e[t](n);
|
|
1559
922
|
}
|
|
1560
923
|
|
|
1561
|
-
export { GlobalQueue,
|
|
924
|
+
export { GlobalQueue, MAINLINE_QUESTION, ROOT_ERROR_HOOK, actionDepth, blocked, blockersOf, bumpNotifyEpoch, clock, commitPendingNode, deferZombie, dirtyQueue, enforceLoadingBoundary, enterAction, exitAction, flush, flushTransaction, globalQueue, haltReactivity, heldTrim, holdNode, insertSubs, joinFuture, joinPassTx, laneDirty, list, liveTx, merge, newTransaction, nextQuestion, notifyEpoch, ownFlights, passLane, passTx, question, queuePendingNode, releaseQueues, reruns, resetErrorHalt, resolveTx, sameLane, schedule, setPassLane, setQuestion, stagedReaders, staleReader, txOf };
|