@the-w1zard/dx 1.0.11 → 1.0.13

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.
@@ -0,0 +1,418 @@
1
+ type F64 = Float64Array;
2
+ type V128 = N | F64; // a "value" is either scalar (stride=1) or a stride-length Float64 view
3
+ type Derived = {
4
+ deps: AR<N>;
5
+ fn: (...vals: AR<V128>) => V128;
6
+ dirty: B;
7
+ has: B;
8
+ value?: V128;
9
+ };
10
+ export type M2BEvent = {
11
+ op: "set" | "upd" | "del" | "dirty";
12
+ pid: N;
13
+ v?: V128;
14
+ prev?: V128;
15
+ has: B;
16
+ batch?: B;
17
+ };
18
+ type Sub = (e: M2BEvent) => V;
19
+ const sameValue128 = (a: V128 | UD, b: V128 | UD): B => {
20
+ if (a === b) return true;
21
+ if (a instanceof Float64Array && b instanceof Float64Array) {
22
+ if (a.length !== b.length) return false;
23
+ for (let i = 0; i < a.length; i++) if (!Object.is(a[i], b[i])) return false;
24
+ return true;
25
+ }
26
+ return Object.is(a, b);
27
+ };
28
+ export class M2BI {
29
+ readonly cap: N;
30
+ readonly stride: N;
31
+
32
+ readonly pidToSlot: I32;
33
+ readonly slotToPid: I32;
34
+ readonly vals: F64;
35
+
36
+ #size = 0;
37
+ #subs?: Set<Sub>;
38
+ #hist?: AR<M2BEvent>;
39
+ #histLimit = 0;
40
+ #strict = false;
41
+ #iterating = 0;
42
+ #batching = 0;
43
+ #pending?: Map<N, M2BEvent>;
44
+ #derived?: Map<N, Derived>;
45
+ #dependents?: Map<N, Set<N>>;
46
+
47
+ constructor(maxParticles: N= 65_536, stride: N = 1, seed?: I<readonly [N, V128]>) {
48
+ this.cap = maxParticles;
49
+ this.stride = stride;
50
+ this.pidToSlot = new Int32Array(maxParticles).fill(-1);
51
+ this.slotToPid = new Int32Array(maxParticles).fill(-1);
52
+ this.vals = new Float64Array(maxParticles * stride);
53
+ if (seed) for (const [pid, v] of seed) this.s(v, pid);
54
+ }
55
+
56
+ get size(): N { return this.#size; }
57
+
58
+ // ---------------------------------------------------------------- internals
59
+ #emit(e: M2BEvent): V {
60
+ if (this.#batching > 0) {
61
+ this.#pending ??= new Map();
62
+ const pending = this.#pending.get(e.pid);
63
+ this.#pending.set(e.pid, pending ? { ...e, prev: pending.prev, batch: true } : { ...e, batch: true });
64
+ return;
65
+ }
66
+ this.#record(e);
67
+ this.#subs?.forEach((sub) => { try { sub(e); } catch (err: A) { process.emitWarning(err); } });
68
+ }
69
+ #record(e: M2BEvent): V {
70
+ if (!this.#hist) return;
71
+ this.#hist.push(e);
72
+ if (this.#hist.length > this.#histLimit) this.#hist.splice(0, this.#hist.length - this.#histLimit);
73
+ }
74
+ #invalidate(pid: N): V {
75
+ const deps = this.#dependents?.get(pid);
76
+ if (!deps) return;
77
+ for (const dk of deps) {
78
+ const d = this.#derived?.get(dk);
79
+ if (!d || d.dirty) continue;
80
+ d.dirty = true;
81
+ this.#emit({ op: "dirty", pid: dk, has: d.has });
82
+ }
83
+ }
84
+ #computeDerived(pid: N, d: Derived): V128 {
85
+ const vals = d.deps.map((dep) => this.g(dep) as V128);
86
+ const next = d.fn(...vals);
87
+ const had = d.has;
88
+ const prev = d.value;
89
+ d.value = next; d.has = true; d.dirty = false;
90
+ if (!had || !sameValue128(prev, next)) {
91
+ this.#emit({ op: had ? "upd" : "set", pid, v: next, prev, has: true });
92
+ }
93
+ return next;
94
+ }
95
+ #assertNotIterating(op: S): V {
96
+ if (this.#strict && this.#iterating > 0) {
97
+ throw new Error(`M2BI.${op}() during iteration. Snapshot keys first, or disable $strict().`);
98
+ }
99
+ }
100
+ #readSlot(slot: N): V128 {
101
+ return this.stride === 1 ? this.vals[slot] : this.vals.subarray(slot * this.stride, slot * this.stride + this.stride);
102
+ }
103
+ #writeSlot(slot: N, v: V128): V {
104
+ if (this.stride === 1) this.vals[slot] = v as N;
105
+ else this.vals.set(v as F64, slot * this.stride);
106
+ }
107
+ #fresh(): M2BI { return new M2BI(this.cap, this.stride); } // [sibling-store factory]
108
+
109
+ // ---------------------------------------------------------------- subs / batch
110
+ public on(fn: Sub): () => V { (this.#subs ??= new Set()).add(fn); return () => this.off(fn); } // [ON]
111
+ public off(fn: Sub): V { this.#subs?.delete(fn); } // [OFF]
112
+ public batch(fn: () => V): this { // [BATCH]
113
+ this.#batching++;
114
+ this.#pending ??= new Map();
115
+ try { fn(); }
116
+ finally {
117
+ this.#batching--;
118
+ if (this.#batching === 0) {
119
+ const events = [...this.#pending.values()];
120
+ this.#pending = undefined;
121
+ for (const e of events) { this.#record(e); this.#subs?.forEach(sub => sub(e)); }
122
+ }
123
+ }
124
+ return this;
125
+ }
126
+
127
+ // ---------------------------------------------------------------- core CRUD
128
+ public s(v: V128, pid: N): this { // [SET]
129
+ this.#assertNotIterating("s");
130
+ let slot = this.pidToSlot[pid];
131
+ const has = slot !== -1;
132
+ const prev = has ? this.#readSlot(slot) : undefined;
133
+ if (!has) {
134
+ slot = this.#size;
135
+ this.pidToSlot[pid] = slot;
136
+ this.slotToPid[slot] = pid;
137
+ this.#size++;
138
+ }
139
+ this.#writeSlot(slot, v);
140
+ this.#invalidate(pid);
141
+ this.#emit({ op: has ? "upd" : "set", pid, v, prev, has });
142
+ return this;
143
+ }
144
+ public mutate(pid: N, fn: (old: V128, has: true) => V128): V128 | UD { // [MUTATE]
145
+ this.#assertNotIterating("mutate");
146
+ if (!this.h(pid)) return undefined;
147
+ const next = fn(this.g(pid) as V128, true);
148
+ this.s(next, pid);
149
+ return next;
150
+ }
151
+ public d(pid: N): B { // [DELETE]
152
+ this.#assertNotIterating("delete");
153
+ const slot = this.pidToSlot[pid];
154
+ if (slot === -1) return false;
155
+ const prev = this.#readSlot(slot);
156
+ const lastSlot = this.#size - 1;
157
+ const lastPid = this.slotToPid[lastSlot];
158
+ if (slot !== lastSlot) {
159
+ this.#writeSlot(slot, this.#readSlot(lastSlot));
160
+ this.slotToPid[slot] = lastPid;
161
+ this.pidToSlot[lastPid] = slot;
162
+ }
163
+ this.pidToSlot[pid] = -1;
164
+ this.slotToPid[lastSlot] = -1;
165
+ this.#size--;
166
+ this.#invalidate(pid);
167
+ this.#emit({ op: "del", pid, prev, has: false });
168
+ return true;
169
+ }
170
+ public c(): V { // [CLEAR]
171
+ this.#assertNotIterating("clear");
172
+ for (let i = this.#size - 1; i >= 0; i--) {
173
+ const pid = this.slotToPid[i];
174
+ const prev = this.#readSlot(i);
175
+ this.pidToSlot[pid] = -1;
176
+ this.slotToPid[i] = -1;
177
+ this.#invalidate(pid);
178
+ this.#emit({ op: "del", pid, prev, has: false });
179
+ }
180
+ this.#size = 0;
181
+ }
182
+ public dv(pid: N, deps: AR<N>, fn: (...vals: AR<V128>) => V128): this { // [DERIVED]
183
+ (this.#derived ??= new Map()).set(pid, { deps, fn, dirty: true, has: false });
184
+ for (const dep of deps) {
185
+ const set = (this.#dependents ??= new Map());
186
+ if (!set.has(dep)) set.set(dep, new Set());
187
+ set.get(dep)!.add(pid);
188
+ }
189
+ return this;
190
+ }
191
+ public dirty(...pids: AR<N>): this { // [DIRTY]
192
+ for (const pid of pids) {
193
+ const d = this.#derived?.get(pid);
194
+ if (d && !d.dirty) { d.dirty = true; this.#emit({ op: "dirty", pid, has: d.has }); }
195
+ }
196
+ return this;
197
+ }
198
+ public g(pid: N): V128 | UD { // [GET]
199
+ const d = this.#derived?.get(pid);
200
+ if (d) return d.dirty || !d.has ? this.#computeDerived(pid, d) : d.value!;
201
+ const slot = this.pidToSlot[pid];
202
+ return slot === -1 ? undefined : this.#readSlot(slot);
203
+ }
204
+ public h(pid: N): B { // [HAS]
205
+ const d = this.#derived?.get(pid);
206
+ if (d) { if (d.dirty || !d.has) this.#computeDerived(pid, d); return true; }
207
+ return this.pidToSlot[pid] !== -1;
208
+ }
209
+
210
+ // ---------------------------------------------------------------- debug
211
+ public $debug(limit: N = 256): this { this.#hist ??= []; this.#histLimit = limit; return this; } // [$DEBUG]
212
+ public $strict(): this { this.#strict = true; return this; } // [$STRICT]
213
+ public $history(): AR<M2BEvent> { return this.#hist ? [...this.#hist] : []; } // [$HISTORY]
214
+ public $why(pid: N): M2BEvent | UD { // [$WHY]
215
+ if (!this.#hist) return undefined;
216
+ for (let i = this.#hist.length - 1; i >= 0; i--) if (this.#hist[i].pid === pid) return this.#hist[i];
217
+ return undefined;
218
+ }
219
+ public $diff(other: M2BI) { // [$DIFF]
220
+ const add: AR<[N, V128]> = [];
221
+ const del: AR<N> = [];
222
+ const chg: AR<[N, V128, V128]> = [];
223
+ for (const [pid, v] of this) {
224
+ if (!other.has(pid)) add.push([pid, v]);
225
+ else if (!sameValue128(other.get(pid), v)) chg.push([pid, v, other.get(pid)!]);
226
+ }
227
+ for (const pid of other.keys()) if (!this.has(pid)) del.push(pid);
228
+ return { add, del, chg };
229
+ }
230
+
231
+ // ---------------------------------------------------------------- iteration (dense slots, no hashing)
232
+ public *entries(): Generator<[N, V128]> { // [*ENTRIES]
233
+ this.#iterating++;
234
+ try { for (let i = 0; i < this.#size; i++) yield [this.slotToPid[i], this.#readSlot(i)]; }
235
+ finally { this.#iterating--; }
236
+ }
237
+ public *keys(): Generator<N> { // [*KEYS]
238
+ this.#iterating++;
239
+ try { for (let i = 0; i < this.#size; i++) yield this.slotToPid[i]; }
240
+ finally { this.#iterating--; }
241
+ }
242
+ public *values(): Generator<V128> { // [*VALUES]
243
+ this.#iterating++;
244
+ try { for (let i = 0; i < this.#size; i++) yield this.#readSlot(i); }
245
+ finally { this.#iterating--; }
246
+ }
247
+ public [Symbol.iterator](): Generator<[N, V128]> { return this.entries(); }
248
+
249
+ // ---------------------------------------------------------------- alias / has/has-not helpers
250
+ public has = this.h; // [has alias]
251
+ public get = this.g; // [get alias]
252
+ public delete = this.d; // [delete alias]
253
+ public clear = this.c; // [clear alias]
254
+ public f = this.forEach.bind(this); // [FOR EACH]
255
+ public forEach(fn: (v: V128, pid: N, map: this) => V): V { for (const [pid, v] of this) fn(v, pid, this); }
256
+
257
+ // ---------------------------------------------------------------- sugar
258
+ public swap(value: V128, pid: N): V128 | UD { // [SWAP]
259
+ const previous = this.has(pid) ? this.get(pid) : value;
260
+ this.s(value, pid);
261
+ return previous;
262
+ }
263
+ public take(pid: N): V128 | UD { const value = this.g(pid); this.d(pid); return value; } // [TAKE]
264
+ public peek(pid: N): V128 | false { return this.h(pid) ? (this.g(pid) as V128) : false; } // [PEEK]
265
+ public getOrI(value: V128, pid: N): V128 { // [GETORI]
266
+ if (this.h(pid)) return this.g(pid) as V128;
267
+ this.s(value, pid);
268
+ return value;
269
+ }
270
+ public getOrM(make: () => V128, pid: N): V128 { // [GETORM]
271
+ if (this.h(pid)) return this.g(pid) as V128;
272
+ const value = make();
273
+ this.s(value, pid);
274
+ return value;
275
+ }
276
+ public update(pid: N, fn: (old: V128, has: B) => V128, initial?: V128): V128 { // [UPDATE]
277
+ const has = this.h(pid);
278
+ const old = has ? (this.g(pid) as V128) : (initial === undefined ? (0 as V128) : initial);
279
+ const next = fn(old, has);
280
+ this.s(next, pid);
281
+ return next;
282
+ }
283
+ public setIfAbsent(value: V128, pid: N): B { // [SETIFABSENT]
284
+ if (this.h(pid)) return false;
285
+ this.s(value, pid);
286
+ return true;
287
+ }
288
+ public setIfPresent(value: V128, pid: N): B { // [SETIFPRESENT]
289
+ if (!this.h(pid)) return false;
290
+ this.s(value, pid);
291
+ return true;
292
+ }
293
+ public replace(value: V128, pid: N): B { return this.setIfPresent(value, pid); } // [REPLACE]
294
+ public inc(pid: N, by: N = 1): N { // [INC]
295
+ return this.update(pid, (old) => (((old as N) ?? 0) + by) as V128, 0) as N;
296
+ }
297
+ public touch(pid: N): B { return this.setIfAbsent(0 as V128, pid); } // [SWTOUCHAP]
298
+
299
+ // ---------------------------------------------------------------- transforms (produce new M2BI, same cap/stride)
300
+ public mapValues(fn: (v: V128, pid: N) => V128): M2BI { // [MAPVALUES]
301
+ const out = this.#fresh();
302
+ for (const [pid, v] of this) out.s(fn(v, pid), pid);
303
+ return out;
304
+ }
305
+ public mapKeys(fn: (v: V128, pid: N) => N): M2BI { // [MAPKEYS]
306
+ const out = this.#fresh(); // target pids must still fit within cap
307
+ for (const [pid, v] of this) out.s(v, fn(v, pid));
308
+ return out;
309
+ }
310
+ public filterKV(pred: (v: V128, pid: N) => B): this { // [FILTERKV]
311
+ for (const pid of [...this.keys()]) if (!pred(this.g(pid) as V128, pid)) this.d(pid);
312
+ return this;
313
+ }
314
+ public filterKeys(pred: (v: V128, pid: N) => B): this { return this.filterKV(pred); } // [FILTERKEYS]
315
+ public partition(pred: (v: V128, pid: N) => B): [M2BI, M2BI] { // [PARTITION]
316
+ const yes = this.#fresh(), no = this.#fresh();
317
+ for (const [pid, v] of this) (pred(v, pid) ? yes : no).s(v, pid);
318
+ return [yes, no];
319
+ }
320
+ public find(pred: (v: V128, pid: N) => B): [N, V128] | UD { // [FIND]
321
+ for (const [pid, v] of this) if (pred(v, pid)) return [pid, v];
322
+ return undefined;
323
+ }
324
+ public findKey(pred: (v: V128, pid: N) => B): N | UD { // [FINDKEY]
325
+ for (const [pid, v] of this) if (pred(v, pid)) return pid;
326
+ return undefined;
327
+ }
328
+ public findValue(pred: (v: V128, pid: N) => B): V128 | UD { // [FINDVALUE]
329
+ for (const [pid, v] of this) if (pred(v, pid)) return v;
330
+ return undefined;
331
+ }
332
+ public some(pred: (v: V128, pid: N) => B): B { // [SOME]
333
+ for (const [pid, v] of this) if (pred(v, pid)) return true;
334
+ return false;
335
+ }
336
+ public every(pred: (v: V128, pid: N) => B): B { // [EVERY]
337
+ for (const [pid, v] of this) if (!pred(v, pid)) return false;
338
+ return true;
339
+ }
340
+ public reduce<G>(fn: (acc: G, v: V128, pid: N) => G, initial: G): G { // [REDUCE]
341
+ let acc = initial;
342
+ for (const [pid, v] of this) acc = fn(acc, v, pid);
343
+ return acc;
344
+ }
345
+ public forEachKV(fn: (v: V128, pid: N, map: this) => V): V { for (const [pid, v] of this) fn(v, pid, this); } // [FOREACH]
346
+ public invert(): M2<V128, N> { // [INVERT]
347
+ const out = new Map<S, N>(); // values aren't valid pids/Int32 slots in general -> plain Map, not M2BI
348
+ for (const [pid, v] of this) out.set(this.stride === 1 ? String(v) : Array.from(v as F64).join(","), pid);
349
+ return out as A;
350
+ }
351
+ public unique(fn?: (v: V128, pid: N) => N): M2BI { // [UNIQUE]
352
+ const out = this.#fresh();
353
+ const seen = new Set<S>();
354
+ for (const [pid, v] of this) {
355
+ const keyFor = fn ? fn(v, pid) : pid;
356
+ const dedupeKey = this.stride === 1 ? String(v) : Array.from(v as F64).join(",");
357
+ if (!seen.has(dedupeKey)) { seen.add(dedupeKey); out.s(v, keyFor); }
358
+ }
359
+ return out;
360
+ }
361
+ public pick(pids: Iterable<N>): M2BI { // [PICK]
362
+ const out = this.#fresh();
363
+ for (const pid of pids) if (this.h(pid)) out.s(this.g(pid) as V128, pid);
364
+ return out;
365
+ }
366
+ public omit(pids: Iterable<N>): M2BI { // [OMIT]
367
+ const omitted = new Set(pids);
368
+ const out = this.#fresh();
369
+ for (const [pid, v] of this) if (!omitted.has(pid)) out.s(v, pid);
370
+ return out;
371
+ }
372
+ public merge(other: Iterable<readonly [N, V128]>): this { // [MERGE]
373
+ for (const [pid, v] of other) this.s(v, pid);
374
+ return this;
375
+ }
376
+ public clearAndSet(entries: Iterable<readonly [N, V128]>): this { // [CLEARANDSET]
377
+ return this.batch(() => { this.clear(); for (const [pid, v] of entries) this.s(v, pid); });
378
+ }
379
+ public toObject(): OB<V128> { // [TOPOBJECT]
380
+ const out = {} as OB<V128>;
381
+ for (const [pid, v] of this) out[pid as unknown as S] = v;
382
+ return out;
383
+ }
384
+ public toValues(): AR<V128> { return [...this.values()]; } // [TOVALUES]
385
+ public toKeys(): AR<N> { return [...this.keys()]; } // [TOKEYS]
386
+ public toArray(): AR<[N, V128]> { return [...this.entries()]; } // [TOARRAY]
387
+ public group<G>(fn: (v: V128, pid: N) => G): Map<G, AR<V128>> { // [GROUP]
388
+ const out = new Map<G, AR<V128>>();
389
+ for (const [pid, v] of this) {
390
+ const k = fn(v, pid);
391
+ if (!out.has(k)) out.set(k, []);
392
+ out.get(k)!.push(v);
393
+ }
394
+ return out;
395
+ }
396
+ public clone(): this { // [CLONE]
397
+ return new M2BI(this.cap, this.stride, this.entries()) as this;
398
+ }
399
+ public equals(other: M2BI): B { // [EQUALS]
400
+ if (other === this) return true;
401
+ if (this.size !== other.size) return false;
402
+ for (const [pid, v] of this) if (!other.has(pid) || !sameValue128(other.get(pid), v)) return false;
403
+ return true;
404
+ }
405
+
406
+ // ---------------------------------------------------------------- statics
407
+ static fromObject(obj: Record<N, V128>, maxParticles: N, stride: N = 1): M2BI { // [FROMOBBJ]
408
+ return new M2BI(maxParticles, stride, Object.entries(obj).map(([k, v]) => [Number(k), v] as [N, V128]));
409
+ }
410
+ static fromEntries(entries: I<readonly [N, V128]>, maxParticles: N, stride: N = 1): M2BI { // [FROMENTIRES]
411
+ return new M2BI(maxParticles, stride, entries);
412
+ }
413
+ static from(map: RM<N, V128>, maxParticles: N, stride: N = 1): M2BI { // [FROM]
414
+ return new M2BI(maxParticles, stride, map as A);
415
+ }
416
+ }
417
+
418
+ globalThis.M2B = M2BI;
@@ -0,0 +1,167 @@
1
+
2
+
3
+ export class M2DI<Key=A, V = number> {
4
+ #vals: F64;
5
+ #live: U8;
6
+ #size = 0;
7
+ #odd: M2<Key, V> | null = null;
8
+
9
+ constructor(capacity = 1024) {
10
+ const cap = Math.max(1, capacity | 0);
11
+ this.#vals = new F64(cap);
12
+ this.#live = new U8(cap);
13
+ }
14
+
15
+ get size(): N {
16
+ return this.#size + (this.#odd === null ? 0 : this.#odd.size);
17
+ }
18
+ get capacity(): N {
19
+ return this.#vals.length;
20
+ }
21
+ /** live backing buffer: value of pid p is buffer[p], valid where live[p] === 1 */
22
+ get buffer(): F64 {
23
+ return this.#vals;
24
+ }
25
+ get live(): U8 {
26
+ return this.#live;
27
+ }
28
+
29
+ // ---- Map API ----------------------------------------------------------
30
+ get(k: number): V | UD {
31
+ const l = this.#live;
32
+ if (typeof k === "number" && k >= 0 && k < l.length && l[k] === 1) return this.#vals[k] as V;
33
+ if (typeof k !== "number" || k < 0) return this.#odd === null ? undefined : this.#odd.get(k as Key);
34
+ return undefined;
35
+ }
36
+ has(k: number): boolean {
37
+ const l = this.#live;
38
+ if (typeof k === "number" && k >= 0 && k < l.length) return l[k] === 1;
39
+ return this.#odd !== null && this.#odd.has(k as Key);
40
+ }
41
+ set(k: number, v: V): this {
42
+ if (typeof v === "number" && typeof k === "number" && k >= 0) {
43
+ if (k >= this.#live.length) this.#grow(k);
44
+ if (this.#live[k] === 0) {
45
+ this.#live[k] = 1;
46
+ this.#size++;
47
+ }
48
+ this.#vals[k] = v;
49
+ return this;
50
+ }
51
+ (this.#odd ??= new Map()).set(k as Key, v);
52
+ return this;
53
+ }
54
+ delete(k: number): B {
55
+ const l = this.#live;
56
+ if (typeof k === "number" && k >= 0) {
57
+ if (k >= l.length || l[k] === 0) return false;
58
+ l[k] = 0;
59
+ this.#size--;
60
+ return true;
61
+ }
62
+ return this.#odd !== null && this.#odd.delete(k as Key);
63
+ }
64
+ clear(): V {
65
+ this.#live.fill(0);
66
+ this.#size = 0;
67
+ this.#odd?.clear();
68
+ }
69
+ *keys(): MapIterator<N> {
70
+ const l = this.#live,
71
+ n = l.length;
72
+ for (let k = 0; k < n; k++) if (l[k] === 1) yield k;
73
+ if (this.#odd !== null) yield* this.#odd.keys() as MapIterator<N>;
74
+ }
75
+ *values(): MapIterator<V> {
76
+ const l = this.#live,
77
+ n = this.#size;
78
+ if (this.#odd?.size) {
79
+ const odd = [...this.#odd.values()];
80
+ let seen = 0;
81
+ for (let k = 0; k < l.length && seen < n; k++)
82
+ if (l[k] === 1) {
83
+ seen++;
84
+ yield this.#vals[k] as V;
85
+ }
86
+ yield* odd;
87
+ return;
88
+ }
89
+ let seen = 0;
90
+ for (let k = 0; k < l.length && seen < n; k++)
91
+ if (l[k] === 1) {
92
+ seen++;
93
+ yield this.#vals[k] as V;
94
+ }
95
+ }
96
+ *entries(): MapIterator<[N, V]> {
97
+ const l = this.#live;
98
+ for (let k = 0; k < l.length; k++) if (l[k] === 1) yield [k, this.#vals[k] as V];
99
+ if (this.#odd !== null) yield* this.#odd.entries() as MapIterator<[N, V]>;
100
+ }
101
+ [Symbol.iterator](): MapIterator<[N, V]> {
102
+ return this.entries();
103
+ }
104
+ forEach(fn: (v: V, k: N, m: M<N, V>) => V, thisArg?: U): V {
105
+ const l = this.#live;
106
+ for (let k = 0; k < l.length; k++) if (l[k] === 1) fn.call(thisArg, this.#vals[k] as V, k, this as U as Map<N, V>);
107
+ this.#odd?.forEach((v, k) => fn.call(thisArg, v, k as N, this as U as Map<N, V>));
108
+ }
109
+
110
+ // ---- binary lane -------------------------------------------------------
111
+ /** read by pid — no typeof guard: pids are numbers */
112
+ g(pid: N): V | UD {
113
+ return this.#live[pid] === 1 ? (this.#vals[pid] as V) : undefined;
114
+ }
115
+ /** write by pid */
116
+ s(v: N, pid: N): this {
117
+ if (pid >= this.#live.length) this.#grow(pid);
118
+ if (this.#live[pid] === 0) {
119
+ this.#live[pid] = 1;
120
+ this.#size++;
121
+ }
122
+ this.#vals[pid] = v;
123
+ return this;
124
+ }
125
+ getOr(pid: N, dflt = 0): N {
126
+ return this.#live[pid] === 1 ? this.#vals[pid] : dflt;
127
+ }
128
+ /** pid += delta, returns the new value */
129
+ add(pid: N, delta: N): N {
130
+ if (pid >= this.#live.length) this.#grow(pid);
131
+ if (this.#live[pid] === 0) {
132
+ this.#live[pid] = 1;
133
+ this.#size++;
134
+ }
135
+ return (this.#vals[pid] += delta);
136
+ }
137
+ in(pid: N): B {
138
+ return this.#live[pid] === 1;
139
+ }
140
+ /** walk live pids without allocating an iterator */
141
+ forEachRaw(fn: (pid: N, value: N) => V): V {
142
+ const l = this.#live;
143
+ for (let k = 0; k < l.length; k++) if (l[k] === 1) fn(k, this.#vals[k]);
144
+ }
145
+ /** values for a list of pids, NaN where absent */
146
+ gather(pids: ArrayLike<N>, out = new Float64Array(pids.length)): Float64Array {
147
+ for (let i = 0; i < pids.length; i++) {
148
+ const p = pids[i];
149
+ out[i] = this.#live[p] === 1 ? this.#vals[p] : NaN;
150
+ }
151
+ return out;
152
+ }
153
+ #grow(need: N): V {
154
+ let cap = this.#live.length;
155
+ while (cap <= need) cap *= 2;
156
+ const v = new Float64Array(cap);
157
+ v.set(this.#vals);
158
+ this.#vals = v;
159
+ const l = new Uint8Array(cap);
160
+ l.set(this.#live);
161
+ this.#live = l;
162
+ }
163
+ }
164
+
165
+ export interface M2D<K, V> extends M2DI<K, V> {}
166
+
167
+ globalThis.M2D = M2DI;