@daneren2005/shared-memory-objects 1.4.0 → 1.5.1

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.
Files changed (54) hide show
  1. package/README.md +137 -8
  2. package/dist/allocated-memory.d.ts +7 -2
  3. package/dist/allocated-memory.js +11 -4
  4. package/dist/allocated-memory.js.map +1 -1
  5. package/dist/constant-string.js +2 -2
  6. package/dist/constant-string.js.map +1 -1
  7. package/dist/local-pool.js +2 -2
  8. package/dist/local-pool.js.map +1 -1
  9. package/dist/lock/read-write-lock.js +25 -11
  10. package/dist/lock/read-write-lock.js.map +1 -1
  11. package/dist/memory-buffer.d.ts +11 -5
  12. package/dist/memory-buffer.js +70 -74
  13. package/dist/memory-buffer.js.map +1 -1
  14. package/dist/memory-heap.d.ts +3 -0
  15. package/dist/memory-heap.js +37 -29
  16. package/dist/memory-heap.js.map +1 -1
  17. package/dist/serialize-object.js +1 -1
  18. package/dist/serialize-object.js.map +1 -1
  19. package/dist/shared-list.d.ts +2 -0
  20. package/dist/shared-list.js +32 -29
  21. package/dist/shared-list.js.map +1 -1
  22. package/dist/shared-map.d.ts +11 -3
  23. package/dist/shared-map.js +93 -47
  24. package/dist/shared-map.js.map +1 -1
  25. package/dist/shared-pointer-list.js +3 -3
  26. package/dist/shared-pointer-list.js.map +1 -1
  27. package/dist/shared-pool.d.ts +11 -2
  28. package/dist/shared-pool.js +58 -26
  29. package/dist/shared-pool.js.map +1 -1
  30. package/dist/shared-stack.js +19 -8
  31. package/dist/shared-stack.js.map +1 -1
  32. package/dist/shared-string.js +3 -3
  33. package/dist/shared-string.js.map +1 -1
  34. package/dist/shared-vector.js +10 -7
  35. package/dist/shared-vector.js.map +1 -1
  36. package/dist/spatial/shared-quadtree.d.ts +67 -0
  37. package/dist/spatial/shared-quadtree.js +361 -0
  38. package/dist/spatial/shared-quadtree.js.map +1 -0
  39. package/dist/spatial/shared-spatial-grid.d.ts +78 -0
  40. package/dist/spatial/shared-spatial-grid.js +426 -0
  41. package/dist/spatial/shared-spatial-grid.js.map +1 -0
  42. package/dist/spatial/shared-spatial-map.d.ts +87 -0
  43. package/dist/spatial/shared-spatial-map.js +555 -0
  44. package/dist/spatial/shared-spatial-map.js.map +1 -0
  45. package/dist/spatial/spatial-entity.d.ts +11 -0
  46. package/dist/spatial/spatial-entity.js +19 -0
  47. package/dist/spatial/spatial-entity.js.map +1 -0
  48. package/dist/spatial/spatial-neighbors.d.ts +21 -0
  49. package/dist/spatial/spatial-neighbors.js +55 -0
  50. package/dist/spatial/spatial-neighbors.js.map +1 -0
  51. package/dist/utils/pointer.d.ts +8 -6
  52. package/dist/utils/pointer.js +22 -17
  53. package/dist/utils/pointer.js.map +1 -1
  54. package/package.json +6 -1
@@ -4,6 +4,8 @@ import { type NumericArray, type TypedArrayConstructor } from './utils/array-typ
4
4
  export default class SharedList<T extends NumericArray = Uint32Array> implements Iterable<SharedListIterable<T>> {
5
5
  static readonly ALLOCATE_COUNT = 4;
6
6
  private memory;
7
+ private positionBits;
8
+ private positionMask;
7
9
  private firstBlock;
8
10
  private uint16Array;
9
11
  private cachedType;
@@ -2,9 +2,11 @@ import { loadPointer as e, loadRawPointer as t, replaceRawPointer as n, storeRaw
2
2
  import i from "./allocated-memory.js";
3
3
  import { getArrayTypeCode as a, getByteMultipler as o, isBigIntType as s, isFloatType as c, makeArrayView as l } from "./utils/array-type.js";
4
4
  //#region src/shared-list.ts
5
- var u = 4, d = 2, f = d * Uint32Array.BYTES_PER_ELEMENT, p = 0, m = 1, h = 2, g = 0, _ = 1, v = 4095, y = class {
5
+ var u = 4, d = 2, f = d * Uint32Array.BYTES_PER_ELEMENT, p = 0, m = 1, h = 2, g = 0, _ = 1, v = class {
6
6
  static ALLOCATE_COUNT = u;
7
7
  memory;
8
+ positionBits;
9
+ positionMask;
8
10
  firstBlock;
9
11
  uint16Array;
10
12
  cachedType;
@@ -18,13 +20,13 @@ var u = 4, d = 2, f = d * Uint32Array.BYTES_PER_ELEMENT, p = 0, m = 1, h = 2, g
18
20
  return Atomics.load(this.firstBlock.data, h);
19
21
  }
20
22
  get usedMemory() {
21
- let t = this.firstBlock.usedMemory, { bufferPosition: n, bufferByteOffset: r } = e(this.firstBlock.data, p);
23
+ let t = this.firstBlock.usedMemory, { bufferPosition: n, bufferByteOffset: r } = e(this.firstBlock.data, p, this.positionBits);
22
24
  for (; r;) {
23
25
  let a = new i(this.memory, {
24
26
  bufferPosition: n,
25
27
  bufferByteOffset: r
26
28
  });
27
- t += a.usedMemory, {bufferPosition: n, bufferByteOffset: r} = e(a.data, g);
29
+ t += a.usedMemory, {bufferPosition: n, bufferByteOffset: r} = e(a.data, g, this.positionBits);
28
30
  }
29
31
  return t;
30
32
  }
@@ -41,7 +43,7 @@ var u = 4, d = 2, f = d * Uint32Array.BYTES_PER_ELEMENT, p = 0, m = 1, h = 2, g
41
43
  Atomics.store(this.uint16Array, 1, e);
42
44
  }
43
45
  constructor(e, t) {
44
- this.memory = e, t && "firstBlock" in t ? (this.firstBlock = new i(e, t.firstBlock), this.uint16Array = new Uint16Array(this.firstBlock.data.buffer, this.firstBlock.bufferByteOffset + 3 * Uint32Array.BYTES_PER_ELEMENT, 2)) : (this.firstBlock = t && t.initWithBlock ? new i(e, t.initWithBlock) : e.allocUI32(u), this.uint16Array = new Uint16Array(this.firstBlock.data.buffer, this.firstBlock.bufferByteOffset + 3 * Uint32Array.BYTES_PER_ELEMENT, 2), r(this.firstBlock.data, m, this.firstBlock.pointer), this.type = a(t?.type ?? Uint32Array), this.dataLength = t?.dataLength ?? 1), this.cachedType = Atomics.load(this.uint16Array, 0), this.cachedDataLength = Math.max(1, Atomics.load(this.uint16Array, 1)), this.cachedByteMultipler = o(this.cachedType), this.cachedIsBigInt = s(this.cachedType), this.cachedIsFloat = c(this.cachedType);
46
+ this.memory = e, this.positionBits = e.positionBits, this.positionMask = (1 << this.positionBits) - 1, t && "firstBlock" in t ? (this.firstBlock = new i(e, t.firstBlock), this.uint16Array = new Uint16Array(this.firstBlock.data.buffer, this.firstBlock.bufferByteOffset + 3 * Uint32Array.BYTES_PER_ELEMENT, 2)) : (this.firstBlock = t && t.initWithBlock ? new i(e, t.initWithBlock) : e.allocUI32(u), this.uint16Array = new Uint16Array(this.firstBlock.data.buffer, this.firstBlock.bufferByteOffset + 3 * Uint32Array.BYTES_PER_ELEMENT, 2), r(this.firstBlock.data, m, this.firstBlock.pointer), this.type = a(t?.type ?? Uint32Array), this.dataLength = t?.dataLength ?? 1), this.cachedType = Atomics.load(this.uint16Array, 0), this.cachedDataLength = Math.max(1, Atomics.load(this.uint16Array, 1)), this.cachedByteMultipler = o(this.cachedType), this.cachedIsBigInt = s(this.cachedType), this.cachedIsFloat = c(this.cachedType);
45
47
  }
46
48
  insert(e) {
47
49
  Array.isArray(e) || (e = [e]);
@@ -49,10 +51,11 @@ var u = 4, d = 2, f = d * Uint32Array.BYTES_PER_ELEMENT, p = 0, m = 1, h = 2, g
49
51
  if (e.length > r) throw Error(`Can't insert ${e.length} array into shared list of ${r} dataLength`);
50
52
  let i = this.memory.allocUI32(d + r * this.cachedByteMultipler), a = this.getDataBlock(i.data.buffer, i.data.byteOffset), o = i.pointer, s = a;
51
53
  for (let t = 0; t < e.length; t++) this.cachedIsFloat ? s[t] = e[t] : (this.cachedIsBigInt, Atomics.store(a, t, e[t]));
54
+ let c = this.positionBits, l = this.positionMask;
52
55
  for (;;) {
53
- let e = t(this.firstBlock.data, m), r = this.getBufferView(e & v);
56
+ let e = t(this.firstBlock.data, m), r = this.getBufferView(e & l);
54
57
  if (!r) continue;
55
- let i = (e >>> 12 >>> 2) + g, a = Atomics.load(r, i);
58
+ let i = (e >>> c >>> 2) + g, a = Atomics.load(r, i);
56
59
  if (e === t(this.firstBlock.data, m)) {
57
60
  if (a === 0) {
58
61
  if (Atomics.compareExchange(r, i, 0, o) === 0) {
@@ -81,12 +84,12 @@ var u = 4, d = 2, f = d * Uint32Array.BYTES_PER_ELEMENT, p = 0, m = 1, h = 2, g
81
84
  }) : this.deleteMatch((t) => t[0] === e);
82
85
  }
83
86
  compact() {
84
- let e = this.firstBlock.pointer, n = this.firstBlock.data, i = g, a = t(n, i);
85
- for (; a;) {
86
- let t = a & v, o = this.memory.buffers[t];
87
- if (!o) break;
88
- let s = this.getBufferView(t), c = a >>> 12, l = c >>> 2, u = Atomics.load(s, l + g);
89
- Atomics.load(s, l + _) ? (r(n, i, u), o.free(c)) : (e = a, n = s, i = l + g), a = u;
87
+ let e = this.firstBlock.pointer, n = this.firstBlock.data, i = g, a = this.positionBits, o = this.positionMask, s = t(n, i);
88
+ for (; s;) {
89
+ let t = s & o, c = this.memory.buffers[t];
90
+ if (!c) break;
91
+ let l = this.getBufferView(t), u = s >>> a, d = u >>> 2, f = Atomics.load(l, d + g);
92
+ Atomics.load(l, d + _) ? (r(n, i, f), c.free(u)) : (e = s, n = l, i = d + g), s = f;
90
93
  }
91
94
  r(this.firstBlock.data, m, e);
92
95
  }
@@ -98,27 +101,27 @@ var u = 4, d = 2, f = d * Uint32Array.BYTES_PER_ELEMENT, p = 0, m = 1, h = 2, g
98
101
  }
99
102
  if (!e) return;
100
103
  r(this.firstBlock.data, m, this.firstBlock.pointer);
101
- let a = 0, o = e;
102
- for (; o;) {
103
- let e = o & v, t = this.memory.buffers[e];
104
+ let a = 0, o = this.positionBits, s = this.positionMask, c = e;
105
+ for (; c;) {
106
+ let e = c & s, t = this.memory.buffers[e];
104
107
  if (!t) break;
105
- let n = this.getBufferView(e), r = o >>> 12, i = r >>> 2;
106
- o = Atomics.load(n, i + g), Atomics.load(n, i + _) || (a++, this.onDelete && this.onDelete(this.getDataBlock(n.buffer, r))), t.free(r);
108
+ let n = this.getBufferView(e), r = c >>> o, i = r >>> 2;
109
+ c = Atomics.load(n, i + g), Atomics.load(n, i + _) || (a++, this.onDelete && this.onDelete(this.getDataBlock(n.buffer, r))), t.free(r);
107
110
  }
108
111
  Atomics.sub(this.firstBlock.data, h, a);
109
112
  }
110
113
  *[Symbol.iterator]() {
111
- let e = 0, n = t(this.firstBlock.data, p);
112
- for (; n;) {
113
- let t = n & v, r = this.getBufferView(t);
114
- if (!r) return;
115
- let i = n >>> 12, a = i >>> 2;
116
- if (n = Atomics.load(r, a + g), Atomics.load(r, a + _)) continue;
117
- let o = r.buffer, s = a + _;
114
+ let e = 0, n = this.positionBits, r = this.positionMask, i = t(this.firstBlock.data, p);
115
+ for (; i;) {
116
+ let t = i & r, a = this.getBufferView(t);
117
+ if (!a) return;
118
+ let o = i >>> n, s = o >>> 2;
119
+ if (i = Atomics.load(a, s + g), Atomics.load(a, s + _)) continue;
120
+ let c = a.buffer, l = s + _;
118
121
  yield {
119
- data: this.getDataBlock(o, i),
122
+ data: this.getDataBlock(c, o),
120
123
  index: e,
121
- deleteCurrent: () => Atomics.compareExchange(r, s, 0, 1) === 0 && (Atomics.sub(this.firstBlock.data, h, 1), this.onDelete && this.onDelete(this.getDataBlock(o, i)), !0)
124
+ deleteCurrent: () => Atomics.compareExchange(a, l, 0, 1) === 0 && (Atomics.sub(this.firstBlock.data, h, 1), this.onDelete && this.onDelete(this.getDataBlock(c, o)), !0)
122
125
  }, e++;
123
126
  }
124
127
  }
@@ -139,18 +142,18 @@ var u = 4, d = 2, f = d * Uint32Array.BYTES_PER_ELEMENT, p = 0, m = 1, h = 2, g
139
142
  return l(this.cachedType, e, n, this.cachedDataLength);
140
143
  }
141
144
  free() {
142
- let { bufferPosition: t, bufferByteOffset: n } = e(this.firstBlock.data, p);
145
+ let { bufferPosition: t, bufferByteOffset: n } = e(this.firstBlock.data, p, this.positionBits);
143
146
  for (; n;) {
144
147
  let r = new i(this.memory, {
145
148
  bufferPosition: t,
146
149
  bufferByteOffset: n
147
150
  });
148
- ({bufferPosition: t, bufferByteOffset: n} = e(r.data, g)), this.onDelete && !Atomics.load(r.data, _) && this.onDelete(this.getDataBlock(r.data.buffer, r.data.byteOffset)), r.free();
151
+ ({bufferPosition: t, bufferByteOffset: n} = e(r.data, g, this.positionBits)), this.onDelete && !Atomics.load(r.data, _) && this.onDelete(this.getDataBlock(r.data.buffer, r.data.byteOffset)), r.free();
149
152
  }
150
153
  this.firstBlock.free();
151
154
  }
152
155
  };
153
156
  //#endregion
154
- export { y as default };
157
+ export { v as default };
155
158
 
156
159
  //# sourceMappingURL=shared-list.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"shared-list.js","names":[],"sources":["../src/shared-list.ts"],"sourcesContent":["import type { SharedAllocatedMemory } from './allocated-memory';\nimport AllocatedMemory from './allocated-memory';\nimport type MemoryHeap from './memory-heap';\nimport { loadPointer, loadRawPointer, POSITION_BIT_COUNT, replaceRawPointer, storeRawPointer } from './utils/pointer';\nimport { getArrayTypeCode, getByteMultipler, isBigIntType, isFloatType, makeArrayView, type NumericArray, type NumericArrayIO, type TypedArrayConstructor } from './utils/array-type';\n\n// Lock-free singly-linked list. firstBlock doubles as an immortal sentinel node whose next pointer (index 0) is the head.\n// insert appends with a Michael-Scott CAS enqueue; delete only tombstones a node (never unlinks/frees). Memory is\n// reclaimed exclusively by the quiescent compact()/clear()/free() passes. That deferral is what makes the CAS loops\n// safe without hazard pointers: a live node's address is never handed back to the allocator, so no append can ever\n// observe a recycled address (ABA) while another thread is mid-traversal.\nconst FIRST_BLOCK_RECORD_KEEPING_COUNT = 4;\nconst DATA_BLOCK_RECORD_KEEPING_COUNT = 2;\nconst DATA_BLOCK_BYTE_OFFSET = DATA_BLOCK_RECORD_KEEPING_COUNT * Uint32Array.BYTES_PER_ELEMENT;\nconst HEAD_INDEX = 0;\nconst TAIL_INDEX = 1;\nconst LENGTH_INDEX = 2;\nconst NODE_NEXT_INDEX = 0;\nconst NODE_DELETED_INDEX = 1;\n// A pointer packs the buffer position in its low bits and the byte offset in the high bits (see utils/pointer)\nconst POSITION_MASK = (1 << POSITION_BIT_COUNT) - 1;\nexport default class SharedList<T extends NumericArray = Uint32Array> implements Iterable<SharedListIterable<T>> {\n\tstatic readonly ALLOCATE_COUNT = FIRST_BLOCK_RECORD_KEEPING_COUNT;\n\n\tprivate memory: MemoryHeap;\n\t/* First block (also the sentinel node - index 0 is its next pointer = list head)\n\t\t32 index 0\n\t\tuint16 0 - head buffer position | sentinel.next\n\t\tuint16 1 - head buffer index\n\t\t32 index 1\n\t\tuint16 2 - tail hint buffer position\n\t\tuint16 3 - tail hint buffer index\n\t\t32 index 2\n\t\tuint32 4 - length\n\t\t32 index 3\n\t\tuint16 6 - type\n\t\tuint16 7 - data length (defaults to 1 number per data)\n\t*/\n\t/* Other blocks\n\t\t32 index 0 - next buffer pointer\n\t\t32 index 1 - deleted flag (0 = live, 1 = tombstoned)\n\t\t32 index 2 => data\n\t*/\n\tprivate firstBlock: AllocatedMemory;\n\tprivate uint16Array: Uint16Array;\n\t// type and dataLength are written once at init and never change, so cache them instead of atomic-loading per node\n\tprivate cachedType: number;\n\tprivate cachedDataLength: number;\n\t// u32 units per element: 1 for 32-bit types, 2 for 64-bit (float64/int64/uint64). Node data blocks scale by this.\n\tprivate cachedByteMultipler = 1;\n\t// Whether the value type is a BigInt view (int64/uint64), so insert stores bigint via Atomics; cached to keep the\n\t// per-insert branch off an instanceof check\n\tprivate cachedIsBigInt = false;\n\t// Float views can't be published with Atomics, so insert falls back to a plain store for them\n\tprivate cachedIsFloat = false;\n\t// One full-buffer Uint32Array per buffer position, so traversal indexes shared memory directly instead of\n\t// allocating a fresh view (and a getPointer object) for every node it touches\n\tprivate bufferViews: Array<Uint32Array> = [];\n\tonDelete?: (data: T) => void;\n\n\tget length(): number {\n\t\treturn Atomics.load(this.firstBlock.data, LENGTH_INDEX);\n\t}\n\n\t// Own internal memory plus every allocated node still linked in the chain (tombstoned nodes included, since they stay\n\t// allocated until a compact()/clear() reclaims them).\n\tget usedMemory(): number {\n\t\tlet total = this.firstBlock.usedMemory;\n\t\tlet { bufferPosition, bufferByteOffset } = loadPointer(this.firstBlock.data, HEAD_INDEX);\n\t\twhile(bufferByteOffset) {\n\t\t\tlet node = new AllocatedMemory(this.memory, { bufferPosition, bufferByteOffset });\n\t\t\ttotal += node.usedMemory;\n\t\t\t({ bufferPosition, bufferByteOffset } = loadPointer(node.data, NODE_NEXT_INDEX));\n\t\t}\n\t\treturn total;\n\t}\n\n\tget type(): number {\n\t\treturn this.cachedType;\n\t}\n\tprivate set type(value: number) {\n\t\tAtomics.store(this.uint16Array, 0, value);\n\t}\n\tget dataLength(): number {\n\t\treturn this.cachedDataLength;\n\t}\n\tprivate set dataLength(value: number) {\n\t\tAtomics.store(this.uint16Array, 1, value);\n\t}\n\n\tconstructor(memory: MemoryHeap, config?: SharedListConfig<T> | SharedListMemory) {\n\t\tthis.memory = memory;\n\n\t\tif(config && 'firstBlock' in config) {\n\t\t\t// TODO: How to handle referencing memory we don't have access to yet because buffer not synced from worker?\n\t\t\tthis.firstBlock = new AllocatedMemory(memory, config.firstBlock);\n\t\t\tthis.uint16Array = new Uint16Array(this.firstBlock.data.buffer, this.firstBlock.bufferByteOffset + (LENGTH_INDEX + 1) * Uint32Array.BYTES_PER_ELEMENT, 2);\n\t\t} else {\n\t\t\tif(config && config.initWithBlock) {\n\t\t\t\tthis.firstBlock = new AllocatedMemory(memory, config.initWithBlock);\n\t\t\t} else {\n\t\t\t\tthis.firstBlock = memory.allocUI32(FIRST_BLOCK_RECORD_KEEPING_COUNT);\n\t\t\t}\n\t\t\tthis.uint16Array = new Uint16Array(this.firstBlock.data.buffer, this.firstBlock.bufferByteOffset + (LENGTH_INDEX + 1) * Uint32Array.BYTES_PER_ELEMENT, 2);\n\n\t\t\t// Empty list: the tail hint points at the sentinel (firstBlock) so insert never special-cases the first node\n\t\t\tstoreRawPointer(this.firstBlock.data, TAIL_INDEX, this.firstBlock.pointer);\n\n\t\t\tthis.type = getArrayTypeCode(config?.type ?? Uint32Array);\n\t\t\tthis.dataLength = config?.dataLength ?? 1;\n\t\t}\n\n\t\tthis.cachedType = Atomics.load(this.uint16Array, 0);\n\t\t// dataLength defaults to at least one even when initialized from raw memory that was never explicitly set\n\t\tthis.cachedDataLength = Math.max(1, Atomics.load(this.uint16Array, 1));\n\t\tthis.cachedByteMultipler = getByteMultipler(this.cachedType);\n\t\tthis.cachedIsBigInt = isBigIntType(this.cachedType);\n\t\tthis.cachedIsFloat = isFloatType(this.cachedType);\n\t}\n\n\tinsert(values: T[number] | Array<T[number]>) {\n\t\tif(!Array.isArray(values)) {\n\t\t\tvalues = [values];\n\t\t}\n\n\t\tlet dataLength = this.cachedDataLength;\n\t\tif(values.length > dataLength) {\n\t\t\tthrow new Error(`Can't insert ${values.length} array into shared list of ${dataLength} dataLength`);\n\t\t}\n\t\tlet newBlock = this.memory.allocUI32(DATA_BLOCK_RECORD_KEEPING_COUNT + dataLength * this.cachedByteMultipler);\n\t\tlet newData = this.getDataBlock(newBlock.data.buffer, newBlock.data.byteOffset);\n\t\tlet newBlockPointer = newBlock.pointer;\n\n\t\tlet io = newData as NumericArrayIO;\n\t\tfor(let i = 0; i < values.length; i++) {\n\t\t\tif(this.cachedIsFloat) {\n\t\t\t\t// Float views don't support Atomics; the enqueue CAS below still provides the cross-thread publish\n\t\t\t\tio[i] = values[i];\n\t\t\t} else if(this.cachedIsBigInt) {\n\t\t\t\tAtomics.store(newData as BigInt64Array, i, values[i] as bigint);\n\t\t\t} else {\n\t\t\t\tAtomics.store(newData as Int32Array, i, values[i] as number);\n\t\t\t}\n\t\t}\n\n\t\t// Michael-Scott enqueue: link the node onto tail.next BEFORE swinging the tail hint, so a committed node is never\n\t\t// invisible to a concurrent traversal. The tail hint may lag by one node; enqueuers help advance it.\n\t\t// eslint-disable-next-line no-constant-condition\n\t\twhile(true) {\n\t\t\tlet tailPointer = loadRawPointer(this.firstBlock.data, TAIL_INDEX);\n\t\t\tlet view = this.getBufferView(tailPointer & POSITION_MASK);\n\t\t\t// Tail node lives in a buffer this thread hasn't synced yet - retry until it appears\n\t\t\tif(!view) {\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tlet tailNextIndex = ((tailPointer >>> POSITION_BIT_COUNT) >>> 2) + NODE_NEXT_INDEX;\n\t\t\tlet nextPointer = Atomics.load(view, tailNextIndex);\n\n\t\t\t// Re-check the hint hasn't moved out from under us before acting on what we read\n\t\t\tif(tailPointer !== loadRawPointer(this.firstBlock.data, TAIL_INDEX)) {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tif(nextPointer === 0) {\n\t\t\t\tif(Atomics.compareExchange(view, tailNextIndex, 0, newBlockPointer) === 0) {\n\t\t\t\t\t// Linked. Try to swing the hint forward - failure is harmless, the next enqueuer will help.\n\t\t\t\t\treplaceRawPointer(this.firstBlock.data, TAIL_INDEX, newBlockPointer, tailPointer);\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\t// Tail hint is lagging behind a linked node - help it catch up, then retry\n\t\t\t\treplaceRawPointer(this.firstBlock.data, TAIL_INDEX, nextPointer, tailPointer);\n\t\t\t}\n\t\t}\n\n\t\tAtomics.add(this.firstBlock.data, LENGTH_INDEX, 1);\n\t}\n\n\tdeleteMatch(callback: (values: T, index: number) => boolean): boolean {\n\t\tfor(let { data, index, deleteCurrent } of this) {\n\t\t\tif(callback(data, index)) {\n\t\t\t\tif(deleteCurrent()) {\n\t\t\t\t\treturn true;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\treturn false;\n\t}\n\tdeleteIndex(deleteIndex: number): boolean {\n\t\tif(deleteIndex >= this.length || deleteIndex < 0) {\n\t\t\treturn false;\n\t\t}\n\n\t\t// Compare on index only so the payload view is never materialized for an index-based delete\n\t\tfor(let item of this) {\n\t\t\tif(item.index === deleteIndex) {\n\t\t\t\tif(item.deleteCurrent()) {\n\t\t\t\t\treturn true;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\treturn false;\n\t}\n\tdeleteValue(deleteValues: T[number] | Array<T[number]>) {\n\t\tif(!Array.isArray(deleteValues)) {\n\t\t\treturn this.deleteMatch(values => values[0] === deleteValues);\n\t\t} else {\n\t\t\treturn this.deleteMatch(values => {\n\t\t\t\tif(values.length !== deleteValues.length) {\n\t\t\t\t\treturn false;\n\t\t\t\t} else {\n\t\t\t\t\tfor(let i = 0; i < values.length; i++) {\n\t\t\t\t\t\tif(values[i] !== deleteValues[i]) {\n\t\t\t\t\t\t\treturn false;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t\treturn true;\n\t\t\t\t}\n\t\t\t});\n\t\t}\n\t}\n\n\t// Reclaims tombstoned nodes: physically unlinks and frees them. Not safe to run concurrently with insert/delete.\n\tcompact() {\n\t\tlet prevPointer = this.firstBlock.pointer;\n\t\tlet prevView = this.firstBlock.data;\n\t\tlet prevNextIndex = NODE_NEXT_INDEX;\n\t\tlet nextPointer = loadRawPointer(prevView, prevNextIndex);\n\t\twhile(nextPointer) {\n\t\t\tlet position = nextPointer & POSITION_MASK;\n\t\t\tlet memPool = this.memory.buffers[position];\n\t\t\tif(!memPool) {\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tlet view = this.getBufferView(position)!;\n\t\t\tlet byteOffset = nextPointer >>> POSITION_BIT_COUNT;\n\t\t\tlet nodeIndex = byteOffset >>> 2;\n\t\t\tlet followingPointer = Atomics.load(view, nodeIndex + NODE_NEXT_INDEX);\n\n\t\t\tif(Atomics.load(view, nodeIndex + NODE_DELETED_INDEX)) {\n\t\t\t\t// Skip over the tombstone and free it. onDelete already fired when the node was logically deleted.\n\t\t\t\tstoreRawPointer(prevView, prevNextIndex, followingPointer);\n\t\t\t\tmemPool.free(byteOffset);\n\t\t\t} else {\n\t\t\t\tprevPointer = nextPointer;\n\t\t\t\tprevView = view;\n\t\t\t\tprevNextIndex = nodeIndex + NODE_NEXT_INDEX;\n\t\t\t}\n\n\t\t\tnextPointer = followingPointer;\n\t\t}\n\n\t\t// The last surviving node (or the sentinel when everything is gone) is the new tail\n\t\tstoreRawPointer(this.firstBlock.data, TAIL_INDEX, prevPointer);\n\t}\n\n\tclear() {\n\t\t// Detach the whole chain, then free it. Not safe to run concurrently with insert/delete.\n\t\tlet firstBlockPointer;\n\t\tlet updateWorked = false;\n\t\twhile(!updateWorked) {\n\t\t\tfirstBlockPointer = loadRawPointer(this.firstBlock.data, HEAD_INDEX);\n\t\t\t// Already cleared\n\t\t\tif(!firstBlockPointer) {\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tupdateWorked = replaceRawPointer(this.firstBlock.data, HEAD_INDEX, 0, firstBlockPointer);\n\t\t}\n\t\t// Shouldn't be possible to hit: making Typescript happy\n\t\tif(!firstBlockPointer) {\n\t\t\treturn;\n\t\t}\n\n\t\t// Reset the tail hint back to the sentinel\n\t\tstoreRawPointer(this.firstBlock.data, TAIL_INDEX, this.firstBlock.pointer);\n\n\t\tlet liveItems = 0;\n\t\tlet nextBlockPointer = firstBlockPointer;\n\t\twhile(nextBlockPointer) {\n\t\t\tlet position = nextBlockPointer & POSITION_MASK;\n\t\t\tlet memPool = this.memory.buffers[position];\n\t\t\t// Short circuit iterations if we can't access memory\n\t\t\tif(!memPool) {\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\tlet view = this.getBufferView(position)!;\n\t\t\tlet byteOffset = nextBlockPointer >>> POSITION_BIT_COUNT;\n\t\t\tlet nodeIndex = byteOffset >>> 2;\n\t\t\tnextBlockPointer = Atomics.load(view, nodeIndex + NODE_NEXT_INDEX);\n\n\t\t\tif(!Atomics.load(view, nodeIndex + NODE_DELETED_INDEX)) {\n\t\t\t\tliveItems++;\n\t\t\t\tif(this.onDelete) {\n\t\t\t\t\tthis.onDelete(this.getDataBlock(view.buffer, byteOffset));\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tmemPool.free(byteOffset);\n\t\t}\n\n\t\t// Only subtract the live nodes we detached so a concurrent insert's increment stays accurate\n\t\tAtomics.sub(this.firstBlock.data, LENGTH_INDEX, liveItems);\n\t}\n\n\t*[Symbol.iterator]() {\n\t\tlet currentIndex = 0;\n\t\tlet nextPointer = loadRawPointer(this.firstBlock.data, HEAD_INDEX);\n\t\twhile(nextPointer) {\n\t\t\tlet position = nextPointer & POSITION_MASK;\n\t\t\tlet view = this.getBufferView(position);\n\t\t\t// Short circuit iterations if we can't access memory\n\t\t\tif(!view) {\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tlet byteOffset = nextPointer >>> POSITION_BIT_COUNT;\n\t\t\tlet nodeIndex = byteOffset >>> 2;\n\t\t\tnextPointer = Atomics.load(view, nodeIndex + NODE_NEXT_INDEX);\n\n\t\t\t// Skip tombstoned nodes - they stay linked until a quiescent compact()/clear()\n\t\t\tif(Atomics.load(view, nodeIndex + NODE_DELETED_INDEX)) {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tlet buffer = view.buffer;\n\t\t\tlet deletedIndex = nodeIndex + NODE_DELETED_INDEX;\n\t\t\t// Plain object with an eager data view: V8 keeps this monomorphic and fast. A lazy `get data()` accessor is far\n\t\t\t// slower to allocate than the payload view it would save, so pay for the view up front.\n\t\t\tyield {\n\t\t\t\tdata: this.getDataBlock(buffer, byteOffset),\n\t\t\t\tindex: currentIndex,\n\t\t\t\t// Logical delete: tombstone the node and drop length. Returns false if another thread beat us to it.\n\t\t\t\tdeleteCurrent: (): boolean => {\n\t\t\t\t\tif(Atomics.compareExchange(view, deletedIndex, 0, 1) === 0) {\n\t\t\t\t\t\tAtomics.sub(this.firstBlock.data, LENGTH_INDEX, 1);\n\t\t\t\t\t\tif(this.onDelete) {\n\t\t\t\t\t\t\tthis.onDelete(this.getDataBlock(buffer, byteOffset));\n\t\t\t\t\t\t}\n\t\t\t\t\t\treturn true;\n\t\t\t\t\t}\n\n\t\t\t\t\treturn false;\n\t\t\t\t},\n\t\t\t};\n\n\t\t\tcurrentIndex++;\n\t\t}\n\t}\n\n\tforEach(callback: (data: T) => void) {\n\t\tfor(let value of this) {\n\t\t\tcallback(value.data);\n\t\t}\n\t}\n\n\tgetSharedMemory(): SharedListMemory {\n\t\treturn {\n\t\t\tfirstBlock: this.firstBlock.getSharedMemory(),\n\t\t};\n\t}\n\n\tprivate getBufferView(position: number): Uint32Array | undefined {\n\t\tlet view = this.bufferViews[position];\n\t\tif(view) {\n\t\t\treturn view;\n\t\t}\n\n\t\tlet buffer = this.memory.buffers[position];\n\t\tif(!buffer) {\n\t\t\treturn undefined;\n\t\t}\n\n\t\tview = new Uint32Array(buffer.buf);\n\t\tthis.bufferViews[position] = view;\n\t\treturn view;\n\t}\n\n\tprivate getDataBlock(buffer: ArrayBufferLike, nodeByteOffset: number): T {\n\t\tconst startIndex = nodeByteOffset + DATA_BLOCK_BYTE_OFFSET;\n\t\treturn makeArrayView(this.cachedType, buffer, startIndex, this.cachedDataLength) as T;\n\t}\n\n\tfree() {\n\t\tlet { bufferPosition: nextBlockPosition, bufferByteOffset: nextBlockByteOffset } = loadPointer(this.firstBlock.data, HEAD_INDEX);\n\t\twhile(nextBlockByteOffset) {\n\t\t\tlet allocatedMemory = new AllocatedMemory(this.memory, {\n\t\t\t\tbufferPosition: nextBlockPosition,\n\t\t\t\tbufferByteOffset: nextBlockByteOffset,\n\t\t\t});\n\n\t\t\t({ bufferPosition: nextBlockPosition, bufferByteOffset: nextBlockByteOffset } = loadPointer(allocatedMemory.data, NODE_NEXT_INDEX));\n\n\t\t\tif(this.onDelete && !Atomics.load(allocatedMemory.data, NODE_DELETED_INDEX)) {\n\t\t\t\tthis.onDelete(this.getDataBlock(allocatedMemory.data.buffer, allocatedMemory.data.byteOffset));\n\t\t\t}\n\n\t\t\tallocatedMemory.free();\n\t\t}\n\n\t\tthis.firstBlock.free();\n\t}\n}\n\ninterface SharedListConfig<T extends NumericArray> {\n\tinitWithBlock?: SharedAllocatedMemory\n\ttype?: TypedArrayConstructor<T>\n\tdataLength?: number\n}\ninterface SharedListMemory {\n\tfirstBlock: SharedAllocatedMemory\n}\n\ninterface SharedListIterable<T extends NumericArray> {\n\tdata: T\n\tindex: number\n\tdeleteCurrent: () => boolean\n}\n\nexport { type SharedListMemory };\n"],"mappings":"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"}
1
+ {"version":3,"file":"shared-list.js","names":[],"sources":["../src/shared-list.ts"],"sourcesContent":["import type { SharedAllocatedMemory } from './allocated-memory';\nimport AllocatedMemory from './allocated-memory';\nimport type MemoryHeap from './memory-heap';\nimport { loadPointer, loadRawPointer, replaceRawPointer, storeRawPointer } from './utils/pointer';\nimport { getArrayTypeCode, getByteMultipler, isBigIntType, isFloatType, makeArrayView, type NumericArray, type NumericArrayIO, type TypedArrayConstructor } from './utils/array-type';\n\n// Lock-free singly-linked list. firstBlock doubles as an immortal sentinel node whose next pointer (index 0) is the head.\n// insert appends with a Michael-Scott CAS enqueue; delete only tombstones a node (never unlinks/frees). Memory is\n// reclaimed exclusively by the quiescent compact()/clear()/free() passes. That deferral is what makes the CAS loops\n// safe without hazard pointers: a live node's address is never handed back to the allocator, so no append can ever\n// observe a recycled address (ABA) while another thread is mid-traversal.\nconst FIRST_BLOCK_RECORD_KEEPING_COUNT = 4;\nconst DATA_BLOCK_RECORD_KEEPING_COUNT = 2;\nconst DATA_BLOCK_BYTE_OFFSET = DATA_BLOCK_RECORD_KEEPING_COUNT * Uint32Array.BYTES_PER_ELEMENT;\nconst HEAD_INDEX = 0;\nconst TAIL_INDEX = 1;\nconst LENGTH_INDEX = 2;\nconst NODE_NEXT_INDEX = 0;\nconst NODE_DELETED_INDEX = 1;\n// A pointer packs the buffer position in its low `positionBits` bits and the byte offset in the high bits (see\n// utils/pointer). The split is per-heap, so it is read from the heap and cached rather than a module constant.\nexport default class SharedList<T extends NumericArray = Uint32Array> implements Iterable<SharedListIterable<T>> {\n\tstatic readonly ALLOCATE_COUNT = FIRST_BLOCK_RECORD_KEEPING_COUNT;\n\n\tprivate memory: MemoryHeap;\n\t// Per-heap pointer split, cached from the heap. Hoisted into locals inside the hot loops below.\n\tprivate positionBits: number;\n\tprivate positionMask: number;\n\t/* First block (also the sentinel node - index 0 is its next pointer = list head)\n\t\t32 index 0\n\t\tuint16 0 - head buffer position | sentinel.next\n\t\tuint16 1 - head buffer index\n\t\t32 index 1\n\t\tuint16 2 - tail hint buffer position\n\t\tuint16 3 - tail hint buffer index\n\t\t32 index 2\n\t\tuint32 4 - length\n\t\t32 index 3\n\t\tuint16 6 - type\n\t\tuint16 7 - data length (defaults to 1 number per data)\n\t*/\n\t/* Other blocks\n\t\t32 index 0 - next buffer pointer\n\t\t32 index 1 - deleted flag (0 = live, 1 = tombstoned)\n\t\t32 index 2 => data\n\t*/\n\tprivate firstBlock: AllocatedMemory;\n\tprivate uint16Array: Uint16Array;\n\t// type and dataLength are written once at init and never change, so cache them instead of atomic-loading per node\n\tprivate cachedType: number;\n\tprivate cachedDataLength: number;\n\t// u32 units per element: 1 for 32-bit types, 2 for 64-bit (float64/int64/uint64). Node data blocks scale by this.\n\tprivate cachedByteMultipler = 1;\n\t// Whether the value type is a BigInt view (int64/uint64), so insert stores bigint via Atomics; cached to keep the\n\t// per-insert branch off an instanceof check\n\tprivate cachedIsBigInt = false;\n\t// Float views can't be published with Atomics, so insert falls back to a plain store for them\n\tprivate cachedIsFloat = false;\n\t// One full-buffer Uint32Array per buffer position, so traversal indexes shared memory directly instead of\n\t// allocating a fresh view (and a getPointer object) for every node it touches\n\tprivate bufferViews: Array<Uint32Array> = [];\n\tonDelete?: (data: T) => void;\n\n\tget length(): number {\n\t\treturn Atomics.load(this.firstBlock.data, LENGTH_INDEX);\n\t}\n\n\t// Own internal memory plus every allocated node still linked in the chain (tombstoned nodes included, since they stay\n\t// allocated until a compact()/clear() reclaims them).\n\tget usedMemory(): number {\n\t\tlet total = this.firstBlock.usedMemory;\n\t\tlet { bufferPosition, bufferByteOffset } = loadPointer(this.firstBlock.data, HEAD_INDEX, this.positionBits);\n\t\twhile(bufferByteOffset) {\n\t\t\tlet node = new AllocatedMemory(this.memory, { bufferPosition, bufferByteOffset });\n\t\t\ttotal += node.usedMemory;\n\t\t\t({ bufferPosition, bufferByteOffset } = loadPointer(node.data, NODE_NEXT_INDEX, this.positionBits));\n\t\t}\n\t\treturn total;\n\t}\n\n\tget type(): number {\n\t\treturn this.cachedType;\n\t}\n\tprivate set type(value: number) {\n\t\tAtomics.store(this.uint16Array, 0, value);\n\t}\n\tget dataLength(): number {\n\t\treturn this.cachedDataLength;\n\t}\n\tprivate set dataLength(value: number) {\n\t\tAtomics.store(this.uint16Array, 1, value);\n\t}\n\n\tconstructor(memory: MemoryHeap, config?: SharedListConfig<T> | SharedListMemory) {\n\t\tthis.memory = memory;\n\t\tthis.positionBits = memory.positionBits;\n\t\tthis.positionMask = (1 << this.positionBits) - 1;\n\n\t\tif(config && 'firstBlock' in config) {\n\t\t\t// TODO: How to handle referencing memory we don't have access to yet because buffer not synced from worker?\n\t\t\tthis.firstBlock = new AllocatedMemory(memory, config.firstBlock);\n\t\t\tthis.uint16Array = new Uint16Array(this.firstBlock.data.buffer, this.firstBlock.bufferByteOffset + (LENGTH_INDEX + 1) * Uint32Array.BYTES_PER_ELEMENT, 2);\n\t\t} else {\n\t\t\tif(config && config.initWithBlock) {\n\t\t\t\tthis.firstBlock = new AllocatedMemory(memory, config.initWithBlock);\n\t\t\t} else {\n\t\t\t\tthis.firstBlock = memory.allocUI32(FIRST_BLOCK_RECORD_KEEPING_COUNT);\n\t\t\t}\n\t\t\tthis.uint16Array = new Uint16Array(this.firstBlock.data.buffer, this.firstBlock.bufferByteOffset + (LENGTH_INDEX + 1) * Uint32Array.BYTES_PER_ELEMENT, 2);\n\n\t\t\t// Empty list: the tail hint points at the sentinel (firstBlock) so insert never special-cases the first node\n\t\t\tstoreRawPointer(this.firstBlock.data, TAIL_INDEX, this.firstBlock.pointer);\n\n\t\t\tthis.type = getArrayTypeCode(config?.type ?? Uint32Array);\n\t\t\tthis.dataLength = config?.dataLength ?? 1;\n\t\t}\n\n\t\tthis.cachedType = Atomics.load(this.uint16Array, 0);\n\t\t// dataLength defaults to at least one even when initialized from raw memory that was never explicitly set\n\t\tthis.cachedDataLength = Math.max(1, Atomics.load(this.uint16Array, 1));\n\t\tthis.cachedByteMultipler = getByteMultipler(this.cachedType);\n\t\tthis.cachedIsBigInt = isBigIntType(this.cachedType);\n\t\tthis.cachedIsFloat = isFloatType(this.cachedType);\n\t}\n\n\tinsert(values: T[number] | Array<T[number]>) {\n\t\tif(!Array.isArray(values)) {\n\t\t\tvalues = [values];\n\t\t}\n\n\t\tlet dataLength = this.cachedDataLength;\n\t\tif(values.length > dataLength) {\n\t\t\tthrow new Error(`Can't insert ${values.length} array into shared list of ${dataLength} dataLength`);\n\t\t}\n\t\tlet newBlock = this.memory.allocUI32(DATA_BLOCK_RECORD_KEEPING_COUNT + dataLength * this.cachedByteMultipler);\n\t\tlet newData = this.getDataBlock(newBlock.data.buffer, newBlock.data.byteOffset);\n\t\tlet newBlockPointer = newBlock.pointer;\n\n\t\tlet io = newData as NumericArrayIO;\n\t\tfor(let i = 0; i < values.length; i++) {\n\t\t\tif(this.cachedIsFloat) {\n\t\t\t\t// Float views don't support Atomics; the enqueue CAS below still provides the cross-thread publish\n\t\t\t\tio[i] = values[i];\n\t\t\t} else if(this.cachedIsBigInt) {\n\t\t\t\tAtomics.store(newData as BigInt64Array, i, values[i] as bigint);\n\t\t\t} else {\n\t\t\t\tAtomics.store(newData as Int32Array, i, values[i] as number);\n\t\t\t}\n\t\t}\n\n\t\t// Michael-Scott enqueue: link the node onto tail.next BEFORE swinging the tail hint, so a committed node is never\n\t\t// invisible to a concurrent traversal. The tail hint may lag by one node; enqueuers help advance it.\n\t\tlet positionBits = this.positionBits;\n\t\tlet positionMask = this.positionMask;\n\t\t// eslint-disable-next-line no-constant-condition\n\t\twhile(true) {\n\t\t\tlet tailPointer = loadRawPointer(this.firstBlock.data, TAIL_INDEX);\n\t\t\tlet view = this.getBufferView(tailPointer & positionMask);\n\t\t\t// Tail node lives in a buffer this thread hasn't synced yet - retry until it appears\n\t\t\tif(!view) {\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tlet tailNextIndex = ((tailPointer >>> positionBits) >>> 2) + NODE_NEXT_INDEX;\n\t\t\tlet nextPointer = Atomics.load(view, tailNextIndex);\n\n\t\t\t// Re-check the hint hasn't moved out from under us before acting on what we read\n\t\t\tif(tailPointer !== loadRawPointer(this.firstBlock.data, TAIL_INDEX)) {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tif(nextPointer === 0) {\n\t\t\t\tif(Atomics.compareExchange(view, tailNextIndex, 0, newBlockPointer) === 0) {\n\t\t\t\t\t// Linked. Try to swing the hint forward - failure is harmless, the next enqueuer will help.\n\t\t\t\t\treplaceRawPointer(this.firstBlock.data, TAIL_INDEX, newBlockPointer, tailPointer);\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\t// Tail hint is lagging behind a linked node - help it catch up, then retry\n\t\t\t\treplaceRawPointer(this.firstBlock.data, TAIL_INDEX, nextPointer, tailPointer);\n\t\t\t}\n\t\t}\n\n\t\tAtomics.add(this.firstBlock.data, LENGTH_INDEX, 1);\n\t}\n\n\tdeleteMatch(callback: (values: T, index: number) => boolean): boolean {\n\t\tfor(let { data, index, deleteCurrent } of this) {\n\t\t\tif(callback(data, index)) {\n\t\t\t\tif(deleteCurrent()) {\n\t\t\t\t\treturn true;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\treturn false;\n\t}\n\tdeleteIndex(deleteIndex: number): boolean {\n\t\tif(deleteIndex >= this.length || deleteIndex < 0) {\n\t\t\treturn false;\n\t\t}\n\n\t\t// Compare on index only so the payload view is never materialized for an index-based delete\n\t\tfor(let item of this) {\n\t\t\tif(item.index === deleteIndex) {\n\t\t\t\tif(item.deleteCurrent()) {\n\t\t\t\t\treturn true;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\treturn false;\n\t}\n\tdeleteValue(deleteValues: T[number] | Array<T[number]>) {\n\t\tif(!Array.isArray(deleteValues)) {\n\t\t\treturn this.deleteMatch(values => values[0] === deleteValues);\n\t\t} else {\n\t\t\treturn this.deleteMatch(values => {\n\t\t\t\tif(values.length !== deleteValues.length) {\n\t\t\t\t\treturn false;\n\t\t\t\t} else {\n\t\t\t\t\tfor(let i = 0; i < values.length; i++) {\n\t\t\t\t\t\tif(values[i] !== deleteValues[i]) {\n\t\t\t\t\t\t\treturn false;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t\treturn true;\n\t\t\t\t}\n\t\t\t});\n\t\t}\n\t}\n\n\t// Reclaims tombstoned nodes: physically unlinks and frees them. Not safe to run concurrently with insert/delete.\n\tcompact() {\n\t\tlet prevPointer = this.firstBlock.pointer;\n\t\tlet prevView = this.firstBlock.data;\n\t\tlet prevNextIndex = NODE_NEXT_INDEX;\n\t\tlet positionBits = this.positionBits;\n\t\tlet positionMask = this.positionMask;\n\t\tlet nextPointer = loadRawPointer(prevView, prevNextIndex);\n\t\twhile(nextPointer) {\n\t\t\tlet position = nextPointer & positionMask;\n\t\t\tlet memPool = this.memory.buffers[position];\n\t\t\tif(!memPool) {\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tlet view = this.getBufferView(position)!;\n\t\t\tlet byteOffset = nextPointer >>> positionBits;\n\t\t\tlet nodeIndex = byteOffset >>> 2;\n\t\t\tlet followingPointer = Atomics.load(view, nodeIndex + NODE_NEXT_INDEX);\n\n\t\t\tif(Atomics.load(view, nodeIndex + NODE_DELETED_INDEX)) {\n\t\t\t\t// Skip over the tombstone and free it. onDelete already fired when the node was logically deleted.\n\t\t\t\tstoreRawPointer(prevView, prevNextIndex, followingPointer);\n\t\t\t\tmemPool.free(byteOffset);\n\t\t\t} else {\n\t\t\t\tprevPointer = nextPointer;\n\t\t\t\tprevView = view;\n\t\t\t\tprevNextIndex = nodeIndex + NODE_NEXT_INDEX;\n\t\t\t}\n\n\t\t\tnextPointer = followingPointer;\n\t\t}\n\n\t\t// The last surviving node (or the sentinel when everything is gone) is the new tail\n\t\tstoreRawPointer(this.firstBlock.data, TAIL_INDEX, prevPointer);\n\t}\n\n\tclear() {\n\t\t// Detach the whole chain, then free it. Not safe to run concurrently with insert/delete.\n\t\tlet firstBlockPointer;\n\t\tlet updateWorked = false;\n\t\twhile(!updateWorked) {\n\t\t\tfirstBlockPointer = loadRawPointer(this.firstBlock.data, HEAD_INDEX);\n\t\t\t// Already cleared\n\t\t\tif(!firstBlockPointer) {\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tupdateWorked = replaceRawPointer(this.firstBlock.data, HEAD_INDEX, 0, firstBlockPointer);\n\t\t}\n\t\t// Shouldn't be possible to hit: making Typescript happy\n\t\tif(!firstBlockPointer) {\n\t\t\treturn;\n\t\t}\n\n\t\t// Reset the tail hint back to the sentinel\n\t\tstoreRawPointer(this.firstBlock.data, TAIL_INDEX, this.firstBlock.pointer);\n\n\t\tlet liveItems = 0;\n\t\tlet positionBits = this.positionBits;\n\t\tlet positionMask = this.positionMask;\n\t\tlet nextBlockPointer = firstBlockPointer;\n\t\twhile(nextBlockPointer) {\n\t\t\tlet position = nextBlockPointer & positionMask;\n\t\t\tlet memPool = this.memory.buffers[position];\n\t\t\t// Short circuit iterations if we can't access memory\n\t\t\tif(!memPool) {\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\tlet view = this.getBufferView(position)!;\n\t\t\tlet byteOffset = nextBlockPointer >>> positionBits;\n\t\t\tlet nodeIndex = byteOffset >>> 2;\n\t\t\tnextBlockPointer = Atomics.load(view, nodeIndex + NODE_NEXT_INDEX);\n\n\t\t\tif(!Atomics.load(view, nodeIndex + NODE_DELETED_INDEX)) {\n\t\t\t\tliveItems++;\n\t\t\t\tif(this.onDelete) {\n\t\t\t\t\tthis.onDelete(this.getDataBlock(view.buffer, byteOffset));\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tmemPool.free(byteOffset);\n\t\t}\n\n\t\t// Only subtract the live nodes we detached so a concurrent insert's increment stays accurate\n\t\tAtomics.sub(this.firstBlock.data, LENGTH_INDEX, liveItems);\n\t}\n\n\t*[Symbol.iterator]() {\n\t\tlet currentIndex = 0;\n\t\tlet positionBits = this.positionBits;\n\t\tlet positionMask = this.positionMask;\n\t\tlet nextPointer = loadRawPointer(this.firstBlock.data, HEAD_INDEX);\n\t\twhile(nextPointer) {\n\t\t\tlet position = nextPointer & positionMask;\n\t\t\tlet view = this.getBufferView(position);\n\t\t\t// Short circuit iterations if we can't access memory\n\t\t\tif(!view) {\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tlet byteOffset = nextPointer >>> positionBits;\n\t\t\tlet nodeIndex = byteOffset >>> 2;\n\t\t\tnextPointer = Atomics.load(view, nodeIndex + NODE_NEXT_INDEX);\n\n\t\t\t// Skip tombstoned nodes - they stay linked until a quiescent compact()/clear()\n\t\t\tif(Atomics.load(view, nodeIndex + NODE_DELETED_INDEX)) {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tlet buffer = view.buffer;\n\t\t\tlet deletedIndex = nodeIndex + NODE_DELETED_INDEX;\n\t\t\t// Plain object with an eager data view: V8 keeps this monomorphic and fast. A lazy `get data()` accessor is far\n\t\t\t// slower to allocate than the payload view it would save, so pay for the view up front.\n\t\t\tyield {\n\t\t\t\tdata: this.getDataBlock(buffer, byteOffset),\n\t\t\t\tindex: currentIndex,\n\t\t\t\t// Logical delete: tombstone the node and drop length. Returns false if another thread beat us to it.\n\t\t\t\tdeleteCurrent: (): boolean => {\n\t\t\t\t\tif(Atomics.compareExchange(view, deletedIndex, 0, 1) === 0) {\n\t\t\t\t\t\tAtomics.sub(this.firstBlock.data, LENGTH_INDEX, 1);\n\t\t\t\t\t\tif(this.onDelete) {\n\t\t\t\t\t\t\tthis.onDelete(this.getDataBlock(buffer, byteOffset));\n\t\t\t\t\t\t}\n\t\t\t\t\t\treturn true;\n\t\t\t\t\t}\n\n\t\t\t\t\treturn false;\n\t\t\t\t},\n\t\t\t};\n\n\t\t\tcurrentIndex++;\n\t\t}\n\t}\n\n\tforEach(callback: (data: T) => void) {\n\t\tfor(let value of this) {\n\t\t\tcallback(value.data);\n\t\t}\n\t}\n\n\tgetSharedMemory(): SharedListMemory {\n\t\treturn {\n\t\t\tfirstBlock: this.firstBlock.getSharedMemory(),\n\t\t};\n\t}\n\n\tprivate getBufferView(position: number): Uint32Array | undefined {\n\t\tlet view = this.bufferViews[position];\n\t\tif(view) {\n\t\t\treturn view;\n\t\t}\n\n\t\tlet buffer = this.memory.buffers[position];\n\t\tif(!buffer) {\n\t\t\treturn undefined;\n\t\t}\n\n\t\tview = new Uint32Array(buffer.buf);\n\t\tthis.bufferViews[position] = view;\n\t\treturn view;\n\t}\n\n\tprivate getDataBlock(buffer: ArrayBufferLike, nodeByteOffset: number): T {\n\t\tconst startIndex = nodeByteOffset + DATA_BLOCK_BYTE_OFFSET;\n\t\treturn makeArrayView(this.cachedType, buffer, startIndex, this.cachedDataLength) as T;\n\t}\n\n\tfree() {\n\t\tlet { bufferPosition: nextBlockPosition, bufferByteOffset: nextBlockByteOffset } = loadPointer(this.firstBlock.data, HEAD_INDEX, this.positionBits);\n\t\twhile(nextBlockByteOffset) {\n\t\t\tlet allocatedMemory = new AllocatedMemory(this.memory, {\n\t\t\t\tbufferPosition: nextBlockPosition,\n\t\t\t\tbufferByteOffset: nextBlockByteOffset,\n\t\t\t});\n\n\t\t\t({ bufferPosition: nextBlockPosition, bufferByteOffset: nextBlockByteOffset } = loadPointer(allocatedMemory.data, NODE_NEXT_INDEX, this.positionBits));\n\n\t\t\tif(this.onDelete && !Atomics.load(allocatedMemory.data, NODE_DELETED_INDEX)) {\n\t\t\t\tthis.onDelete(this.getDataBlock(allocatedMemory.data.buffer, allocatedMemory.data.byteOffset));\n\t\t\t}\n\n\t\t\tallocatedMemory.free();\n\t\t}\n\n\t\tthis.firstBlock.free();\n\t}\n}\n\ninterface SharedListConfig<T extends NumericArray> {\n\tinitWithBlock?: SharedAllocatedMemory\n\ttype?: TypedArrayConstructor<T>\n\tdataLength?: number\n}\ninterface SharedListMemory {\n\tfirstBlock: SharedAllocatedMemory\n}\n\ninterface SharedListIterable<T extends NumericArray> {\n\tdata: T\n\tindex: number\n\tdeleteCurrent: () => boolean\n}\n\nexport { type SharedListMemory };\n"],"mappings":"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"}
@@ -1,10 +1,11 @@
1
- import { type SharedAllocatedMemory } from './allocated-memory';
1
+ import AllocatedMemory, { type SharedAllocatedMemory } from './allocated-memory';
2
2
  import type MemoryHeap from './memory-heap';
3
3
  import { type NumericArray, type TypedArrayConstructor } from './utils/array-type';
4
4
  export default class SharedMap<K extends string | number, V extends NumericArray = Uint32Array> {
5
5
  static readonly ALLOCATE_COUNT = 6;
6
6
  private memory;
7
7
  private pointerMemory;
8
+ private ownsFirstBlock;
8
9
  private lock;
9
10
  private cachedType;
10
11
  private cachedValueUnits;
@@ -20,10 +21,16 @@ export default class SharedMap<K extends string | number, V extends NumericArray
20
21
  get capacity(): number;
21
22
  get maxHash(): number;
22
23
  get type(): number;
23
- constructor(memory: MemoryHeap, config?: SharedMapConfig<V> | SharedMapMemory);
24
+ get usedMemory(): number;
25
+ constructor(memory: MemoryHeap, config?: SharedMapConfig<V> | SharedMapMemory | (SharedMapMemory & SharedMapConfig<V>));
26
+ private initialize;
24
27
  set(key: K, value: V[number]): void;
28
+ setAll(entries: Iterable<readonly [K, V[number]]>): void;
29
+ private setNoLock;
25
30
  update(key: K, updater: (current: V[number] | undefined) => V[number]): V[number];
26
31
  get(key: K): V[number] | undefined;
32
+ getAll(keys: Iterable<K>): Array<V[number] | undefined>;
33
+ private getNoLock;
27
34
  has(key: K): boolean;
28
35
  delete(key: K): boolean;
29
36
  [Symbol.iterator](): IterableIterator<[number, V[number]]>;
@@ -33,8 +40,9 @@ export default class SharedMap<K extends string | number, V extends NumericArray
33
40
  }
34
41
  interface SharedMapConfig<V extends NumericArray> {
35
42
  type?: TypedArrayConstructor<V>;
43
+ capacity?: number;
36
44
  }
37
45
  interface SharedMapMemory {
38
- firstBlock: SharedAllocatedMemory;
46
+ firstBlock: SharedAllocatedMemory | AllocatedMemory;
39
47
  }
40
48
  export type { SharedMapConfig, SharedMapMemory };
@@ -8,6 +8,7 @@ var u = 0, d = 1, f = 2, p = 16, m = 3, h = 4, g = 0, _ = 1, v = 2, y = 3, b = 4
8
8
  static ALLOCATE_COUNT = 6;
9
9
  memory;
10
10
  pointerMemory;
11
+ ownsFirstBlock = !0;
11
12
  lock;
12
13
  cachedType;
13
14
  cachedValueUnits = 1;
@@ -19,7 +20,7 @@ var u = 0, d = 1, f = 2, p = 16, m = 3, h = 4, g = 0, _ = 1, v = 2, y = 3, b = 4
19
20
  ensureTable() {
20
21
  let t = this.pointerMemory.data, r = t[g], i = t[b];
21
22
  if (r === this.cachedTablePointer && i === this.cachedCapacity && this.cachedSlots) return;
22
- let a = new n(this.memory, e(r));
23
+ let a = new n(this.memory, e(r, this.memory.positionBits));
23
24
  this.cachedTableMemory = a, this.cachedSlots = a.data, this.cachedValues = this.makeValuesView(a, i), this.cachedCapacity = i, this.cachedTablePointer = r;
24
25
  }
25
26
  makeValuesView(e, t) {
@@ -41,42 +42,71 @@ var u = 0, d = 1, f = 2, p = 16, m = 3, h = 4, g = 0, _ = 1, v = 2, y = 3, b = 4
41
42
  get type() {
42
43
  return this.cachedType;
43
44
  }
44
- constructor(e, a) {
45
- if (this.memory = e, a && "firstBlock" in a) this.pointerMemory = new n(e, a.firstBlock);
46
- else {
47
- this.pointerMemory = e.allocUI32(S.ALLOCATE_COUNT);
48
- let n = r(a?.type ?? Uint32Array);
49
- this.cachedValueUnits = i(n);
50
- let o = e.allocUI32(p * (2 + this.cachedValueUnits));
51
- t(this.pointerMemory.data, g, o.pointer), Atomics.store(this.pointerMemory.data, b, p), Atomics.store(this.pointerMemory.data, x, n);
45
+ get usedMemory() {
46
+ o(this.lock);
47
+ try {
48
+ return this.ensureTable(), (this.ownsFirstBlock ? this.pointerMemory.usedMemory : 0) + this.cachedTableMemory.usedMemory;
49
+ } finally {
50
+ s(this.lock);
52
51
  }
53
- this.lock = new Int32Array(this.pointerMemory.data.buffer, this.pointerMemory.bufferByteOffset + _ * Uint32Array.BYTES_PER_ELEMENT, 1), this.cachedType = Atomics.load(this.pointerMemory.data, x), this.cachedValueUnits = i(this.cachedType);
52
+ }
53
+ constructor(e, t) {
54
+ this.memory = e, t && "firstBlock" in t ? t.firstBlock instanceof n ? (this.pointerMemory = t.firstBlock, this.initialize(t), this.ownsFirstBlock = !1) : this.pointerMemory = new n(e, {
55
+ length: S.ALLOCATE_COUNT,
56
+ ...t.firstBlock
57
+ }) : (this.pointerMemory = e.allocUI32(S.ALLOCATE_COUNT), this.initialize(t)), this.lock = new Int32Array(this.pointerMemory.data.buffer, this.pointerMemory.bufferByteOffset + _ * Uint32Array.BYTES_PER_ELEMENT, 1), this.cachedType = Atomics.load(this.pointerMemory.data, x), this.cachedValueUnits = i(this.cachedType);
58
+ }
59
+ initialize(e) {
60
+ let n = e && "type" in e ? e.type : void 0, a = r(n ?? Uint32Array);
61
+ this.cachedValueUnits = i(a);
62
+ let o = e && "capacity" in e ? e.capacity : void 0, s = p;
63
+ for (; s < (o ?? p);) s *= 2;
64
+ if (s * (2 + this.cachedValueUnits) > this.memory.maxAllocationLength) throw Error(`SharedMap capacity ${s} exceeds the largest contiguous allocation`);
65
+ let c = this.memory.allocUI32(s * (2 + this.cachedValueUnits));
66
+ t(this.pointerMemory.data, g, c.pointer), Atomics.store(this.pointerMemory.data, b, s), Atomics.store(this.pointerMemory.data, x, a);
54
67
  }
55
68
  set(e, t) {
56
- let n = c(e);
57
69
  o(this.lock);
58
70
  try {
59
- let e = this.pointerMemory.data;
60
- (e[v] + e[y] + 1) * h > e[b] * m && this.resize(), this.ensureTable();
61
- let r = this.cachedSlots, i = this.cachedValues, a = this.cachedCapacity, o = a - 1, s = a, c = l(n) & o, p = -1;
62
- for (;;) {
63
- let a = r[c];
64
- if (a === u) {
65
- let a = p >= 0 ? p : c;
66
- r[a] = d, r[s + a] = n, i[a] = t, e[v]++, p >= 0 && e[y]--;
67
- return;
68
- }
69
- if (a === f) p < 0 && (p = c);
70
- else if (r[s + c] === n) {
71
- i[c] = t;
72
- return;
73
- }
74
- c = c + 1 & o;
71
+ this.setNoLock(e, t);
72
+ } finally {
73
+ s(this.lock);
74
+ }
75
+ }
76
+ setAll(e) {
77
+ let t = Array.from(e);
78
+ o(this.lock);
79
+ try {
80
+ let e = this.pointerMemory.data, n = e[v] + e[y] + t.length;
81
+ if (n * h > e[b] * m) {
82
+ let t = e[b];
83
+ for (; n * h > t * m;) t *= 2;
84
+ this.resize(t);
75
85
  }
86
+ for (let [e, n] of t) this.setNoLock(e, n);
76
87
  } finally {
77
88
  s(this.lock);
78
89
  }
79
90
  }
91
+ setNoLock(e, t) {
92
+ let n = c(e), r = this.pointerMemory.data;
93
+ (r[v] + r[y] + 1) * h > r[b] * m && this.resize(), this.ensureTable();
94
+ let i = this.cachedSlots, a = this.cachedValues, o = this.cachedCapacity, s = o - 1, p = o, g = l(n) & s, _ = -1;
95
+ for (;;) {
96
+ let e = i[g];
97
+ if (e === u) {
98
+ let e = _ >= 0 ? _ : g;
99
+ i[e] = d, i[p + e] = n, a[e] = t, r[v]++, _ >= 0 && r[y]--;
100
+ return;
101
+ }
102
+ if (e === f) _ < 0 && (_ = g);
103
+ else if (i[p + g] === n) {
104
+ a[g] = t;
105
+ return;
106
+ }
107
+ g = g + 1 & s;
108
+ }
109
+ }
80
110
  update(e, t) {
81
111
  let n = c(e);
82
112
  o(this.lock);
@@ -102,21 +132,34 @@ var u = 0, d = 1, f = 2, p = 16, m = 3, h = 4, g = 0, _ = 1, v = 2, y = 3, b = 4
102
132
  }
103
133
  }
104
134
  get(e) {
105
- let t = c(e);
106
135
  o(this.lock);
107
136
  try {
108
- this.ensureTable();
109
- let e = this.cachedSlots, n = this.cachedValues, r = this.cachedCapacity, i = r - 1, a = r, o = l(t) & i;
110
- for (;;) {
111
- let r = e[o];
112
- if (r === u) return;
113
- if (r === d && e[a + o] === t) return n[o];
114
- o = o + 1 & i;
115
- }
137
+ return this.getNoLock(e);
116
138
  } finally {
117
139
  s(this.lock);
118
140
  }
119
141
  }
142
+ getAll(e) {
143
+ o(this.lock);
144
+ try {
145
+ let t = [];
146
+ for (let n of e) t.push(this.getNoLock(n));
147
+ return t;
148
+ } finally {
149
+ s(this.lock);
150
+ }
151
+ }
152
+ getNoLock(e) {
153
+ let t = c(e);
154
+ this.ensureTable();
155
+ let n = this.cachedSlots, r = this.cachedValues, i = this.cachedCapacity, a = i - 1, o = i, s = l(t) & a;
156
+ for (;;) {
157
+ let e = n[s];
158
+ if (e === u) return;
159
+ if (e === d && n[o + s] === t) return r[s];
160
+ s = s + 1 & a;
161
+ }
162
+ }
120
163
  has(e) {
121
164
  return this.get(e) !== void 0;
122
165
  }
@@ -141,21 +184,24 @@ var u = 0, d = 1, f = 2, p = 16, m = 3, h = 4, g = 0, _ = 1, v = 2, y = 3, b = 4
141
184
  let e = this.cachedSlots, t = this.cachedValues, n = this.cachedCapacity, r = n;
142
185
  for (let i = 0; i < n; i++) e[i] === d && (yield [e[r + i], t[i]]);
143
186
  }
144
- resize() {
187
+ resize(e) {
145
188
  this.ensureTable();
146
- let e = this.cachedTableMemory, n = this.cachedSlots, r = this.cachedValues, i = this.cachedCapacity, a = i, o = Atomics.load(this.pointerMemory.data, v), s = i;
147
- (o + 1) * h > i * m && (s = i * 2);
148
- let c = this.memory.allocUI32(this.tableLength(s)), f = c.data, p = this.makeValuesView(c, s), _ = s - 1, x = s;
149
- for (let e = 0; e < i; e++) {
150
- if (n[e] !== d) continue;
151
- let t = n[a + e], i = r[e], o = l(t) & _;
152
- for (; f[o] !== u;) o = o + 1 & _;
153
- f[o] = d, f[x + o] = t, p[o] = i;
189
+ let n = this.cachedTableMemory, r = this.cachedSlots, i = this.cachedValues, a = this.cachedCapacity, o = a, s = Atomics.load(this.pointerMemory.data, v), c = e ?? a;
190
+ if (e === void 0 && (s + 1) * h > a * m && (c = a * 2), this.tableLength(c) > this.memory.maxAllocationLength) {
191
+ let e = Math.floor(this.memory.maxAllocationLength / (2 + this.cachedValueUnits) * m / h);
192
+ throw Error(`SharedMap can't grow to capacity ${c}: its table would exceed the largest contiguous allocation (${this.memory.maxAllocationLength} u32s, ~${e} entries). Use larger buffers or shard across multiple maps.`);
193
+ }
194
+ let f = this.memory.allocUI32(this.tableLength(c)), p = f.data, _ = this.makeValuesView(f, c), x = c - 1, S = c;
195
+ for (let e = 0; e < a; e++) {
196
+ if (r[e] !== d) continue;
197
+ let t = r[o + e], n = i[e], a = l(t) & x;
198
+ for (; p[a] !== u;) a = a + 1 & x;
199
+ p[a] = d, p[S + a] = t, _[a] = n;
154
200
  }
155
- t(this.pointerMemory.data, g, c.pointer), Atomics.store(this.pointerMemory.data, b, s), Atomics.store(this.pointerMemory.data, y, 0), this.cachedTablePointer = 0, this.cachedSlots = void 0, e.free();
201
+ t(this.pointerMemory.data, g, f.pointer), Atomics.store(this.pointerMemory.data, b, c), Atomics.store(this.pointerMemory.data, y, 0), this.cachedTablePointer = 0, this.cachedSlots = void 0, n.free();
156
202
  }
157
203
  free() {
158
- this.ensureTable(), this.cachedTableMemory.free(), this.pointerMemory.free();
204
+ this.ensureTable(), this.cachedTableMemory.free(), this.ownsFirstBlock && this.pointerMemory.free();
159
205
  }
160
206
  getSharedMemory() {
161
207
  return { firstBlock: this.pointerMemory.getSharedMemory() };
@@ -1 +1 @@
1
- {"version":3,"file":"shared-map.js","names":[],"sources":["../src/shared-map.ts"],"sourcesContent":["import AllocatedMemory, { type SharedAllocatedMemory } from './allocated-memory';\nimport type MemoryHeap from './memory-heap';\nimport { getPointer, storeRawPointer } from './utils/pointer';\nimport { get32BitHash, mix32 } from './utils/hash';\nimport { lock, unlock, SIMPLE_LOCK_ALLOCATE_COUNT } from './lock/simple-lock';\nimport { getArrayTypeCode, getByteMultipler, makeArrayView, type NumericArray, type NumericArrayIO, type TypedArrayConstructor } from './utils/array-type';\n\n// Open-addressing hash map with linear probing, held in one contiguous table (no per-bucket lists, no per-operation\n// allocation). A single exclusive lock guards the table so slot reads/writes inside the critical section can be plain\n// memory access instead of per-slot Atomics; the lock's acquire/release provides the cross-thread happens-before.\n//\n// The table uses a Structure-of-Arrays layout: three contiguous regions - states | keys | values - so probing touches\n// the dense states and keys regions (more candidates per cache line) and iteration walks values densely. capacity is\n// always a power of two, so slot = mix(key) & (capacity - 1). state distinguishes EMPTY/FULL/TOMBSTONE, so key 0 and\n// value 0 need no sentinel.\n//\n// Keys are always stored as the 32-bit hash (a numeric key is its own hash); values are stored in the typed array the\n// map was created with (Uint32Array by default, or Int32Array / Float32Array). Iteration is a best-effort scan that is\n// not lock-guarded, so it must not run concurrently with mutation from another thread. A single allocation can't span\n// buffers, so the table is bounded by MemoryHeap.maxAllocationLength.\n\nconst EMPTY = 0;\nconst FULL = 1;\nconst TOMBSTONE = 2;\n\nconst DEFAULT_CAPACITY = 16;\n// Grow when (occupied + tombstones) / capacity exceeds this. Kept as a fraction to avoid floating point in the hot path.\nconst LOAD_NUMERATOR = 3;\nconst LOAD_DENOMINATOR = 4;\n\n// Metadata block layout (pointerMemory)\nconst TABLE_POINTER_INDEX = 0;\nconst LOCK_INDEX = 1;\nconst LENGTH_INDEX = 2;\nconst TOMBSTONE_INDEX = 3;\nconst CAPACITY_INDEX = 4;\nconst TYPE_INDEX = 5;\n\nexport default class SharedMap<K extends string | number, V extends NumericArray = Uint32Array> {\n\tstatic readonly ALLOCATE_COUNT = 6;\n\n\tprivate memory: MemoryHeap;\n\tprivate pointerMemory: AllocatedMemory;\n\tprivate lock: Int32Array;\n\t// Value type is written once at creation and never changes, so resolve it once instead of reading metadata per op\n\tprivate cachedType: number;\n\t// u32 units per value slot: 1 for 32-bit types, 2 for 64-bit (float64/int64/uint64). The values region and every\n\t// table allocation scale by this.\n\tprivate cachedValueUnits = 1;\n\n\t// Cache the resolved views so most operations avoid rebuilding them. Rebuild whenever the table pointer OR the capacity\n\t// changes: the pointer alone is not enough because the allocator recycles freed addresses (split:false), so a rehash can\n\t// land a differently-sized table at the same address (ABA). The cached views are plain windows into shared memory, so\n\t// they always reflect the current bytes at that address; only the derived capacity (mask/offsets) can go stale, and the\n\t// capacity check catches exactly that.\n\tprivate cachedTablePointer = 0;\n\tprivate cachedTableMemory?: AllocatedMemory;\n\tprivate cachedSlots?: Uint32Array; // whole block as u32: states region, then keys region\n\tprivate cachedValues?: V; // typed view over the values region\n\tprivate cachedCapacity = 0;\n\t// Callers hold the lock, so the acquire fence already published the latest pointer/capacity - plain reads are enough\n\t// and avoid two Atomics.load per operation on the hot path.\n\tprivate ensureTable() {\n\t\tlet data = this.pointerMemory.data;\n\t\tlet pointer = data[TABLE_POINTER_INDEX];\n\t\tlet capacity = data[CAPACITY_INDEX];\n\t\tif(pointer === this.cachedTablePointer && capacity === this.cachedCapacity && this.cachedSlots) {\n\t\t\treturn;\n\t\t}\n\n\t\tlet table = new AllocatedMemory(this.memory, getPointer(pointer));\n\t\tthis.cachedTableMemory = table;\n\t\tthis.cachedSlots = table.data;\n\t\tthis.cachedValues = this.makeValuesView(table, capacity);\n\t\tthis.cachedCapacity = capacity;\n\t\tthis.cachedTablePointer = pointer;\n\t}\n\tprivate makeValuesView(table: AllocatedMemory, capacity: number): V {\n\t\t// states and keys each take one u32 per slot, so the values region starts after 2 * capacity u32s\n\t\tlet byteOffset = table.bufferByteOffset + capacity * 2 * Uint32Array.BYTES_PER_ELEMENT;\n\t\treturn makeArrayView(this.cachedType, table.data.buffer, byteOffset, capacity) as V;\n\t}\n\t// u32s a table of this capacity needs: states + keys (one each per slot) plus the values region, which is\n\t// cachedValueUnits u32s per slot.\n\tprivate tableLength(capacity: number): number {\n\t\treturn capacity * (2 + this.cachedValueUnits);\n\t}\n\n\tget length(): number {\n\t\treturn Atomics.load(this.pointerMemory.data, LENGTH_INDEX);\n\t}\n\tget capacity(): number {\n\t\treturn Atomics.load(this.pointerMemory.data, CAPACITY_INDEX);\n\t}\n\tget maxHash(): number {\n\t\treturn this.capacity;\n\t}\n\tget type(): number {\n\t\treturn this.cachedType;\n\t}\n\n\tconstructor(memory: MemoryHeap, config?: SharedMapConfig<V> | SharedMapMemory) {\n\t\tthis.memory = memory;\n\n\t\tif(config && 'firstBlock' in config) {\n\t\t\tthis.pointerMemory = new AllocatedMemory(memory, config.firstBlock);\n\t\t} else {\n\t\t\tthis.pointerMemory = memory.allocUI32(SharedMap.ALLOCATE_COUNT);\n\t\t\tlet typeCode = getArrayTypeCode(config?.type ?? Uint32Array);\n\t\t\tthis.cachedValueUnits = getByteMultipler(typeCode);\n\t\t\tlet table = memory.allocUI32(DEFAULT_CAPACITY * (2 + this.cachedValueUnits));\n\t\t\tstoreRawPointer(this.pointerMemory.data, TABLE_POINTER_INDEX, table.pointer);\n\t\t\tAtomics.store(this.pointerMemory.data, CAPACITY_INDEX, DEFAULT_CAPACITY);\n\t\t\tAtomics.store(this.pointerMemory.data, TYPE_INDEX, typeCode);\n\t\t}\n\t\tthis.lock = new Int32Array(this.pointerMemory.data.buffer, this.pointerMemory.bufferByteOffset + LOCK_INDEX * Uint32Array.BYTES_PER_ELEMENT, SIMPLE_LOCK_ALLOCATE_COUNT);\n\t\tthis.cachedType = Atomics.load(this.pointerMemory.data, TYPE_INDEX);\n\t\tthis.cachedValueUnits = getByteMultipler(this.cachedType);\n\t}\n\n\tset(key: K, value: V[number]) {\n\t\tlet fullHashKey = get32BitHash(key);\n\n\t\tlock(this.lock);\n\t\ttry {\n\t\t\tlet data = this.pointerMemory.data;\n\t\t\tlet length = data[LENGTH_INDEX];\n\t\t\tlet tombstones = data[TOMBSTONE_INDEX];\n\t\t\tif((length + tombstones + 1) * LOAD_DENOMINATOR > data[CAPACITY_INDEX] * LOAD_NUMERATOR) {\n\t\t\t\tthis.resize();\n\t\t\t}\n\n\t\t\tthis.ensureTable();\n\t\t\tlet slots = this.cachedSlots!;\n\t\t\tlet values = this.cachedValues! as NumericArrayIO;\n\t\t\tlet capacity = this.cachedCapacity;\n\t\t\tlet mask = capacity - 1;\n\t\t\tlet keysBase = capacity;\n\t\t\tlet idx = mix32(fullHashKey) & mask;\n\t\t\tlet firstTomb = -1;\n\t\t\t// eslint-disable-next-line no-constant-condition\n\t\t\twhile(true) {\n\t\t\t\tlet state = slots[idx];\n\t\t\t\tif(state === EMPTY) {\n\t\t\t\t\tlet target = firstTomb >= 0 ? firstTomb : idx;\n\t\t\t\t\tslots[target] = FULL;\n\t\t\t\t\tslots[keysBase + target] = fullHashKey;\n\t\t\t\t\tvalues[target] = value;\n\t\t\t\t\tdata[LENGTH_INDEX]++;\n\t\t\t\t\t// Reusing a tombstone slot reclaims it\n\t\t\t\t\tif(firstTomb >= 0) {\n\t\t\t\t\t\tdata[TOMBSTONE_INDEX]--;\n\t\t\t\t\t}\n\t\t\t\t\treturn;\n\t\t\t\t} else if(state === TOMBSTONE) {\n\t\t\t\t\tif(firstTomb < 0) {\n\t\t\t\t\t\tfirstTomb = idx;\n\t\t\t\t\t}\n\t\t\t\t} else if(slots[keysBase + idx] === fullHashKey) {\n\t\t\t\t\tvalues[idx] = value;\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tidx = (idx + 1) & mask;\n\t\t\t}\n\t\t} finally {\n\t\t\tunlock(this.lock);\n\t\t}\n\t}\n\n\t// Atomic read-modify-write under the table lock: resolves the current value (undefined if the key is\n\t// absent), calls updater, and stores the result - so two threads mutating the same key never lose an\n\t// update the way a separate get() + set() can. updater runs inside the lock and must be synchronous and\n\t// must not touch this same map (the lock is not reentrant).\n\tupdate(key: K, updater: (current: V[number] | undefined) => V[number]): V[number] {\n\t\tlet fullHashKey = get32BitHash(key);\n\n\t\tlock(this.lock);\n\t\ttry {\n\t\t\tlet data = this.pointerMemory.data;\n\t\t\tlet length = data[LENGTH_INDEX];\n\t\t\tlet tombstones = data[TOMBSTONE_INDEX];\n\t\t\tif((length + tombstones + 1) * LOAD_DENOMINATOR > data[CAPACITY_INDEX] * LOAD_NUMERATOR) {\n\t\t\t\tthis.resize();\n\t\t\t}\n\n\t\t\tthis.ensureTable();\n\t\t\tlet slots = this.cachedSlots!;\n\t\t\tlet values = this.cachedValues! as NumericArrayIO;\n\t\t\tlet capacity = this.cachedCapacity;\n\t\t\tlet mask = capacity - 1;\n\t\t\tlet keysBase = capacity;\n\t\t\tlet idx = mix32(fullHashKey) & mask;\n\t\t\tlet firstTomb = -1;\n\t\t\t// eslint-disable-next-line no-constant-condition\n\t\t\twhile(true) {\n\t\t\t\tlet state = slots[idx];\n\t\t\t\tif(state === EMPTY) {\n\t\t\t\t\tlet newValue = updater(undefined);\n\t\t\t\t\tlet target = firstTomb >= 0 ? firstTomb : idx;\n\t\t\t\t\tslots[target] = FULL;\n\t\t\t\t\tslots[keysBase + target] = fullHashKey;\n\t\t\t\t\tvalues[target] = newValue;\n\t\t\t\t\tdata[LENGTH_INDEX]++;\n\t\t\t\t\tif(firstTomb >= 0) {\n\t\t\t\t\t\tdata[TOMBSTONE_INDEX]--;\n\t\t\t\t\t}\n\t\t\t\t\treturn newValue;\n\t\t\t\t} else if(state === TOMBSTONE) {\n\t\t\t\t\tif(firstTomb < 0) {\n\t\t\t\t\t\tfirstTomb = idx;\n\t\t\t\t\t}\n\t\t\t\t} else if(slots[keysBase + idx] === fullHashKey) {\n\t\t\t\t\tlet newValue = updater(values[idx]);\n\t\t\t\t\tvalues[idx] = newValue;\n\t\t\t\t\treturn newValue;\n\t\t\t\t}\n\n\t\t\t\tidx = (idx + 1) & mask;\n\t\t\t}\n\t\t} finally {\n\t\t\tunlock(this.lock);\n\t\t}\n\t}\n\n\tget(key: K): V[number] | undefined {\n\t\tlet fullHashKey = get32BitHash(key);\n\n\t\tlock(this.lock);\n\t\ttry {\n\t\t\tthis.ensureTable();\n\t\t\tlet slots = this.cachedSlots!;\n\t\t\tlet values = this.cachedValues! as NumericArrayIO;\n\t\t\tlet capacity = this.cachedCapacity;\n\t\t\tlet mask = capacity - 1;\n\t\t\tlet keysBase = capacity;\n\t\t\tlet idx = mix32(fullHashKey) & mask;\n\t\t\t// eslint-disable-next-line no-constant-condition\n\t\t\twhile(true) {\n\t\t\t\tlet state = slots[idx];\n\t\t\t\tif(state === EMPTY) {\n\t\t\t\t\treturn undefined;\n\t\t\t\t}\n\t\t\t\tif(state === FULL && slots[keysBase + idx] === fullHashKey) {\n\t\t\t\t\treturn values[idx];\n\t\t\t\t}\n\t\t\t\tidx = (idx + 1) & mask;\n\t\t\t}\n\t\t} finally {\n\t\t\tunlock(this.lock);\n\t\t}\n\t}\n\thas(key: K): boolean {\n\t\treturn this.get(key) !== undefined;\n\t}\n\n\tdelete(key: K): boolean {\n\t\tlet fullHashKey = get32BitHash(key);\n\n\t\tlock(this.lock);\n\t\ttry {\n\t\t\tthis.ensureTable();\n\t\t\tlet data = this.pointerMemory.data;\n\t\t\tlet slots = this.cachedSlots!;\n\t\t\tlet capacity = this.cachedCapacity;\n\t\t\tlet mask = capacity - 1;\n\t\t\tlet keysBase = capacity;\n\t\t\tlet idx = mix32(fullHashKey) & mask;\n\t\t\t// eslint-disable-next-line no-constant-condition\n\t\t\twhile(true) {\n\t\t\t\tlet state = slots[idx];\n\t\t\t\tif(state === EMPTY) {\n\t\t\t\t\treturn false;\n\t\t\t\t}\n\t\t\t\tif(state === FULL && slots[keysBase + idx] === fullHashKey) {\n\t\t\t\t\tslots[idx] = TOMBSTONE;\n\t\t\t\t\tdata[LENGTH_INDEX]--;\n\t\t\t\t\tdata[TOMBSTONE_INDEX]++;\n\t\t\t\t\treturn true;\n\t\t\t\t}\n\t\t\t\tidx = (idx + 1) & mask;\n\t\t\t}\n\t\t} finally {\n\t\t\tunlock(this.lock);\n\t\t}\n\t}\n\n\t// Best-effort scan (see class note): not lock-guarded, so must not run concurrently with writes from another thread.\n\t*[Symbol.iterator](): IterableIterator<[number, V[number]]> {\n\t\tthis.ensureTable();\n\t\tlet slots = this.cachedSlots!;\n\t\tlet values = this.cachedValues! as NumericArrayIO;\n\t\tlet capacity = this.cachedCapacity;\n\t\tlet keysBase = capacity;\n\t\tfor(let i = 0; i < capacity; i++) {\n\t\t\tif(slots[i] === FULL) {\n\t\t\t\tyield [slots[keysBase + i], values[i]];\n\t\t\t}\n\t\t}\n\t}\n\n\t// Rehash into a fresh table. Doubles capacity when live entries are crowding the table; when tombstones are the cause,\n\t// a same-size table already reclaims them. Caller must hold the lock.\n\tprivate resize() {\n\t\tthis.ensureTable();\n\t\tlet oldTable = this.cachedTableMemory!;\n\t\tlet oldSlots = this.cachedSlots!;\n\t\tlet oldValues = this.cachedValues! as NumericArrayIO;\n\t\tlet oldCapacity = this.cachedCapacity;\n\t\tlet oldKeysBase = oldCapacity;\n\t\tlet length = Atomics.load(this.pointerMemory.data, LENGTH_INDEX);\n\n\t\tlet newCapacity = oldCapacity;\n\t\tif((length + 1) * LOAD_DENOMINATOR > oldCapacity * LOAD_NUMERATOR) {\n\t\t\tnewCapacity = oldCapacity * 2;\n\t\t}\n\t\tlet newTable = this.memory.allocUI32(this.tableLength(newCapacity));\n\t\tlet newSlots = newTable.data;\n\t\tlet newValues = this.makeValuesView(newTable, newCapacity) as NumericArrayIO;\n\t\tlet mask = newCapacity - 1;\n\t\tlet newKeysBase = newCapacity;\n\n\t\tfor(let i = 0; i < oldCapacity; i++) {\n\t\t\tif(oldSlots[i] !== FULL) {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tlet key = oldSlots[oldKeysBase + i];\n\t\t\tlet value = oldValues[i];\n\t\t\tlet idx = mix32(key) & mask;\n\t\t\t// Fresh table has no tombstones, so probe straight to the first empty slot\n\t\t\twhile(newSlots[idx] !== EMPTY) {\n\t\t\t\tidx = (idx + 1) & mask;\n\t\t\t}\n\t\t\tnewSlots[idx] = FULL;\n\t\t\tnewSlots[newKeysBase + idx] = key;\n\t\t\tnewValues[idx] = value;\n\t\t}\n\n\t\tstoreRawPointer(this.pointerMemory.data, TABLE_POINTER_INDEX, newTable.pointer);\n\t\tAtomics.store(this.pointerMemory.data, CAPACITY_INDEX, newCapacity);\n\t\tAtomics.store(this.pointerMemory.data, TOMBSTONE_INDEX, 0);\n\t\t// Invalidate the cached views so the next access rebuilds against the new table\n\t\tthis.cachedTablePointer = 0;\n\t\tthis.cachedSlots = undefined;\n\t\toldTable.free();\n\t}\n\n\tfree() {\n\t\tthis.ensureTable();\n\t\tthis.cachedTableMemory!.free();\n\t\tthis.pointerMemory.free();\n\t}\n\n\tgetSharedMemory(): SharedMapMemory {\n\t\treturn {\n\t\t\tfirstBlock: this.pointerMemory.getSharedMemory(),\n\t\t};\n\t}\n}\n\ninterface SharedMapConfig<V extends NumericArray> {\n\ttype?: TypedArrayConstructor<V>\n}\ninterface SharedMapMemory {\n\tfirstBlock: SharedAllocatedMemory\n}\n\nexport type { SharedMapConfig, SharedMapMemory };\n"],"mappings":"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"}
1
+ {"version":3,"file":"shared-map.js","names":[],"sources":["../src/shared-map.ts"],"sourcesContent":["import AllocatedMemory, { type SharedAllocatedMemory } from './allocated-memory';\nimport type MemoryHeap from './memory-heap';\nimport { getPointer, storeRawPointer } from './utils/pointer';\nimport { get32BitHash, mix32 } from './utils/hash';\nimport { lock, unlock, SIMPLE_LOCK_ALLOCATE_COUNT } from './lock/simple-lock';\nimport { getArrayTypeCode, getByteMultipler, makeArrayView, type NumericArray, type NumericArrayIO, type TypedArrayConstructor } from './utils/array-type';\n\n// Open-addressing hash map with linear probing, held in one contiguous table (no per-bucket lists, no per-operation\n// allocation). A single exclusive lock guards the table so slot reads/writes inside the critical section can be plain\n// memory access instead of per-slot Atomics; the lock's acquire/release provides the cross-thread happens-before.\n//\n// The table uses a Structure-of-Arrays layout: three contiguous regions - states | keys | values - so probing touches\n// the dense states and keys regions (more candidates per cache line) and iteration walks values densely. capacity is\n// always a power of two, so slot = mix(key) & (capacity - 1). state distinguishes EMPTY/FULL/TOMBSTONE, so key 0 and\n// value 0 need no sentinel.\n//\n// Keys are always stored as the 32-bit hash (a numeric key is its own hash); values are stored in the typed array the\n// map was created with (Uint32Array by default, or Int32Array / Float32Array). Iteration is a best-effort scan that is\n// not lock-guarded, so it must not run concurrently with mutation from another thread. A single allocation can't span\n// buffers, so the table is bounded by MemoryHeap.maxAllocationLength.\n\nconst EMPTY = 0;\nconst FULL = 1;\nconst TOMBSTONE = 2;\n\nconst DEFAULT_CAPACITY = 16;\n// Grow when (occupied + tombstones) / capacity exceeds this. Kept as a fraction to avoid floating point in the hot path.\nconst LOAD_NUMERATOR = 3;\nconst LOAD_DENOMINATOR = 4;\n\n// Metadata block layout (pointerMemory)\nconst TABLE_POINTER_INDEX = 0;\nconst LOCK_INDEX = 1;\nconst LENGTH_INDEX = 2;\nconst TOMBSTONE_INDEX = 3;\nconst CAPACITY_INDEX = 4;\nconst TYPE_INDEX = 5;\n\nexport default class SharedMap<K extends string | number, V extends NumericArray = Uint32Array> {\n\tstatic readonly ALLOCATE_COUNT = 6;\n\n\tprivate memory: MemoryHeap;\n\tprivate pointerMemory: AllocatedMemory;\n\t// False when pointerMemory is a slice of another structure's memory (e.g. inlined in SharedQuadtree); that owner frees it\n\tprivate ownsFirstBlock = true;\n\tprivate lock: Int32Array;\n\t// Value type is written once at creation and never changes, so resolve it once instead of reading metadata per op\n\tprivate cachedType: number;\n\t// u32 units per value slot: 1 for 32-bit types, 2 for 64-bit (float64/int64/uint64). The values region and every\n\t// table allocation scale by this.\n\tprivate cachedValueUnits = 1;\n\n\t// Cache the resolved views so most operations avoid rebuilding them. Rebuild whenever the table pointer OR the capacity\n\t// changes: the pointer alone is not enough because the allocator recycles freed addresses (split:false), so a rehash can\n\t// land a differently-sized table at the same address (ABA). The cached views are plain windows into shared memory, so\n\t// they always reflect the current bytes at that address; only the derived capacity (mask/offsets) can go stale, and the\n\t// capacity check catches exactly that.\n\tprivate cachedTablePointer = 0;\n\tprivate cachedTableMemory?: AllocatedMemory;\n\tprivate cachedSlots?: Uint32Array; // whole block as u32: states region, then keys region\n\tprivate cachedValues?: V; // typed view over the values region\n\tprivate cachedCapacity = 0;\n\t// Callers hold the lock, so the acquire fence already published the latest pointer/capacity - plain reads are enough\n\t// and avoid two Atomics.load per operation on the hot path.\n\tprivate ensureTable() {\n\t\tlet data = this.pointerMemory.data;\n\t\tlet pointer = data[TABLE_POINTER_INDEX];\n\t\tlet capacity = data[CAPACITY_INDEX];\n\t\tif(pointer === this.cachedTablePointer && capacity === this.cachedCapacity && this.cachedSlots) {\n\t\t\treturn;\n\t\t}\n\n\t\tlet table = new AllocatedMemory(this.memory, getPointer(pointer, this.memory.positionBits));\n\t\tthis.cachedTableMemory = table;\n\t\tthis.cachedSlots = table.data;\n\t\tthis.cachedValues = this.makeValuesView(table, capacity);\n\t\tthis.cachedCapacity = capacity;\n\t\tthis.cachedTablePointer = pointer;\n\t}\n\tprivate makeValuesView(table: AllocatedMemory, capacity: number): V {\n\t\t// states and keys each take one u32 per slot, so the values region starts after 2 * capacity u32s\n\t\tlet byteOffset = table.bufferByteOffset + capacity * 2 * Uint32Array.BYTES_PER_ELEMENT;\n\t\treturn makeArrayView(this.cachedType, table.data.buffer, byteOffset, capacity) as V;\n\t}\n\t// u32s a table of this capacity needs: states + keys (one each per slot) plus the values region, which is\n\t// cachedValueUnits u32s per slot.\n\tprivate tableLength(capacity: number): number {\n\t\treturn capacity * (2 + this.cachedValueUnits);\n\t}\n\n\tget length(): number {\n\t\treturn Atomics.load(this.pointerMemory.data, LENGTH_INDEX);\n\t}\n\tget capacity(): number {\n\t\treturn Atomics.load(this.pointerMemory.data, CAPACITY_INDEX);\n\t}\n\tget maxHash(): number {\n\t\treturn this.capacity;\n\t}\n\tget type(): number {\n\t\treturn this.cachedType;\n\t}\n\t// Metadata block plus the single table allocation the map currently points at.\n\tget usedMemory(): number {\n\t\tlock(this.lock);\n\t\ttry {\n\t\t\tthis.ensureTable();\n\t\t\t// When inlined, the owner already counts pointerMemory as part of its own firstBlock\n\t\t\tlet ownMemory = this.ownsFirstBlock ? this.pointerMemory.usedMemory : 0;\n\t\t\treturn ownMemory + this.cachedTableMemory!.usedMemory;\n\t\t} finally {\n\t\t\tunlock(this.lock);\n\t\t}\n\t}\n\n\tconstructor(memory: MemoryHeap, config?: SharedMapConfig<V> | SharedMapMemory | SharedMapMemory & SharedMapConfig<V>) {\n\t\tthis.memory = memory;\n\n\t\tif(config && 'firstBlock' in config) {\n\t\t\t// An AllocatedMemory instance is a caller-reserved slice (e.g. inlined in SharedQuadtree) for us to initialize in\n\t\t\t// place; a plain pointer is the serialized clone path, whose table already exists and is read from metadata.\n\t\t\tif(config.firstBlock instanceof AllocatedMemory) {\n\t\t\t\tthis.pointerMemory = config.firstBlock;\n\t\t\t\tthis.initialize(config);\n\t\t\t\tthis.ownsFirstBlock = false;\n\t\t\t} else {\n\t\t\t\tthis.pointerMemory = new AllocatedMemory(memory, { length: SharedMap.ALLOCATE_COUNT, ...config.firstBlock });\n\t\t\t}\n\t\t} else {\n\t\t\tthis.pointerMemory = memory.allocUI32(SharedMap.ALLOCATE_COUNT);\n\t\t\tthis.initialize(config);\n\t\t}\n\t\tthis.lock = new Int32Array(this.pointerMemory.data.buffer, this.pointerMemory.bufferByteOffset + LOCK_INDEX * Uint32Array.BYTES_PER_ELEMENT, SIMPLE_LOCK_ALLOCATE_COUNT);\n\t\tthis.cachedType = Atomics.load(this.pointerMemory.data, TYPE_INDEX);\n\t\tthis.cachedValueUnits = getByteMultipler(this.cachedType);\n\t}\n\n\t// Allocate the initial table and write the metadata. pointerMemory must already be set. Used by both the fresh-alloc\n\t// path and the inline-in-place path (where pointerMemory is a slice reserved by the owner).\n\tprivate initialize(config?: SharedMapConfig<V> | SharedMapMemory) {\n\t\tlet type = config && 'type' in config ? config.type : undefined;\n\t\tlet typeCode = getArrayTypeCode(type ?? Uint32Array);\n\t\tthis.cachedValueUnits = getByteMultipler(typeCode);\n\t\tlet requestedCapacity = config && 'capacity' in config ? config.capacity : undefined;\n\t\tlet capacity = DEFAULT_CAPACITY;\n\t\twhile(capacity < (requestedCapacity ?? DEFAULT_CAPACITY)) {\n\t\t\tcapacity *= 2;\n\t\t}\n\t\tif(capacity * (2 + this.cachedValueUnits) > this.memory.maxAllocationLength) {\n\t\t\tthrow new Error(`SharedMap capacity ${capacity} exceeds the largest contiguous allocation`);\n\t\t}\n\t\tlet table = this.memory.allocUI32(capacity * (2 + this.cachedValueUnits));\n\t\tstoreRawPointer(this.pointerMemory.data, TABLE_POINTER_INDEX, table.pointer);\n\t\tAtomics.store(this.pointerMemory.data, CAPACITY_INDEX, capacity);\n\t\tAtomics.store(this.pointerMemory.data, TYPE_INDEX, typeCode);\n\t}\n\n\tset(key: K, value: V[number]) {\n\t\tlock(this.lock);\n\t\ttry {\n\t\t\tthis.setNoLock(key, value);\n\t\t} finally {\n\t\t\tunlock(this.lock);\n\t\t}\n\t}\n\n\tsetAll(entries: Iterable<readonly [K, V[number]]>) {\n\t\tlet batch = Array.from(entries);\n\t\tlock(this.lock);\n\t\ttry {\n\t\t\tlet data = this.pointerMemory.data;\n\t\t\tlet projected = data[LENGTH_INDEX] + data[TOMBSTONE_INDEX] + batch.length;\n\t\t\tif(projected * LOAD_DENOMINATOR > data[CAPACITY_INDEX] * LOAD_NUMERATOR) {\n\t\t\t\tlet capacity = data[CAPACITY_INDEX];\n\t\t\t\twhile(projected * LOAD_DENOMINATOR > capacity * LOAD_NUMERATOR) {\n\t\t\t\t\tcapacity *= 2;\n\t\t\t\t}\n\t\t\t\tthis.resize(capacity);\n\t\t\t}\n\t\t\tfor(let [key, value] of batch) {\n\t\t\t\tthis.setNoLock(key, value);\n\t\t\t}\n\t\t} finally {\n\t\t\tunlock(this.lock);\n\t\t}\n\t}\n\n\tprivate setNoLock(key: K, value: V[number]) {\n\t\tlet fullHashKey = get32BitHash(key);\n\t\tlet data = this.pointerMemory.data;\n\t\tlet length = data[LENGTH_INDEX];\n\t\tlet tombstones = data[TOMBSTONE_INDEX];\n\t\tif((length + tombstones + 1) * LOAD_DENOMINATOR > data[CAPACITY_INDEX] * LOAD_NUMERATOR) {\n\t\t\tthis.resize();\n\t\t}\n\n\t\tthis.ensureTable();\n\t\tlet slots = this.cachedSlots!;\n\t\tlet values = this.cachedValues! as NumericArrayIO;\n\t\tlet capacity = this.cachedCapacity;\n\t\tlet mask = capacity - 1;\n\t\tlet keysBase = capacity;\n\t\tlet idx = mix32(fullHashKey) & mask;\n\t\tlet firstTomb = -1;\n\t\t// eslint-disable-next-line no-constant-condition\n\t\twhile(true) {\n\t\t\tlet state = slots[idx];\n\t\t\tif(state === EMPTY) {\n\t\t\t\tlet target = firstTomb >= 0 ? firstTomb : idx;\n\t\t\t\tslots[target] = FULL;\n\t\t\t\tslots[keysBase + target] = fullHashKey;\n\t\t\t\tvalues[target] = value;\n\t\t\t\tdata[LENGTH_INDEX]++;\n\t\t\t\tif(firstTomb >= 0) {\n\t\t\t\t\tdata[TOMBSTONE_INDEX]--;\n\t\t\t\t}\n\t\t\t\treturn;\n\t\t\t} else if(state === TOMBSTONE) {\n\t\t\t\tif(firstTomb < 0) {\n\t\t\t\t\tfirstTomb = idx;\n\t\t\t\t}\n\t\t\t} else if(slots[keysBase + idx] === fullHashKey) {\n\t\t\t\tvalues[idx] = value;\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tidx = (idx + 1) & mask;\n\t\t}\n\t}\n\n\t// Atomic read-modify-write under the table lock: resolves the current value (undefined if the key is\n\t// absent), calls updater, and stores the result - so two threads mutating the same key never lose an\n\t// update the way a separate get() + set() can. updater runs inside the lock and must be synchronous and\n\t// must not touch this same map (the lock is not reentrant).\n\tupdate(key: K, updater: (current: V[number] | undefined) => V[number]): V[number] {\n\t\tlet fullHashKey = get32BitHash(key);\n\n\t\tlock(this.lock);\n\t\ttry {\n\t\t\tlet data = this.pointerMemory.data;\n\t\t\tlet length = data[LENGTH_INDEX];\n\t\t\tlet tombstones = data[TOMBSTONE_INDEX];\n\t\t\tif((length + tombstones + 1) * LOAD_DENOMINATOR > data[CAPACITY_INDEX] * LOAD_NUMERATOR) {\n\t\t\t\tthis.resize();\n\t\t\t}\n\n\t\t\tthis.ensureTable();\n\t\t\tlet slots = this.cachedSlots!;\n\t\t\tlet values = this.cachedValues! as NumericArrayIO;\n\t\t\tlet capacity = this.cachedCapacity;\n\t\t\tlet mask = capacity - 1;\n\t\t\tlet keysBase = capacity;\n\t\t\tlet idx = mix32(fullHashKey) & mask;\n\t\t\tlet firstTomb = -1;\n\t\t\t// eslint-disable-next-line no-constant-condition\n\t\t\twhile(true) {\n\t\t\t\tlet state = slots[idx];\n\t\t\t\tif(state === EMPTY) {\n\t\t\t\t\tlet newValue = updater(undefined);\n\t\t\t\t\tlet target = firstTomb >= 0 ? firstTomb : idx;\n\t\t\t\t\tslots[target] = FULL;\n\t\t\t\t\tslots[keysBase + target] = fullHashKey;\n\t\t\t\t\tvalues[target] = newValue;\n\t\t\t\t\tdata[LENGTH_INDEX]++;\n\t\t\t\t\tif(firstTomb >= 0) {\n\t\t\t\t\t\tdata[TOMBSTONE_INDEX]--;\n\t\t\t\t\t}\n\t\t\t\t\treturn newValue;\n\t\t\t\t} else if(state === TOMBSTONE) {\n\t\t\t\t\tif(firstTomb < 0) {\n\t\t\t\t\t\tfirstTomb = idx;\n\t\t\t\t\t}\n\t\t\t\t} else if(slots[keysBase + idx] === fullHashKey) {\n\t\t\t\t\tlet newValue = updater(values[idx]);\n\t\t\t\t\tvalues[idx] = newValue;\n\t\t\t\t\treturn newValue;\n\t\t\t\t}\n\n\t\t\t\tidx = (idx + 1) & mask;\n\t\t\t}\n\t\t} finally {\n\t\t\tunlock(this.lock);\n\t\t}\n\t}\n\n\tget(key: K): V[number] | undefined {\n\t\tlock(this.lock);\n\t\ttry {\n\t\t\treturn this.getNoLock(key);\n\t\t} finally {\n\t\t\tunlock(this.lock);\n\t\t}\n\t}\n\n\tgetAll(keys: Iterable<K>): Array<V[number] | undefined> {\n\t\tlock(this.lock);\n\t\ttry {\n\t\t\tlet values: Array<V[number] | undefined> = [];\n\t\t\tfor(let key of keys) {\n\t\t\t\tvalues.push(this.getNoLock(key));\n\t\t\t}\n\t\t\treturn values;\n\t\t} finally {\n\t\t\tunlock(this.lock);\n\t\t}\n\t}\n\n\tprivate getNoLock(key: K): V[number] | undefined {\n\t\tlet fullHashKey = get32BitHash(key);\n\t\tthis.ensureTable();\n\t\tlet slots = this.cachedSlots!;\n\t\tlet values = this.cachedValues! as NumericArrayIO;\n\t\tlet capacity = this.cachedCapacity;\n\t\tlet mask = capacity - 1;\n\t\tlet keysBase = capacity;\n\t\tlet idx = mix32(fullHashKey) & mask;\n\t\t// eslint-disable-next-line no-constant-condition\n\t\twhile(true) {\n\t\t\tlet state = slots[idx];\n\t\t\tif(state === EMPTY) {\n\t\t\t\treturn undefined;\n\t\t\t}\n\t\t\tif(state === FULL && slots[keysBase + idx] === fullHashKey) {\n\t\t\t\treturn values[idx];\n\t\t\t}\n\t\t\tidx = (idx + 1) & mask;\n\t\t}\n\t}\n\thas(key: K): boolean {\n\t\treturn this.get(key) !== undefined;\n\t}\n\n\tdelete(key: K): boolean {\n\t\tlet fullHashKey = get32BitHash(key);\n\n\t\tlock(this.lock);\n\t\ttry {\n\t\t\tthis.ensureTable();\n\t\t\tlet data = this.pointerMemory.data;\n\t\t\tlet slots = this.cachedSlots!;\n\t\t\tlet capacity = this.cachedCapacity;\n\t\t\tlet mask = capacity - 1;\n\t\t\tlet keysBase = capacity;\n\t\t\tlet idx = mix32(fullHashKey) & mask;\n\t\t\t// eslint-disable-next-line no-constant-condition\n\t\t\twhile(true) {\n\t\t\t\tlet state = slots[idx];\n\t\t\t\tif(state === EMPTY) {\n\t\t\t\t\treturn false;\n\t\t\t\t}\n\t\t\t\tif(state === FULL && slots[keysBase + idx] === fullHashKey) {\n\t\t\t\t\tslots[idx] = TOMBSTONE;\n\t\t\t\t\tdata[LENGTH_INDEX]--;\n\t\t\t\t\tdata[TOMBSTONE_INDEX]++;\n\t\t\t\t\treturn true;\n\t\t\t\t}\n\t\t\t\tidx = (idx + 1) & mask;\n\t\t\t}\n\t\t} finally {\n\t\t\tunlock(this.lock);\n\t\t}\n\t}\n\n\t// Best-effort scan (see class note): not lock-guarded, so must not run concurrently with writes from another thread.\n\t*[Symbol.iterator](): IterableIterator<[number, V[number]]> {\n\t\tthis.ensureTable();\n\t\tlet slots = this.cachedSlots!;\n\t\tlet values = this.cachedValues! as NumericArrayIO;\n\t\tlet capacity = this.cachedCapacity;\n\t\tlet keysBase = capacity;\n\t\tfor(let i = 0; i < capacity; i++) {\n\t\t\tif(slots[i] === FULL) {\n\t\t\t\tyield [slots[keysBase + i], values[i]];\n\t\t\t}\n\t\t}\n\t}\n\n\t// Rehash into a fresh table. Doubles capacity when live entries are crowding the table; when tombstones are the cause,\n\t// a same-size table already reclaims them. Caller must hold the lock.\n\tprivate resize(requestedCapacity?: number) {\n\t\tthis.ensureTable();\n\t\tlet oldTable = this.cachedTableMemory!;\n\t\tlet oldSlots = this.cachedSlots!;\n\t\tlet oldValues = this.cachedValues! as NumericArrayIO;\n\t\tlet oldCapacity = this.cachedCapacity;\n\t\tlet oldKeysBase = oldCapacity;\n\t\tlet length = Atomics.load(this.pointerMemory.data, LENGTH_INDEX);\n\n\t\tlet newCapacity = requestedCapacity ?? oldCapacity;\n\t\tif(requestedCapacity === undefined && (length + 1) * LOAD_DENOMINATOR > oldCapacity * LOAD_NUMERATOR) {\n\t\t\tnewCapacity = oldCapacity * 2;\n\t\t}\n\t\t// A table is one contiguous allocation, so it can never exceed what a single buffer can hand out. Fail here with an\n\t\t// actionable message instead of letting allocUI32 throw a cryptic \"Unable to allocate N numbers\" from deep inside the\n\t\t// heap. Mirrors the same guard in initialize().\n\t\tif(this.tableLength(newCapacity) > this.memory.maxAllocationLength) {\n\t\t\tlet maxEntries = Math.floor(this.memory.maxAllocationLength / (2 + this.cachedValueUnits) * LOAD_NUMERATOR / LOAD_DENOMINATOR);\n\t\t\tthrow new Error(`SharedMap can't grow to capacity ${newCapacity}: its table would exceed the largest contiguous `\n\t\t\t\t+ `allocation (${this.memory.maxAllocationLength} u32s, ~${maxEntries} entries). Use larger buffers or shard across multiple maps.`);\n\t\t}\n\t\tlet newTable = this.memory.allocUI32(this.tableLength(newCapacity));\n\t\tlet newSlots = newTable.data;\n\t\tlet newValues = this.makeValuesView(newTable, newCapacity) as NumericArrayIO;\n\t\tlet mask = newCapacity - 1;\n\t\tlet newKeysBase = newCapacity;\n\n\t\tfor(let i = 0; i < oldCapacity; i++) {\n\t\t\tif(oldSlots[i] !== FULL) {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tlet key = oldSlots[oldKeysBase + i];\n\t\t\tlet value = oldValues[i];\n\t\t\tlet idx = mix32(key) & mask;\n\t\t\t// Fresh table has no tombstones, so probe straight to the first empty slot\n\t\t\twhile(newSlots[idx] !== EMPTY) {\n\t\t\t\tidx = (idx + 1) & mask;\n\t\t\t}\n\t\t\tnewSlots[idx] = FULL;\n\t\t\tnewSlots[newKeysBase + idx] = key;\n\t\t\tnewValues[idx] = value;\n\t\t}\n\n\t\tstoreRawPointer(this.pointerMemory.data, TABLE_POINTER_INDEX, newTable.pointer);\n\t\tAtomics.store(this.pointerMemory.data, CAPACITY_INDEX, newCapacity);\n\t\tAtomics.store(this.pointerMemory.data, TOMBSTONE_INDEX, 0);\n\t\t// Invalidate the cached views so the next access rebuilds against the new table\n\t\tthis.cachedTablePointer = 0;\n\t\tthis.cachedSlots = undefined;\n\t\toldTable.free();\n\t}\n\n\tfree() {\n\t\tthis.ensureTable();\n\t\tthis.cachedTableMemory!.free();\n\t\tif(this.ownsFirstBlock) {\n\t\t\tthis.pointerMemory.free();\n\t\t}\n\t}\n\n\tgetSharedMemory(): SharedMapMemory {\n\t\treturn {\n\t\t\tfirstBlock: this.pointerMemory.getSharedMemory(),\n\t\t};\n\t}\n}\n\ninterface SharedMapConfig<V extends NumericArray> {\n\ttype?: TypedArrayConstructor<V>\n\tcapacity?: number\n}\ninterface SharedMapMemory {\n\tfirstBlock: SharedAllocatedMemory | AllocatedMemory\n}\n\nexport type { SharedMapConfig, SharedMapMemory };\n"],"mappings":"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"}
@@ -12,10 +12,10 @@ var i = class {
12
12
  return this.list.length;
13
13
  }
14
14
  insert(t) {
15
- this.list.insert(e(t.memory.bufferPosition, t.memory.bufferByteOffset));
15
+ this.list.insert(e(t.memory.bufferPosition, t.memory.bufferByteOffset, this.memory.positionBits));
16
16
  }
17
17
  delete(t) {
18
- let n = this.list.deleteValue(e(t.memory.bufferPosition, t.memory.bufferByteOffset));
18
+ let n = this.list.deleteValue(e(t.memory.bufferPosition, t.memory.bufferByteOffset, this.memory.positionBits));
19
19
  return this.list.compact(), n;
20
20
  }
21
21
  compact() {
@@ -24,7 +24,7 @@ var i = class {
24
24
  *[Symbol.iterator]() {
25
25
  let e = this.list[Symbol.iterator]();
26
26
  for (let { data: r } of e) {
27
- let { bufferPosition: e, bufferByteOffset: i } = t(r, 0), a = new n(this.memory, {
27
+ let { bufferPosition: e, bufferByteOffset: i } = t(r, 0, this.memory.positionBits), a = new n(this.memory, {
28
28
  bufferPosition: e,
29
29
  bufferByteOffset: i
30
30
  });