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