@daneren2005/shared-memory-objects 1.5.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.
@@ -1,21 +1,22 @@
1
1
  import { MAX_BYTE_OFFSET_LENGTH as e, positionBitsForBufferSize as t } from "./utils/pointer.js";
2
2
  import n from "./allocated-memory.js";
3
- import r from "./memory-buffer.js";
3
+ import r, { FIRST_BLOCK_DATA_BYTE_OFFSET as i } from "./memory-buffer.js";
4
4
  //#region src/memory-heap.ts
5
- var i = e, a = 2 ** 31, o = 0, s = 1, c = 2, l = 3, u = 4, d = class {
5
+ var a = e, o = 2 ** 31, s = 0, c = 1, l = 2, u = 3, d = 4, f = class {
6
6
  buffers;
7
7
  onGrowBufferHandlers = [];
8
8
  isClone;
9
9
  memory;
10
+ allocCursor = 0;
10
11
  positionBits;
11
12
  get maxBuffers() {
12
13
  return 2 ** this.positionBits;
13
14
  }
14
15
  get bufferSize() {
15
- return this.memory.data[o];
16
+ return this.memory.data[s];
16
17
  }
17
18
  get maxAllocationLength() {
18
- return Math.floor((this.bufferSize - 32 - 8) / Uint32Array.BYTES_PER_ELEMENT);
19
+ return Math.floor((this.bufferSize - 36 - 16) / Uint32Array.BYTES_PER_ELEMENT);
19
20
  }
20
21
  constructor(e) {
21
22
  if (e && "buffers" in e) this.buffers = e.buffers.map((e) => new r({
@@ -23,23 +24,24 @@ var i = e, a = 2 ** 31, o = 0, s = 1, c = 2, l = 3, u = 4, d = class {
23
24
  skipInitialization: !0
24
25
  })), this.memory = new n(this, {
25
26
  bufferPosition: 0,
26
- bufferByteOffset: 40
27
- }), this.positionBits = this.memory.data[l], this.isClone = !0;
27
+ bufferByteOffset: i
28
+ }), this.positionBits = this.memory.data[u], this.isClone = !0;
28
29
  else {
29
30
  "SharedArrayBuffer" in globalThis || console.warn("SharedArrayBuffer is not working: falling back to ArrayBuffer");
30
- let r = e?.bufferSize ?? i;
31
- if (r > a) throw Error(`Buffer size ${r} is greater than max ${a} that we can reference with pointers`);
31
+ let r = e?.bufferSize ?? a;
32
+ if (r > o) throw Error(`Buffer size ${r} is greater than max ${o} that we can reference with pointers`);
32
33
  this.positionBits = t(r);
33
- let d = this.createBuffer(r);
34
- this.buffers = [d];
35
- let f = d.callocAs("u32", u);
34
+ let i = this.createBuffer(r);
35
+ this.buffers = [i];
36
+ let f = i.callocAs("u32", d);
36
37
  if (f) this.memory = new n(this, {
37
38
  bufferPosition: 0,
38
39
  bufferByteOffset: f.byteOffset
39
40
  });
40
41
  else throw Error("Failed to initialize first byte from buffer");
41
- this.memory.data[o] = r, this.memory.data[s] = 1, this.memory.data[c] = e?.autoGrowSize ?? 100, this.memory.data[l] = this.positionBits, this.isClone = !1;
42
+ this.memory.data[s] = r, this.memory.data[l] = e?.autoGrowSize ?? 100, this.memory.data[u] = this.positionBits, this.isClone = !1;
42
43
  for (let t = 1; t < (e?.initialBuffers ?? 1); t++) this.buffers.push(this.createBuffer(r));
44
+ this.memory.data[c] = this.buffers.length;
43
45
  }
44
46
  }
45
47
  addSharedBuffer(e) {
@@ -49,7 +51,7 @@ var i = e, a = 2 ** 31, o = 0, s = 1, c = 2, l = 3, u = 4, d = class {
49
51
  });
50
52
  }
51
53
  growBuffer() {
52
- let e = this.createBuffer(), t = Atomics.add(this.memory.data, s, 1);
54
+ let e = this.createBuffer(), t = Atomics.add(this.memory.data, c, 1);
53
55
  return this.buffers[t] = e, this.onGrowBufferHandlers.forEach((n) => n({
54
56
  bufferPosition: t,
55
57
  buffer: e.buf
@@ -75,20 +77,21 @@ var i = e, a = 2 ** 31, o = 0, s = 1, c = 2, l = 3, u = 4, d = class {
75
77
  }
76
78
  allocUI32(e) {
77
79
  e = Math.ceil(e);
78
- for (let t = 0; t < this.buffers.length; t++) {
79
- let r = this.buffers[t];
80
- if (!r) continue;
81
- let i = r.callocAs("u32", e);
82
- if (i) return t === this.buffers.length - 1 && Atomics.load(this.memory.data, s) === this.buffers.length && this.memory.data[c] < 100 && this.memory.data[c] > 0 && r.top / r.end > this.memory.data[c] / 100 && this.growBuffer(), new n(this, {
83
- data: i,
84
- buffer: r
80
+ let t = this.buffers.length;
81
+ for (let r = 0; r < t; r++) {
82
+ let i = (this.allocCursor + r) % t, a = this.buffers[i];
83
+ if (!a) continue;
84
+ let o = a.callocAs("u32", e);
85
+ if (o) return this.allocCursor = i, i === this.buffers.length - 1 && Atomics.load(this.memory.data, c) === this.buffers.length && this.memory.data[l] < 100 && this.memory.data[l] > 0 && a.top / a.end > this.memory.data[l] / 100 && this.growBuffer(), new n(this, {
86
+ data: o,
87
+ buffer: a
85
88
  });
86
89
  }
87
90
  if (this.buffers.length >= this.maxBuffers) throw Error(`Can't initialize a new buffer since it would have a position greater than the max of ${this.maxBuffers}`);
88
- let t = this.growBuffer(), r = t.callocAs("u32", e);
89
- if (r) return new n(this, {
90
- data: r,
91
- buffer: t
91
+ let r = this.growBuffer(), i = r.callocAs("u32", e);
92
+ if (i) return this.allocCursor = this.buffers.indexOf(r), new n(this, {
93
+ data: i,
94
+ buffer: r
92
95
  });
93
96
  throw Error(`Unable to allocate ${e} numbers even after adding a new buffer`);
94
97
  }
@@ -106,6 +109,6 @@ var i = e, a = 2 ** 31, o = 0, s = 1, c = 2, l = 3, u = 4, d = class {
106
109
  }
107
110
  };
108
111
  //#endregion
109
- export { d as default };
112
+ export { f as default };
110
113
 
111
114
  //# sourceMappingURL=memory-heap.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"memory-heap.js","names":[],"sources":["../src/memory-heap.ts"],"sourcesContent":["import AllocatedMemory, { type SharedAllocatedMemory } from './allocated-memory';\nimport { MAX_BYTE_OFFSET_LENGTH, positionBitsForBufferSize } from './utils/pointer';\nimport MemoryBuffer, { SIZEOF_MEM_BLOCK, SIZEOF_STATE } from './memory-buffer';\n\nconst DEFAULT_BUFFER_SIZE = MAX_BYTE_OFFSET_LENGTH;\nconst MAX_BUFFER_SIZE = Math.pow(2, 31);\nconst BUFFER_SIZE_INDEX = 0;\nconst BUFFER_COUNT_INDEX = 1;\nconst BUFFER_AUTO_GROW_INDEX = 2;\nconst BUFFER_POSITION_BITS_INDEX = 3;\nconst HEAP_STATE_COUNT = 4;\nexport default class MemoryHeap {\n\tbuffers: Array<MemoryBuffer>;\n\tprivate onGrowBufferHandlers: Array<OnGrowBuffer> = [];\n\tisClone: boolean;\n\tprivate memory: AllocatedMemory;\n\n\tpositionBits: number;\n\t// Max buffer position (count) and max byte offset the split can address\n\tget maxBuffers() {\n\t\treturn Math.pow(2, this.positionBits);\n\t}\n\n\tget bufferSize() {\n\t\treturn this.memory.data[BUFFER_SIZE_INDEX];\n\t}\n\n\t// Largest u32 count a single fresh buffer can hand out in one allocation: the buffer minus the allocator state and the\n\t// block header. A structure that grows in segments must keep each segment within this or the allocation can never fit.\n\tget maxAllocationLength() {\n\t\treturn Math.floor((this.bufferSize - SIZEOF_STATE - SIZEOF_MEM_BLOCK) / Uint32Array.BYTES_PER_ELEMENT);\n\t}\n\n\tconstructor(config?: MemoryHeapConfig | MemoryHeapMemory) {\n\t\tif(config && 'buffers' in config) {\n\t\t\tthis.buffers = config.buffers.map(buffer => {\n\t\t\t\treturn new MemoryBuffer({\n\t\t\t\t\tbuf: buffer,\n\t\t\t\t\tskipInitialization: true,\n\t\t\t\t});\n\t\t\t});\n\n\t\t\t// TODO: This should be programic instead of hoping the first allocation is always byte 40\n\t\t\tthis.memory = new AllocatedMemory(this, {\n\t\t\t\tbufferPosition: 0,\n\t\t\t\tbufferByteOffset: 40,\n\t\t\t});\n\t\t\tthis.positionBits = this.memory.data[BUFFER_POSITION_BITS_INDEX];\n\t\t\tthis.isClone = true;\n\t\t} else {\n\t\t\tif(!('SharedArrayBuffer' in globalThis)) {\n\t\t\t\tconsole.warn('SharedArrayBuffer is not working: falling back to ArrayBuffer');\n\t\t\t}\n\n\t\t\tconst bufferSize = config?.bufferSize ?? DEFAULT_BUFFER_SIZE;\n\t\t\tif(bufferSize > MAX_BUFFER_SIZE) {\n\t\t\t\tthrow new Error(`Buffer size ${bufferSize} is greater than max ${MAX_BUFFER_SIZE} that we can reference with pointers`);\n\t\t\t}\n\t\t\tthis.positionBits = positionBitsForBufferSize(bufferSize);\n\n\t\t\tlet startBuffer = this.createBuffer(bufferSize);\n\t\t\tthis.buffers = [\n\t\t\t\tstartBuffer,\n\t\t\t];\n\t\t\tconst data = startBuffer.callocAs('u32', HEAP_STATE_COUNT);\n\t\t\tif(data) {\n\t\t\t\tthis.memory = new AllocatedMemory(this, {\n\t\t\t\t\tbufferPosition: 0,\n\t\t\t\t\tbufferByteOffset: data.byteOffset,\n\t\t\t\t});\n\t\t\t} else {\n\t\t\t\tthrow new Error('Failed to initialize first byte from buffer');\n\t\t\t}\n\t\t\tthis.memory.data[BUFFER_SIZE_INDEX] = bufferSize;\n\t\t\tthis.memory.data[BUFFER_COUNT_INDEX] = 1;\n\t\t\tthis.memory.data[BUFFER_AUTO_GROW_INDEX] = config?.autoGrowSize ?? 100;\n\t\t\tthis.memory.data[BUFFER_POSITION_BITS_INDEX] = this.positionBits;\n\t\t\tthis.isClone = false;\n\n\t\t\tfor(let i = 1; i < (config?.initialBuffers ?? 1); i++) {\n\t\t\t\tthis.buffers.push(this.createBuffer(bufferSize));\n\t\t\t}\n\t\t}\n\t}\n\n\taddSharedBuffer(data: GrowBufferData) {\n\t\tthis.buffers[data.bufferPosition] = new MemoryBuffer({\n\t\t\tbuf: data.buffer,\n\t\t\tskipInitialization: true,\n\t\t});\n\t}\n\n\tprivate growBuffer() {\n\t\tconst buffer = this.createBuffer();\n\t\tlet nextBufferPosition = Atomics.add(this.memory.data, BUFFER_COUNT_INDEX, 1);\n\t\t// Setting index set by internal Atomic count so we can create new buffers from multiple threads and keep position consistent\n\t\tthis.buffers[nextBufferPosition] = buffer;\n\t\tthis.onGrowBufferHandlers.forEach(handler => handler({\n\t\t\tbufferPosition: nextBufferPosition,\n\t\t\tbuffer: buffer.buf as SharedArrayBuffer,\n\t\t}));\n\n\t\treturn buffer;\n\t}\n\tprivate createBuffer(bufferSize?: number): MemoryBuffer {\n\t\tconst usedBufferSize = bufferSize ?? this.bufferSize;\n\t\tlet buf: ArrayBuffer | SharedArrayBuffer;\n\t\tif('SharedArrayBuffer' in globalThis) {\n\t\t\tbuf = new SharedArrayBuffer(usedBufferSize);\n\t\t} else {\n\t\t\tbuf = new ArrayBuffer(usedBufferSize);\n\t\t}\n\n\t\treturn new MemoryBuffer({\n\t\t\tbuf,\n\n\t\t\t// We can't use this unless we can 100% guarantee that every thread will stop using memory the instant it is freed\n\t\t\t// ex: Allocate 16 bytes. Thread A frees that allocation and then allocates 12 bytes and 4 bytes, but Thread B is mid-execution\n\t\t\t// on the old allocation can changes the internal state of the 4-byte allocation breaking everything\n\t\t\t// After the internal state is wrong MemoryBuffer will loose track of which blocks are where and how big they are\n\t\t\tcompact: false,\n\t\t\tsplit: false,\n\t\t});\n\t}\n\n\taddOnGrowBufferHandlers(handler: OnGrowBuffer) {\n\t\tthis.onGrowBufferHandlers.push(handler);\n\t}\n\n\t// Guarantees at least one completely-empty buffer exists and (via the grow handlers) has been fanned out to every\n\t// thread. Call this on the owning thread before dispatching work that allocates on another thread (a worker): that\n\t// allocation then lands in a buffer everyone already holds instead of forcing growBuffer on the worker, which would\n\t// create a buffer only the worker can see and leave every other thread unable to resolve pointers into it. Returns\n\t// true if it had to grow one.\n\tensureSpareBuffer(): boolean {\n\t\tfor(let i = 0; i < this.buffers.length; i++) {\n\t\t\tconst buffer = this.buffers[i];\n\t\t\tif(buffer && buffer.isEmpty) {\n\t\t\t\treturn false;\n\t\t\t}\n\t\t}\n\n\t\tthis.growBuffer();\n\t\treturn true;\n\t}\n\n\tallocUI32(count: number): AllocatedMemory {\n\t\tcount = Math.ceil(count);\n\t\tfor(let i = 0; i < this.buffers.length; i++) {\n\t\t\tconst buffer = this.buffers[i];\n\t\t\t// Should just mean we haven't synced this buffer from another thread yet\n\t\t\tif(!buffer) {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\t// Should be fine to initialize all values as 0s since unsigned/signed ints and floats all store 0 as all 0s\n\t\t\tconst data = buffer.callocAs('u32', count);\n\t\t\tif(data) {\n\t\t\t\t// Auto grow when nearly full when we need buffer to already be sync'd between threads BEFORE we try to use it\n\t\t\t\tif(\n\t\t\t\t\ti === (this.buffers.length - 1)\n\t\t\t\t\t&& Atomics.load(this.memory.data, BUFFER_COUNT_INDEX) === this.buffers.length\n\t\t\t\t\t&& this.memory.data[BUFFER_AUTO_GROW_INDEX] < 100\n\t\t\t\t\t&& this.memory.data[BUFFER_AUTO_GROW_INDEX] > 0\n\t\t\t\t) {\n\t\t\t\t\tconst percentFull = buffer.top / buffer.end;\n\t\t\t\t\tif(percentFull > (this.memory.data[BUFFER_AUTO_GROW_INDEX] / 100)) {\n\t\t\t\t\t\tthis.growBuffer();\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\treturn new AllocatedMemory(this, {\n\t\t\t\t\tdata,\n\t\t\t\t\tbuffer,\n\t\t\t\t});\n\t\t\t}\n\t\t}\n\n\t\tif(this.buffers.length >= this.maxBuffers) {\n\t\t\tthrow new Error(`Can't initialize a new buffer since it would have a position greater than the max of ${this.maxBuffers}`);\n\t\t}\n\n\t\t// If we get here we need to grow another buffer to continue allocating new memory\n\t\tlet buffer = this.growBuffer();\n\t\tconst data = buffer.callocAs('u32', count);\n\t\tif(data) {\n\t\t\treturn new AllocatedMemory(this, {\n\t\t\t\tdata,\n\t\t\t\tbuffer,\n\t\t\t});\n\t\t} else {\n\t\t\tthrow new Error(`Unable to allocate ${count} numbers even after adding a new buffer`);\n\t\t}\n\t}\n\n\tgetSharedAlloc(shared: SharedAllocatedMemory): AllocatedMemory | undefined {\n\t\t// Should just mean it hasn't synced to this thread yet\n\t\tif(this.buffers[shared.bufferPosition] === undefined) {\n\t\t\treturn undefined;\n\t\t}\n\n\t\treturn new AllocatedMemory(this, shared);\n\t}\n\n\tget currentUsed() {\n\t\treturn this.totalAllocated - this.buffers.reduce((total, memPool) => total + memPool.stats().available, 0);\n\t}\n\tget totalAllocated() {\n\t\treturn this.buffers[0].buf.byteLength * this.buffers.length;\n\t}\n\n\tgetSharedMemory(): MemoryHeapMemory {\n\t\treturn {\n\t\t\tbuffers: this.buffers.map(buffer => buffer.buf as SharedArrayBuffer),\n\t\t};\n\t}\n}\n\ntype OnGrowBuffer = (newBuffer: GrowBufferData) => void;\ninterface GrowBufferData {\n\tbufferPosition: number\n\tbuffer: SharedArrayBuffer\n}\n\ninterface MemoryHeapConfig {\n\tbufferSize?: number\n\tinitialBuffers?: number\n\tautoGrowSize?: number\n}\ninterface MemoryHeapMemory {\n\tbuffers: Array<SharedArrayBuffer>\n}\n\nexport type { MemoryHeapConfig, MemoryHeapMemory, GrowBufferData };"],"mappings":"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"}
1
+ {"version":3,"file":"memory-heap.js","names":[],"sources":["../src/memory-heap.ts"],"sourcesContent":["import AllocatedMemory, { type AllocatedMemoryPointer, type SharedAllocatedMemory } from './allocated-memory';\nimport { MAX_BYTE_OFFSET_LENGTH, positionBitsForBufferSize } from './utils/pointer';\nimport MemoryBuffer, { FIRST_BLOCK_DATA_BYTE_OFFSET, SIZEOF_MEM_BLOCK, SIZEOF_STATE } from './memory-buffer';\n\nconst DEFAULT_BUFFER_SIZE = MAX_BYTE_OFFSET_LENGTH;\nconst MAX_BUFFER_SIZE = Math.pow(2, 31);\nconst BUFFER_SIZE_INDEX = 0;\nconst BUFFER_COUNT_INDEX = 1;\nconst BUFFER_AUTO_GROW_INDEX = 2;\nconst BUFFER_POSITION_BITS_INDEX = 3;\nconst HEAP_STATE_COUNT = 4;\nexport default class MemoryHeap {\n\tbuffers: Array<MemoryBuffer>;\n\tprivate onGrowBufferHandlers: Array<OnGrowBuffer> = [];\n\tisClone: boolean;\n\tprivate memory: AllocatedMemory;\n\n\t// Per-heap (per-thread) hint of the last buffer we allocated into\n\tprivate allocCursor = 0;\n\n\tpositionBits: number;\n\t// Max buffer position (count) and max byte offset the split can address\n\tget maxBuffers() {\n\t\treturn Math.pow(2, this.positionBits);\n\t}\n\n\tget bufferSize() {\n\t\treturn this.memory.data[BUFFER_SIZE_INDEX];\n\t}\n\n\t// Largest u32 count a single fresh buffer can hand out in one allocation: the buffer minus the allocator state and the\n\t// block header. A structure that grows in segments must keep each segment within this or the allocation can never fit.\n\tget maxAllocationLength() {\n\t\treturn Math.floor((this.bufferSize - SIZEOF_STATE - SIZEOF_MEM_BLOCK) / Uint32Array.BYTES_PER_ELEMENT);\n\t}\n\n\tconstructor(config?: MemoryHeapConfig | MemoryHeapMemory) {\n\t\tif(config && 'buffers' in config) {\n\t\t\tthis.buffers = config.buffers.map(buffer => {\n\t\t\t\treturn new MemoryBuffer({\n\t\t\t\t\tbuf: buffer,\n\t\t\t\t\tskipInitialization: true,\n\t\t\t\t});\n\t\t\t});\n\n\t\t\t// The heap state block is always the first allocation, so its data sits at this fixed, header-derived offset.\n\t\t\tthis.memory = new AllocatedMemory(this, {\n\t\t\t\tbufferPosition: 0,\n\t\t\t\tbufferByteOffset: FIRST_BLOCK_DATA_BYTE_OFFSET,\n\t\t\t} satisfies AllocatedMemoryPointer);\n\t\t\tthis.positionBits = this.memory.data[BUFFER_POSITION_BITS_INDEX];\n\t\t\tthis.isClone = true;\n\t\t} else {\n\t\t\tif(!('SharedArrayBuffer' in globalThis)) {\n\t\t\t\tconsole.warn('SharedArrayBuffer is not working: falling back to ArrayBuffer');\n\t\t\t}\n\n\t\t\tconst bufferSize = config?.bufferSize ?? DEFAULT_BUFFER_SIZE;\n\t\t\tif(bufferSize > MAX_BUFFER_SIZE) {\n\t\t\t\tthrow new Error(`Buffer size ${bufferSize} is greater than max ${MAX_BUFFER_SIZE} that we can reference with pointers`);\n\t\t\t}\n\t\t\tthis.positionBits = positionBitsForBufferSize(bufferSize);\n\n\t\t\tlet startBuffer = this.createBuffer(bufferSize);\n\t\t\tthis.buffers = [\n\t\t\t\tstartBuffer,\n\t\t\t];\n\t\t\tconst data = startBuffer.callocAs('u32', HEAP_STATE_COUNT);\n\t\t\tif(data) {\n\t\t\t\tthis.memory = new AllocatedMemory(this, {\n\t\t\t\t\tbufferPosition: 0,\n\t\t\t\t\tbufferByteOffset: data.byteOffset,\n\t\t\t\t});\n\t\t\t} else {\n\t\t\t\tthrow new Error('Failed to initialize first byte from buffer');\n\t\t\t}\n\t\t\tthis.memory.data[BUFFER_SIZE_INDEX] = bufferSize;\n\t\t\tthis.memory.data[BUFFER_AUTO_GROW_INDEX] = config?.autoGrowSize ?? 100;\n\t\t\tthis.memory.data[BUFFER_POSITION_BITS_INDEX] = this.positionBits;\n\t\t\tthis.isClone = false;\n\n\t\t\tfor(let i = 1; i < (config?.initialBuffers ?? 1); i++) {\n\t\t\t\tthis.buffers.push(this.createBuffer(bufferSize));\n\t\t\t}\n\t\t\tthis.memory.data[BUFFER_COUNT_INDEX] = this.buffers.length;\n\t\t}\n\t}\n\n\taddSharedBuffer(data: GrowBufferData) {\n\t\tthis.buffers[data.bufferPosition] = new MemoryBuffer({\n\t\t\tbuf: data.buffer,\n\t\t\tskipInitialization: true,\n\t\t});\n\t}\n\n\tprivate growBuffer() {\n\t\tconst buffer = this.createBuffer();\n\t\tlet nextBufferPosition = Atomics.add(this.memory.data, BUFFER_COUNT_INDEX, 1);\n\t\t// Setting index set by internal Atomic count so we can create new buffers from multiple threads and keep position consistent\n\t\tthis.buffers[nextBufferPosition] = buffer;\n\t\tthis.onGrowBufferHandlers.forEach(handler => handler({\n\t\t\tbufferPosition: nextBufferPosition,\n\t\t\tbuffer: buffer.buf as SharedArrayBuffer,\n\t\t}));\n\n\t\treturn buffer;\n\t}\n\tprivate createBuffer(bufferSize?: number): MemoryBuffer {\n\t\tconst usedBufferSize = bufferSize ?? this.bufferSize;\n\t\tlet buf: ArrayBuffer | SharedArrayBuffer;\n\t\tif('SharedArrayBuffer' in globalThis) {\n\t\t\tbuf = new SharedArrayBuffer(usedBufferSize);\n\t\t} else {\n\t\t\tbuf = new ArrayBuffer(usedBufferSize);\n\t\t}\n\n\t\treturn new MemoryBuffer({\n\t\t\tbuf,\n\n\t\t\t// We can't use this unless we can 100% guarantee that every thread will stop using memory the instant it is freed\n\t\t\t// ex: Allocate 16 bytes. Thread A frees that allocation and then allocates 12 bytes and 4 bytes, but Thread B is mid-execution\n\t\t\t// on the old allocation can changes the internal state of the 4-byte allocation breaking everything\n\t\t\t// After the internal state is wrong MemoryBuffer will loose track of which blocks are where and how big they are\n\t\t\tcompact: false,\n\t\t\tsplit: false,\n\t\t});\n\t}\n\n\taddOnGrowBufferHandlers(handler: OnGrowBuffer) {\n\t\tthis.onGrowBufferHandlers.push(handler);\n\t}\n\n\t// Guarantees at least one completely-empty buffer exists and (via the grow handlers) has been fanned out to every\n\t// thread. Call this on the owning thread before dispatching work that allocates on another thread (a worker): that\n\t// allocation then lands in a buffer everyone already holds instead of forcing growBuffer on the worker, which would\n\t// create a buffer only the worker can see and leave every other thread unable to resolve pointers into it. Returns\n\t// true if it had to grow one.\n\tensureSpareBuffer(): boolean {\n\t\tfor(let i = 0; i < this.buffers.length; i++) {\n\t\t\tconst buffer = this.buffers[i];\n\t\t\tif(buffer && buffer.isEmpty) {\n\t\t\t\treturn false;\n\t\t\t}\n\t\t}\n\n\t\tthis.growBuffer();\n\t\treturn true;\n\t}\n\n\tallocUI32(count: number): AllocatedMemory {\n\t\tcount = Math.ceil(count);\n\t\tconst bufferCount = this.buffers.length;\n\t\tfor(let n = 0; n < bufferCount; n++) {\n\t\t\tconst i = (this.allocCursor + n) % bufferCount;\n\t\t\tconst buffer = this.buffers[i];\n\t\t\t// Should just mean we haven't synced this buffer from another thread yet\n\t\t\tif(!buffer) {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\t// Should be fine to initialize all values as 0s since unsigned/signed ints and floats all store 0 as all 0s\n\t\t\tconst data = buffer.callocAs('u32', count);\n\t\t\tif(data) {\n\t\t\t\tthis.allocCursor = i;\n\n\t\t\t\t// Auto grow when nearly full when we need buffer to already be sync'd between threads BEFORE we try to use it\n\t\t\t\tif(\n\t\t\t\t\ti === (this.buffers.length - 1)\n\t\t\t\t\t&& Atomics.load(this.memory.data, BUFFER_COUNT_INDEX) === this.buffers.length\n\t\t\t\t\t&& this.memory.data[BUFFER_AUTO_GROW_INDEX] < 100\n\t\t\t\t\t&& this.memory.data[BUFFER_AUTO_GROW_INDEX] > 0\n\t\t\t\t) {\n\t\t\t\t\tconst percentFull = buffer.top / buffer.end;\n\t\t\t\t\tif(percentFull > (this.memory.data[BUFFER_AUTO_GROW_INDEX] / 100)) {\n\t\t\t\t\t\tthis.growBuffer();\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\treturn new AllocatedMemory(this, {\n\t\t\t\t\tdata,\n\t\t\t\t\tbuffer,\n\t\t\t\t});\n\t\t\t}\n\t\t}\n\n\t\tif(this.buffers.length >= this.maxBuffers) {\n\t\t\tthrow new Error(`Can't initialize a new buffer since it would have a position greater than the max of ${this.maxBuffers}`);\n\t\t}\n\n\t\t// If we get here we need to grow another buffer to continue allocating new memory\n\t\tlet buffer = this.growBuffer();\n\t\tconst data = buffer.callocAs('u32', count);\n\t\tif(data) {\n\t\t\tthis.allocCursor = this.buffers.indexOf(buffer);\n\t\t\treturn new AllocatedMemory(this, {\n\t\t\t\tdata,\n\t\t\t\tbuffer,\n\t\t\t});\n\t\t} else {\n\t\t\tthrow new Error(`Unable to allocate ${count} numbers even after adding a new buffer`);\n\t\t}\n\t}\n\n\tgetSharedAlloc(shared: SharedAllocatedMemory): AllocatedMemory | undefined {\n\t\t// Should just mean it hasn't synced to this thread yet\n\t\tif(this.buffers[shared.bufferPosition] === undefined) {\n\t\t\treturn undefined;\n\t\t}\n\n\t\treturn new AllocatedMemory(this, shared);\n\t}\n\n\tget currentUsed() {\n\t\treturn this.totalAllocated - this.buffers.reduce((total, memPool) => total + memPool.stats().available, 0);\n\t}\n\tget totalAllocated() {\n\t\treturn this.buffers[0].buf.byteLength * this.buffers.length;\n\t}\n\n\tgetSharedMemory(): MemoryHeapMemory {\n\t\treturn {\n\t\t\tbuffers: this.buffers.map(buffer => buffer.buf as SharedArrayBuffer),\n\t\t};\n\t}\n}\n\ntype OnGrowBuffer = (newBuffer: GrowBufferData) => void;\ninterface GrowBufferData {\n\tbufferPosition: number\n\tbuffer: SharedArrayBuffer\n}\n\ninterface MemoryHeapConfig {\n\tbufferSize?: number\n\tinitialBuffers?: number\n\tautoGrowSize?: number\n}\ninterface MemoryHeapMemory {\n\tbuffers: Array<SharedArrayBuffer>\n}\n\nexport type { MemoryHeapConfig, MemoryHeapMemory, GrowBufferData };\n"],"mappings":"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"}
@@ -51,7 +51,10 @@ var u = 0, d = 1, f = 2, p = 16, m = 3, h = 4, g = 0, _ = 1, v = 2, y = 3, b = 4
51
51
  }
52
52
  }
53
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, t.firstBlock) : (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);
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);
55
58
  }
56
59
  initialize(e) {
57
60
  let n = e && "type" in e ? e.type : void 0, a = r(n ?? Uint32Array);
@@ -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\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, 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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"}
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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"}
@@ -56,7 +56,10 @@ var u = 0, d = 1, f = 2, p = 3, m = 4, h = 5, g = 100, _ = 1e6, v = 1e5, y = cla
56
56
  return t + this.pointerStack.usedMemory + this.recycleStack.usedMemory;
57
57
  }
58
58
  constructor(n, i) {
59
- this.memory = n, i && "firstBlock" in i ? (this.firstBlock = i.firstBlock instanceof r ? i.firstBlock : new r(n, i.firstBlock), this.uint16Array = new Uint16Array(this.firstBlock.data.buffer, this.firstBlock.bufferByteOffset + d * Uint32Array.BYTES_PER_ELEMENT, 2), "type" in i || "dataLength" in i || "maxLength" in i || "maxChunkSize" in i || "maxRecycledLength" in i ? (this.initialize(i), this.ownsFirstBlock = !1) : (this.pointerStack = new l(n, { firstBlock: e(t(this.firstBlock.data, m), n.positionBits) }), this.recycleStack = new l(n, { firstBlock: e(t(this.firstBlock.data, h), n.positionBits) }), this.ownsFirstBlock = !0)) : (this.firstBlock = n.allocUI32(y.getAllocateCount(n, i)), this.uint16Array = new Uint16Array(this.firstBlock.data.buffer, this.firstBlock.bufferByteOffset + d * Uint32Array.BYTES_PER_ELEMENT, 2), this.initialize(i), this.ownsFirstBlock = !0), this.cachedType = this.uint16Array[0], this.cachedDataLength = Math.max(1, this.uint16Array[1]), this.cachedMaxChunkSize = this.firstBlock.data[f], this.cachedByteMultipler = a(this.cachedType), this.maxLength = this.cachedMaxChunkSize * this.pointerStack.maxLength, this.growLock = new Int32Array(this.firstBlock.data.buffer, this.firstBlock.bufferByteOffset + p * Uint32Array.BYTES_PER_ELEMENT, 1);
59
+ this.memory = n, i && "firstBlock" in i ? (this.firstBlock = i.firstBlock instanceof r ? i.firstBlock : new r(n, {
60
+ length: y.ALLOCATE_COUNT,
61
+ ...i.firstBlock
62
+ }), this.uint16Array = new Uint16Array(this.firstBlock.data.buffer, this.firstBlock.bufferByteOffset + d * Uint32Array.BYTES_PER_ELEMENT, 2), "type" in i || "dataLength" in i || "maxLength" in i || "maxChunkSize" in i || "maxRecycledLength" in i ? (this.initialize(i), this.ownsFirstBlock = !1) : (this.pointerStack = new l(n, { firstBlock: e(t(this.firstBlock.data, m), n.positionBits) }), this.recycleStack = new l(n, { firstBlock: e(t(this.firstBlock.data, h), n.positionBits) }), this.ownsFirstBlock = !0)) : (this.firstBlock = n.allocUI32(y.getAllocateCount(n, i)), this.uint16Array = new Uint16Array(this.firstBlock.data.buffer, this.firstBlock.bufferByteOffset + d * Uint32Array.BYTES_PER_ELEMENT, 2), this.initialize(i), this.ownsFirstBlock = !0), this.cachedType = this.uint16Array[0], this.cachedDataLength = Math.max(1, this.uint16Array[1]), this.cachedMaxChunkSize = this.firstBlock.data[f], this.cachedByteMultipler = a(this.cachedType), this.maxLength = this.cachedMaxChunkSize * this.pointerStack.maxLength, this.growLock = new Int32Array(this.firstBlock.data.buffer, this.firstBlock.bufferByteOffset + p * Uint32Array.BYTES_PER_ELEMENT, 1);
60
63
  }
61
64
  initialize(e) {
62
65
  let t = e?.dataLength ?? 1, r = e?.maxChunkSize ?? g, o = e?.maxLength ?? _, s = e?.maxRecycledLength ?? v, c = l.getMaxSegmentLength(this.memory.maxAllocationLength), u = o / r, d = l.getAllocateCount(u, void 0, c), f = l.getAllocateCount(s, void 0, c);
@@ -1 +1 @@
1
- {"version":3,"file":"shared-pool.js","names":[],"sources":["../src/shared-pool.ts"],"sourcesContent":["import type { SharedAllocatedMemory } from './allocated-memory';\nimport AllocatedMemory from './allocated-memory';\nimport { lock, unlock } from './lock/simple-lock';\nimport type MemoryHeap from './memory-heap';\nimport SharedStack from './shared-stack';\nimport { getPointer, loadRawPointer, storeRawPointer } from './utils/pointer';\nimport { getArrayTypeCode, getByteMultipler, makeArrayView, type NumericArray, type NumericArrayIO, type TypedArrayConstructor } from './utils/array-type';\n\nconst LENGTH_INDEX = 0;\nconst TYPE_INDEX = 1;\nconst MAX_CHUNK_SIZE_INDEX = 2;\n// Guards appending a chunk pointer so only one thread grows the pointer stack at a time\nconst GROW_LOCK_INDEX = 3;\n// Pointers to the two backing SharedStacks (their own firstBlocks): the chunk pointers and the recycled indexes\nconst POINTER_STACK_INDEX = 4;\nconst RECYCLE_STACK_INDEX = 5;\n\nconst DEFAULT_MAX_CHUNK_SIZE = 100;\nconst DEFAULT_MAX_LENGTH = 1_000_000;\nconst DEFAULT_MAX_RECYCLED_LENGTH = 100_000;\n\n// Array with stable indexes and maximum contiguous memory sizes (necessary to fit large data sets into max 1MB buffers).\n// The chunk pointers and the recycled indexes both live in SharedStacks, whose segments are never moved or freed once\n// published, so concurrent readers never touch memory that is being reclaimed.\n// https://plflib.org/colony.htm for future enhancements - it seems to be an optimized version of what we were aiming for with this\nexport default class SharedPool<T extends NumericArray = Uint32Array> implements Iterable<T> {\n\tstatic readonly ALLOCATE_COUNT = 6;\n\n\t// u32s a pool with this config needs for its firstBlock, including both inlined stacks. Lets an owner (e.g.\n\t// SharedQuadtree) reserve one contiguous slice and hand it back through the config's firstBlock to inline the pool.\n\tstatic getAllocateCount(memory: MemoryHeap, config?: SharedPoolConfig<NumericArray>): number {\n\t\tlet maxChunkSize = config?.maxChunkSize ?? DEFAULT_MAX_CHUNK_SIZE;\n\t\tlet maxLength = config?.maxLength ?? DEFAULT_MAX_LENGTH;\n\t\tlet maxRecycledLength = config?.maxRecycledLength ?? DEFAULT_MAX_RECYCLED_LENGTH;\n\t\tlet maxSegmentLength = SharedStack.getMaxSegmentLength(memory.maxAllocationLength);\n\t\tlet pointerStackAllocateCount = SharedStack.getAllocateCount(maxLength / maxChunkSize, undefined, maxSegmentLength);\n\t\tlet recycleStackAllocateCount = SharedStack.getAllocateCount(maxRecycledLength, undefined, maxSegmentLength);\n\t\treturn SharedPool.ALLOCATE_COUNT + pointerStackAllocateCount + recycleStackAllocateCount;\n\t}\n\n\tprivate memory: MemoryHeap;\n\n\t// Current Length, Type/DataLength, MaxChunkLength, Grow lock, Pointer stack, Recycle stack\n\tprivate firstBlock: AllocatedMemory;\n\tprivate uint16Array: Uint16Array;\n\tprivate growLock: Int32Array;\n\tprivate pointerStack!: SharedStack;\n\tprivate recycleStack!: SharedStack;\n\treadonly maxLength: number;\n\n\t// False when firstBlock is a slice of another structure's memory (e.g. inlined in SharedQuadtree); that owner frees it\n\tprivate ownsFirstBlock: boolean;\n\n\t// Chunk views cached by pointer index. An array (dense, small integer keys) resolves faster than an object on the hot\n\t// get/set path and never needs rehashing.\n\tprivate cachedFullDataBlock: Array<T | undefined> = [];\n\n\t// Config is written once at construction and never mutated, so it is cached in plain fields\n\tprivate readonly cachedMaxChunkSize: number;\n\tprivate readonly cachedDataLength: number;\n\tprivate readonly cachedType: number;\n\tprivate readonly cachedByteMultipler: number;\n\n\tget length(): number {\n\t\treturn Atomics.load(this.firstBlock.data, LENGTH_INDEX) - this.recycleStack.length;\n\t}\n\tget maxChunkSize(): number {\n\t\treturn this.cachedMaxChunkSize;\n\t}\n\tprivate set maxChunkSize(value: number) {\n\t\tAtomics.store(this.firstBlock.data, MAX_CHUNK_SIZE_INDEX, value);\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\tget bufferLength(): number {\n\t\treturn this.cachedMaxChunkSize * this.pointerStack.length;\n\t}\n\tget byteMultipler(): number {\n\t\treturn this.cachedByteMultipler;\n\t}\n\n\t// Own internal memory (which already includes both stacks' inlined firstBlocks) plus every chunk buffer and the\n\t// segments each stack has grown. The stacks' usedMemory excludes their inlined firstBlocks, so they aren't double-counted.\n\tget usedMemory(): number {\n\t\tlet total = this.ownsFirstBlock ? this.firstBlock.usedMemory : 0;\n\t\tfor(let pointer of this.pointerStack) {\n\t\t\ttotal += new AllocatedMemory(this.memory, getPointer(pointer, this.memory.positionBits)).usedMemory;\n\t\t}\n\t\treturn total + this.pointerStack.usedMemory + this.recycleStack.usedMemory;\n\t}\n\n\tconstructor(memory: MemoryHeap, config?: SharedPoolConfig<T> | SharedPoolMemory | SharedPoolMemory & SharedPoolConfig<T>) {\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); a plain pointer is the\n\t\t\t// serialized clone path. Taking the instance directly keeps us off the pointer-resolving construction branch.\n\t\t\tthis.firstBlock = config.firstBlock instanceof AllocatedMemory ? config.firstBlock : new AllocatedMemory(memory, config.firstBlock);\n\t\t\tthis.uint16Array = new Uint16Array(this.firstBlock.data.buffer, this.firstBlock.bufferByteOffset + TYPE_INDEX * Uint32Array.BYTES_PER_ELEMENT, 2);\n\n\t\t\t// Memory passed together with config means the caller reserved the firstBlock slot for us to initialize in place\n\t\t\tif('type' in config || 'dataLength' in config || 'maxLength' in config || 'maxChunkSize' in config || 'maxRecycledLength' in config) {\n\t\t\t\tthis.initialize(config);\n\t\t\t\tthis.ownsFirstBlock = false;\n\t\t\t} else {\n\t\t\t\tthis.pointerStack = new SharedStack(memory, {\n\t\t\t\t\tfirstBlock: getPointer(loadRawPointer(this.firstBlock.data, POINTER_STACK_INDEX), memory.positionBits),\n\t\t\t\t});\n\t\t\t\tthis.recycleStack = new SharedStack(memory, {\n\t\t\t\t\tfirstBlock: getPointer(loadRawPointer(this.firstBlock.data, RECYCLE_STACK_INDEX), memory.positionBits),\n\t\t\t\t});\n\t\t\t\tthis.ownsFirstBlock = true;\n\t\t\t}\n\t\t} else {\n\t\t\tthis.firstBlock = memory.allocUI32(SharedPool.getAllocateCount(memory, config));\n\t\t\tthis.uint16Array = new Uint16Array(this.firstBlock.data.buffer, this.firstBlock.bufferByteOffset + TYPE_INDEX * Uint32Array.BYTES_PER_ELEMENT, 2);\n\t\t\tthis.initialize(config);\n\t\t\tthis.ownsFirstBlock = true;\n\t\t}\n\n\t\tthis.cachedType = this.uint16Array[0];\n\t\tthis.cachedDataLength = Math.max(1, this.uint16Array[1]);\n\t\tthis.cachedMaxChunkSize = this.firstBlock.data[MAX_CHUNK_SIZE_INDEX];\n\t\tthis.cachedByteMultipler = getByteMultipler(this.cachedType);\n\t\tthis.maxLength = this.cachedMaxChunkSize * this.pointerStack.maxLength;\n\n\t\tthis.growLock = new Int32Array(this.firstBlock.data.buffer, this.firstBlock.bufferByteOffset + GROW_LOCK_INDEX * Uint32Array.BYTES_PER_ELEMENT, 1);\n\t}\n\n\t// Lay out the two inlined stacks (their firstBlocks sliced from ours), record their pointers, and seed the first chunk.\n\t// firstBlock and uint16Array must already be set. Used by both the fresh-alloc path and the inline-in-place path.\n\tprivate initialize(config?: SharedPoolConfig<T>) {\n\t\tlet dataLength = config?.dataLength ?? 1;\n\t\tlet maxChunkSize = config?.maxChunkSize ?? DEFAULT_MAX_CHUNK_SIZE;\n\t\tlet maxLength = config?.maxLength ?? DEFAULT_MAX_LENGTH;\n\t\tlet maxRecycledLength = config?.maxRecycledLength ?? DEFAULT_MAX_RECYCLED_LENGTH;\n\n\t\t// Each stack sizes its inlined firstBlock from its own maxLength, so compute both up front to lay them out. The\n\t\t// stacks cap their segments at what a buffer can hold, so size the inlined blocks against that same cap.\n\t\tlet maxSegmentLength = SharedStack.getMaxSegmentLength(this.memory.maxAllocationLength);\n\t\tlet pointerStackMaxLength = maxLength / maxChunkSize;\n\t\tlet pointerStackAllocateCount = SharedStack.getAllocateCount(pointerStackMaxLength, undefined, maxSegmentLength);\n\t\tlet recycleStackAllocateCount = SharedStack.getAllocateCount(maxRecycledLength, undefined, maxSegmentLength);\n\n\t\t// Inline both stacks' firstBlocks right after the pool header so the whole spine is one contiguous allocation\n\t\tthis.pointerStack = new SharedStack(this.memory, {\n\t\t\tfirstBlock: this.firstBlock.getSubAllocation(SharedPool.ALLOCATE_COUNT, pointerStackAllocateCount),\n\t\t\ttype: Uint32Array,\n\t\t\tmaxLength: pointerStackMaxLength,\n\t\t});\n\t\tstoreRawPointer(this.firstBlock.data, POINTER_STACK_INDEX, this.pointerStack.pointer);\n\t\tthis.recycleStack = new SharedStack(this.memory, {\n\t\t\tfirstBlock: this.firstBlock.getSubAllocation(SharedPool.ALLOCATE_COUNT + pointerStackAllocateCount, recycleStackAllocateCount),\n\t\t\ttype: Uint32Array,\n\t\t\tmaxLength: maxRecycledLength,\n\t\t});\n\t\tstoreRawPointer(this.firstBlock.data, RECYCLE_STACK_INDEX, this.recycleStack.pointer);\n\n\t\tlet typeCode = getArrayTypeCode(config?.type ?? Uint32Array);\n\t\tthis.type = typeCode;\n\t\tthis.dataLength = dataLength;\n\t\tthis.maxChunkSize = maxChunkSize;\n\n\t\tlet byteMultipler = getByteMultipler(typeCode);\n\t\tlet firstArray = this.memory.allocUI32(maxChunkSize * dataLength * byteMultipler);\n\t\tthis.pointerStack.push(firstArray.pointer);\n\t}\n\n\tat(index: number): T {\n\t\tlet dataBlock = this.getFullDataBlock(index);\n\t\treturn this.getDataBlock(dataBlock, index % this.cachedMaxChunkSize);\n\t}\n\tget(index: number, dataIndex = 0): T[number] {\n\t\tconst dataLength = this.cachedDataLength;\n\t\tif(dataIndex >= dataLength) {\n\t\t\tthrow new Error(`${dataIndex} is out of dataLength bounds ${dataLength}`);\n\t\t}\n\n\t\tlet dataBlock = this.getFullDataBlock(index) as NumericArrayIO;\n\t\treturn dataBlock[(index % this.cachedMaxChunkSize) * dataLength + dataIndex];\n\t}\n\tset(index: number, dataIndex: number, value: T[number]) {\n\t\tconst dataLength = this.cachedDataLength;\n\t\tif(dataIndex >= dataLength) {\n\t\t\tthrow new Error(`${dataIndex} is out of dataLength bounds ${dataLength}`);\n\t\t}\n\n\t\tlet dataBlock = this.getFullDataBlock(index) as NumericArrayIO;\n\t\tdataBlock[(index % this.cachedMaxChunkSize) * dataLength + dataIndex] = value;\n\t}\n\n\t// Resolve the chunk view holding an index once. Callers touching several fields of the same record read/write straight\n\t// into this view at blockOffset(index) + fieldIndex, avoiding a repeated chunk lookup per field.\n\tblockFor(index: number): T {\n\t\treturn this.getFullDataBlock(index);\n\t}\n\t// Base slot of a record within the view blockFor(index) returns.\n\tblockOffset(index: number): number {\n\t\treturn (index % this.cachedMaxChunkSize) * this.cachedDataLength;\n\t}\n\n\tpush(values: T[number] | Array<T[number]>): number {\n\t\tlet dataLength = this.cachedDataLength;\n\t\tconst isSingleValue = typeof values !== 'object';\n\t\tif(!isSingleValue && 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\n\t\t// Reuse a recycled index if one is available. pop() can come back undefined when another thread drained the last\n\t\t// slot between our two calls, so fall through to a fresh index rather than trusting a length() read\n\t\tlet newIndex = this.recycleStack.length === 0 ? undefined : this.recycleStack.pop();\n\t\tif(newIndex === undefined) {\n\t\t\tnewIndex = Atomics.add(this.firstBlock.data, LENGTH_INDEX, 1);\n\t\t}\n\n\t\tlet dataBlock = this.getFullDataBlock(newIndex) as NumericArrayIO;\n\t\tlet startIndex = dataLength * (newIndex % this.cachedMaxChunkSize);\n\t\tif(isSingleValue) {\n\t\t\tdataBlock[startIndex] = values;\n\t\t} else {\n\t\t\tfor(let i = 0; i < values.length; i++) {\n\t\t\t\tdataBlock[startIndex + i] = values[i];\n\t\t\t}\n\t\t}\n\n\t\treturn newIndex;\n\t}\n\n\treserveContiguous(count: number): number {\n\t\tif(!Number.isInteger(count) || count < 0) {\n\t\t\tthrow new Error(`Can't reserve ${count} pool entries`);\n\t\t}\n\t\treturn Atomics.add(this.firstBlock.data, LENGTH_INDEX, count);\n\t}\n\n\tdeleteIndex(index: number) {\n\t\tthis.recycleStack.push(index);\n\t}\n\n\tclear() {\n\t\tthis.firstBlock.data[LENGTH_INDEX] = 0;\n\t\tthis.recycleStack.clear();\n\t}\n\n\t*[Symbol.iterator]() {\n\t\tconst recycledValues: { [key: number]: true } = {};\n\t\tfor(let value of this.recycleStack) {\n\t\t\trecycledValues[value] = true;\n\t\t}\n\n\t\tconst maxChunkSize = this.cachedMaxChunkSize;\n\t\t// Snapshot the length once - iteration over a concurrently mutated pool is a snapshot anyway, and re-loading the\n\t\t// atomic every step is pure overhead\n\t\tconst length = Atomics.load(this.firstBlock.data, LENGTH_INDEX);\n\t\tlet dataBlock = this.getFullDataBlock(0);\n\t\tlet dataBlockIndex = 0;\n\t\tfor(let i = 0; i < length; i++) {\n\t\t\tif(!recycledValues[i]) {\n\t\t\t\tlet newDataBlockIndex = Math.floor(i / maxChunkSize);\n\t\t\t\tif(newDataBlockIndex !== dataBlockIndex) {\n\t\t\t\t\tdataBlock = this.getFullDataBlock(i);\n\t\t\t\t\tdataBlockIndex = newDataBlockIndex;\n\t\t\t\t}\n\t\t\t\tyield this.getDataBlock(dataBlock, i % maxChunkSize);\n\t\t\t}\n\t\t}\n\t}\n\n\t*entries(): IterableIterator<[number, T]> {\n\t\tconst recycledValues: { [key: number]: true } = {};\n\t\tfor(let value of this.recycleStack) {\n\t\t\trecycledValues[value] = true;\n\t\t}\n\n\t\tconst maxChunkSize = this.cachedMaxChunkSize;\n\t\tconst length = Atomics.load(this.firstBlock.data, LENGTH_INDEX);\n\t\tlet dataBlock = this.getFullDataBlock(0);\n\t\tlet dataBlockIndex = 0;\n\t\tfor(let i = 0; i < length; i++) {\n\t\t\tif(!recycledValues[i]) {\n\t\t\t\tlet newDataBlockIndex = Math.floor(i / maxChunkSize);\n\t\t\t\tif(newDataBlockIndex !== dataBlockIndex) {\n\t\t\t\t\tdataBlock = this.getFullDataBlock(i);\n\t\t\t\t\tdataBlockIndex = newDataBlockIndex;\n\t\t\t\t}\n\t\t\t\tyield [i, this.getDataBlock(dataBlock, i % maxChunkSize)];\n\t\t\t}\n\t\t}\n\t}\n\n\tprivate getFullDataBlock(index: number) {\n\t\tlet pointerIndex = Math.floor(index / this.cachedMaxChunkSize);\n\t\tlet cachedDataBlock = this.cachedFullDataBlock[pointerIndex];\n\t\tif(cachedDataBlock) {\n\t\t\treturn cachedDataBlock;\n\t\t}\n\t\tif(pointerIndex >= this.pointerStack.length) {\n\t\t\t// Only let a single thread append a new chunk at a time - otherwise two concurrent inserts landing in a new\n\t\t\t// chunk would each allocate + push a buffer, orphaning one and leaking memory\n\t\t\tlock(this.growLock);\n\t\t\ttry {\n\t\t\t\t// Re-check under the lock: another thread may have already appended the chunk while we were waiting\n\t\t\t\twhile(pointerIndex >= this.pointerStack.length) {\n\t\t\t\t\tlet newArray = this.memory.allocUI32(this.cachedMaxChunkSize * this.cachedDataLength * this.cachedByteMultipler);\n\t\t\t\t\tthis.pointerStack.push(newArray.pointer);\n\t\t\t\t}\n\t\t\t} finally {\n\t\t\t\tunlock(this.growLock);\n\t\t\t}\n\t\t}\n\n\t\tlet array = new AllocatedMemory(this.memory, getPointer(this.pointerStack.at(pointerIndex), this.memory.positionBits));\n\n\t\tlet blockLength = this.cachedDataLength * this.cachedMaxChunkSize;\n\t\tlet data = makeArrayView(this.cachedType, array.data.buffer, array.bufferByteOffset, blockLength) as T;\n\n\t\tthis.cachedFullDataBlock[pointerIndex] = data;\n\t\treturn data;\n\t}\n\n\tprivate getDataBlock(rawData: T, index: number): T {\n\t\tconst dataLength = this.cachedDataLength;\n\t\tconst start = index * dataLength;\n\t\treturn rawData.subarray(start, start + dataLength) as T;\n\t}\n\n\tfree() {\n\t\tthis.recycleStack.free();\n\n\t\tfor(let pointer of this.pointerStack) {\n\t\t\tlet memory = new AllocatedMemory(this.memory, getPointer(pointer, this.memory.positionBits));\n\t\t\tmemory.free();\n\t\t}\n\t\tthis.pointerStack.free();\n\t\tif(this.ownsFirstBlock) {\n\t\t\tthis.firstBlock.free();\n\t\t}\n\t}\n\n\tgetSharedMemory(): SharedPoolMemory {\n\t\treturn {\n\t\t\tfirstBlock: this.firstBlock.getSharedMemory(),\n\t\t};\n\t}\n}\n\ninterface SharedPoolConfig<T extends NumericArray> {\n\tmaxChunkSize?: number\n\tmaxLength?: number\n\tmaxRecycledLength?: number\n\ttype?: TypedArrayConstructor<T>\n\tdataLength?: number\n}\ninterface SharedPoolMemory {\n\tfirstBlock: SharedAllocatedMemory | AllocatedMemory\n}\n\nexport type { SharedPoolConfig, SharedPoolMemory };\n"],"mappings":"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"}
1
+ {"version":3,"file":"shared-pool.js","names":[],"sources":["../src/shared-pool.ts"],"sourcesContent":["import type { SharedAllocatedMemory } from './allocated-memory';\nimport AllocatedMemory from './allocated-memory';\nimport { lock, unlock } from './lock/simple-lock';\nimport type MemoryHeap from './memory-heap';\nimport SharedStack from './shared-stack';\nimport { getPointer, loadRawPointer, storeRawPointer } from './utils/pointer';\nimport { getArrayTypeCode, getByteMultipler, makeArrayView, type NumericArray, type NumericArrayIO, type TypedArrayConstructor } from './utils/array-type';\n\nconst LENGTH_INDEX = 0;\nconst TYPE_INDEX = 1;\nconst MAX_CHUNK_SIZE_INDEX = 2;\n// Guards appending a chunk pointer so only one thread grows the pointer stack at a time\nconst GROW_LOCK_INDEX = 3;\n// Pointers to the two backing SharedStacks (their own firstBlocks): the chunk pointers and the recycled indexes\nconst POINTER_STACK_INDEX = 4;\nconst RECYCLE_STACK_INDEX = 5;\n\nconst DEFAULT_MAX_CHUNK_SIZE = 100;\nconst DEFAULT_MAX_LENGTH = 1_000_000;\nconst DEFAULT_MAX_RECYCLED_LENGTH = 100_000;\n\n// Array with stable indexes and maximum contiguous memory sizes (necessary to fit large data sets into max 1MB buffers).\n// The chunk pointers and the recycled indexes both live in SharedStacks, whose segments are never moved or freed once\n// published, so concurrent readers never touch memory that is being reclaimed.\n// https://plflib.org/colony.htm for future enhancements - it seems to be an optimized version of what we were aiming for with this\nexport default class SharedPool<T extends NumericArray = Uint32Array> implements Iterable<T> {\n\tstatic readonly ALLOCATE_COUNT = 6;\n\n\t// u32s a pool with this config needs for its firstBlock, including both inlined stacks. Lets an owner (e.g.\n\t// SharedQuadtree) reserve one contiguous slice and hand it back through the config's firstBlock to inline the pool.\n\tstatic getAllocateCount(memory: MemoryHeap, config?: SharedPoolConfig<NumericArray>): number {\n\t\tlet maxChunkSize = config?.maxChunkSize ?? DEFAULT_MAX_CHUNK_SIZE;\n\t\tlet maxLength = config?.maxLength ?? DEFAULT_MAX_LENGTH;\n\t\tlet maxRecycledLength = config?.maxRecycledLength ?? DEFAULT_MAX_RECYCLED_LENGTH;\n\t\tlet maxSegmentLength = SharedStack.getMaxSegmentLength(memory.maxAllocationLength);\n\t\tlet pointerStackAllocateCount = SharedStack.getAllocateCount(maxLength / maxChunkSize, undefined, maxSegmentLength);\n\t\tlet recycleStackAllocateCount = SharedStack.getAllocateCount(maxRecycledLength, undefined, maxSegmentLength);\n\t\treturn SharedPool.ALLOCATE_COUNT + pointerStackAllocateCount + recycleStackAllocateCount;\n\t}\n\n\tprivate memory: MemoryHeap;\n\n\t// Current Length, Type/DataLength, MaxChunkLength, Grow lock, Pointer stack, Recycle stack\n\tprivate firstBlock: AllocatedMemory;\n\tprivate uint16Array: Uint16Array;\n\tprivate growLock: Int32Array;\n\tprivate pointerStack!: SharedStack;\n\tprivate recycleStack!: SharedStack;\n\treadonly maxLength: number;\n\n\t// False when firstBlock is a slice of another structure's memory (e.g. inlined in SharedQuadtree); that owner frees it\n\tprivate ownsFirstBlock: boolean;\n\n\t// Chunk views cached by pointer index. An array (dense, small integer keys) resolves faster than an object on the hot\n\t// get/set path and never needs rehashing.\n\tprivate cachedFullDataBlock: Array<T | undefined> = [];\n\n\t// Config is written once at construction and never mutated, so it is cached in plain fields\n\tprivate readonly cachedMaxChunkSize: number;\n\tprivate readonly cachedDataLength: number;\n\tprivate readonly cachedType: number;\n\tprivate readonly cachedByteMultipler: number;\n\n\tget length(): number {\n\t\treturn Atomics.load(this.firstBlock.data, LENGTH_INDEX) - this.recycleStack.length;\n\t}\n\tget maxChunkSize(): number {\n\t\treturn this.cachedMaxChunkSize;\n\t}\n\tprivate set maxChunkSize(value: number) {\n\t\tAtomics.store(this.firstBlock.data, MAX_CHUNK_SIZE_INDEX, value);\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\tget bufferLength(): number {\n\t\treturn this.cachedMaxChunkSize * this.pointerStack.length;\n\t}\n\tget byteMultipler(): number {\n\t\treturn this.cachedByteMultipler;\n\t}\n\n\t// Own internal memory (which already includes both stacks' inlined firstBlocks) plus every chunk buffer and the\n\t// segments each stack has grown. The stacks' usedMemory excludes their inlined firstBlocks, so they aren't double-counted.\n\tget usedMemory(): number {\n\t\tlet total = this.ownsFirstBlock ? this.firstBlock.usedMemory : 0;\n\t\tfor(let pointer of this.pointerStack) {\n\t\t\ttotal += new AllocatedMemory(this.memory, getPointer(pointer, this.memory.positionBits)).usedMemory;\n\t\t}\n\t\treturn total + this.pointerStack.usedMemory + this.recycleStack.usedMemory;\n\t}\n\n\tconstructor(memory: MemoryHeap, config?: SharedPoolConfig<T> | SharedPoolMemory | SharedPoolMemory & SharedPoolConfig<T>) {\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); a plain pointer is the\n\t\t\t// serialized clone path. Taking the instance directly keeps us off the pointer-resolving construction branch.\n\t\t\tthis.firstBlock = config.firstBlock instanceof AllocatedMemory\n\t\t\t\t? config.firstBlock\n\t\t\t\t: new AllocatedMemory(memory, { length: SharedPool.ALLOCATE_COUNT, ...config.firstBlock });\n\t\t\tthis.uint16Array = new Uint16Array(this.firstBlock.data.buffer, this.firstBlock.bufferByteOffset + TYPE_INDEX * Uint32Array.BYTES_PER_ELEMENT, 2);\n\n\t\t\t// Memory passed together with config means the caller reserved the firstBlock slot for us to initialize in place\n\t\t\tif('type' in config || 'dataLength' in config || 'maxLength' in config || 'maxChunkSize' in config || 'maxRecycledLength' in config) {\n\t\t\t\tthis.initialize(config);\n\t\t\t\tthis.ownsFirstBlock = false;\n\t\t\t} else {\n\t\t\t\tthis.pointerStack = new SharedStack(memory, {\n\t\t\t\t\tfirstBlock: getPointer(loadRawPointer(this.firstBlock.data, POINTER_STACK_INDEX), memory.positionBits),\n\t\t\t\t});\n\t\t\t\tthis.recycleStack = new SharedStack(memory, {\n\t\t\t\t\tfirstBlock: getPointer(loadRawPointer(this.firstBlock.data, RECYCLE_STACK_INDEX), memory.positionBits),\n\t\t\t\t});\n\t\t\t\tthis.ownsFirstBlock = true;\n\t\t\t}\n\t\t} else {\n\t\t\tthis.firstBlock = memory.allocUI32(SharedPool.getAllocateCount(memory, config));\n\t\t\tthis.uint16Array = new Uint16Array(this.firstBlock.data.buffer, this.firstBlock.bufferByteOffset + TYPE_INDEX * Uint32Array.BYTES_PER_ELEMENT, 2);\n\t\t\tthis.initialize(config);\n\t\t\tthis.ownsFirstBlock = true;\n\t\t}\n\n\t\tthis.cachedType = this.uint16Array[0];\n\t\tthis.cachedDataLength = Math.max(1, this.uint16Array[1]);\n\t\tthis.cachedMaxChunkSize = this.firstBlock.data[MAX_CHUNK_SIZE_INDEX];\n\t\tthis.cachedByteMultipler = getByteMultipler(this.cachedType);\n\t\tthis.maxLength = this.cachedMaxChunkSize * this.pointerStack.maxLength;\n\n\t\tthis.growLock = new Int32Array(this.firstBlock.data.buffer, this.firstBlock.bufferByteOffset + GROW_LOCK_INDEX * Uint32Array.BYTES_PER_ELEMENT, 1);\n\t}\n\n\t// Lay out the two inlined stacks (their firstBlocks sliced from ours), record their pointers, and seed the first chunk.\n\t// firstBlock and uint16Array must already be set. Used by both the fresh-alloc path and the inline-in-place path.\n\tprivate initialize(config?: SharedPoolConfig<T>) {\n\t\tlet dataLength = config?.dataLength ?? 1;\n\t\tlet maxChunkSize = config?.maxChunkSize ?? DEFAULT_MAX_CHUNK_SIZE;\n\t\tlet maxLength = config?.maxLength ?? DEFAULT_MAX_LENGTH;\n\t\tlet maxRecycledLength = config?.maxRecycledLength ?? DEFAULT_MAX_RECYCLED_LENGTH;\n\n\t\t// Each stack sizes its inlined firstBlock from its own maxLength, so compute both up front to lay them out. The\n\t\t// stacks cap their segments at what a buffer can hold, so size the inlined blocks against that same cap.\n\t\tlet maxSegmentLength = SharedStack.getMaxSegmentLength(this.memory.maxAllocationLength);\n\t\tlet pointerStackMaxLength = maxLength / maxChunkSize;\n\t\tlet pointerStackAllocateCount = SharedStack.getAllocateCount(pointerStackMaxLength, undefined, maxSegmentLength);\n\t\tlet recycleStackAllocateCount = SharedStack.getAllocateCount(maxRecycledLength, undefined, maxSegmentLength);\n\n\t\t// Inline both stacks' firstBlocks right after the pool header so the whole spine is one contiguous allocation\n\t\tthis.pointerStack = new SharedStack(this.memory, {\n\t\t\tfirstBlock: this.firstBlock.getSubAllocation(SharedPool.ALLOCATE_COUNT, pointerStackAllocateCount),\n\t\t\ttype: Uint32Array,\n\t\t\tmaxLength: pointerStackMaxLength,\n\t\t});\n\t\tstoreRawPointer(this.firstBlock.data, POINTER_STACK_INDEX, this.pointerStack.pointer);\n\t\tthis.recycleStack = new SharedStack(this.memory, {\n\t\t\tfirstBlock: this.firstBlock.getSubAllocation(SharedPool.ALLOCATE_COUNT + pointerStackAllocateCount, recycleStackAllocateCount),\n\t\t\ttype: Uint32Array,\n\t\t\tmaxLength: maxRecycledLength,\n\t\t});\n\t\tstoreRawPointer(this.firstBlock.data, RECYCLE_STACK_INDEX, this.recycleStack.pointer);\n\n\t\tlet typeCode = getArrayTypeCode(config?.type ?? Uint32Array);\n\t\tthis.type = typeCode;\n\t\tthis.dataLength = dataLength;\n\t\tthis.maxChunkSize = maxChunkSize;\n\n\t\tlet byteMultipler = getByteMultipler(typeCode);\n\t\tlet firstArray = this.memory.allocUI32(maxChunkSize * dataLength * byteMultipler);\n\t\tthis.pointerStack.push(firstArray.pointer);\n\t}\n\n\tat(index: number): T {\n\t\tlet dataBlock = this.getFullDataBlock(index);\n\t\treturn this.getDataBlock(dataBlock, index % this.cachedMaxChunkSize);\n\t}\n\tget(index: number, dataIndex = 0): T[number] {\n\t\tconst dataLength = this.cachedDataLength;\n\t\tif(dataIndex >= dataLength) {\n\t\t\tthrow new Error(`${dataIndex} is out of dataLength bounds ${dataLength}`);\n\t\t}\n\n\t\tlet dataBlock = this.getFullDataBlock(index) as NumericArrayIO;\n\t\treturn dataBlock[(index % this.cachedMaxChunkSize) * dataLength + dataIndex];\n\t}\n\tset(index: number, dataIndex: number, value: T[number]) {\n\t\tconst dataLength = this.cachedDataLength;\n\t\tif(dataIndex >= dataLength) {\n\t\t\tthrow new Error(`${dataIndex} is out of dataLength bounds ${dataLength}`);\n\t\t}\n\n\t\tlet dataBlock = this.getFullDataBlock(index) as NumericArrayIO;\n\t\tdataBlock[(index % this.cachedMaxChunkSize) * dataLength + dataIndex] = value;\n\t}\n\n\t// Resolve the chunk view holding an index once. Callers touching several fields of the same record read/write straight\n\t// into this view at blockOffset(index) + fieldIndex, avoiding a repeated chunk lookup per field.\n\tblockFor(index: number): T {\n\t\treturn this.getFullDataBlock(index);\n\t}\n\t// Base slot of a record within the view blockFor(index) returns.\n\tblockOffset(index: number): number {\n\t\treturn (index % this.cachedMaxChunkSize) * this.cachedDataLength;\n\t}\n\n\tpush(values: T[number] | Array<T[number]>): number {\n\t\tlet dataLength = this.cachedDataLength;\n\t\tconst isSingleValue = typeof values !== 'object';\n\t\tif(!isSingleValue && 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\n\t\t// Reuse a recycled index if one is available. pop() can come back undefined when another thread drained the last\n\t\t// slot between our two calls, so fall through to a fresh index rather than trusting a length() read\n\t\tlet newIndex = this.recycleStack.length === 0 ? undefined : this.recycleStack.pop();\n\t\tif(newIndex === undefined) {\n\t\t\tnewIndex = Atomics.add(this.firstBlock.data, LENGTH_INDEX, 1);\n\t\t}\n\n\t\tlet dataBlock = this.getFullDataBlock(newIndex) as NumericArrayIO;\n\t\tlet startIndex = dataLength * (newIndex % this.cachedMaxChunkSize);\n\t\tif(isSingleValue) {\n\t\t\tdataBlock[startIndex] = values;\n\t\t} else {\n\t\t\tfor(let i = 0; i < values.length; i++) {\n\t\t\t\tdataBlock[startIndex + i] = values[i];\n\t\t\t}\n\t\t}\n\n\t\treturn newIndex;\n\t}\n\n\treserveContiguous(count: number): number {\n\t\tif(!Number.isInteger(count) || count < 0) {\n\t\t\tthrow new Error(`Can't reserve ${count} pool entries`);\n\t\t}\n\t\treturn Atomics.add(this.firstBlock.data, LENGTH_INDEX, count);\n\t}\n\n\tdeleteIndex(index: number) {\n\t\tthis.recycleStack.push(index);\n\t}\n\n\tclear() {\n\t\tthis.firstBlock.data[LENGTH_INDEX] = 0;\n\t\tthis.recycleStack.clear();\n\t}\n\n\t*[Symbol.iterator]() {\n\t\tconst recycledValues: { [key: number]: true } = {};\n\t\tfor(let value of this.recycleStack) {\n\t\t\trecycledValues[value] = true;\n\t\t}\n\n\t\tconst maxChunkSize = this.cachedMaxChunkSize;\n\t\t// Snapshot the length once - iteration over a concurrently mutated pool is a snapshot anyway, and re-loading the\n\t\t// atomic every step is pure overhead\n\t\tconst length = Atomics.load(this.firstBlock.data, LENGTH_INDEX);\n\t\tlet dataBlock = this.getFullDataBlock(0);\n\t\tlet dataBlockIndex = 0;\n\t\tfor(let i = 0; i < length; i++) {\n\t\t\tif(!recycledValues[i]) {\n\t\t\t\tlet newDataBlockIndex = Math.floor(i / maxChunkSize);\n\t\t\t\tif(newDataBlockIndex !== dataBlockIndex) {\n\t\t\t\t\tdataBlock = this.getFullDataBlock(i);\n\t\t\t\t\tdataBlockIndex = newDataBlockIndex;\n\t\t\t\t}\n\t\t\t\tyield this.getDataBlock(dataBlock, i % maxChunkSize);\n\t\t\t}\n\t\t}\n\t}\n\n\t*entries(): IterableIterator<[number, T]> {\n\t\tconst recycledValues: { [key: number]: true } = {};\n\t\tfor(let value of this.recycleStack) {\n\t\t\trecycledValues[value] = true;\n\t\t}\n\n\t\tconst maxChunkSize = this.cachedMaxChunkSize;\n\t\tconst length = Atomics.load(this.firstBlock.data, LENGTH_INDEX);\n\t\tlet dataBlock = this.getFullDataBlock(0);\n\t\tlet dataBlockIndex = 0;\n\t\tfor(let i = 0; i < length; i++) {\n\t\t\tif(!recycledValues[i]) {\n\t\t\t\tlet newDataBlockIndex = Math.floor(i / maxChunkSize);\n\t\t\t\tif(newDataBlockIndex !== dataBlockIndex) {\n\t\t\t\t\tdataBlock = this.getFullDataBlock(i);\n\t\t\t\t\tdataBlockIndex = newDataBlockIndex;\n\t\t\t\t}\n\t\t\t\tyield [i, this.getDataBlock(dataBlock, i % maxChunkSize)];\n\t\t\t}\n\t\t}\n\t}\n\n\tprivate getFullDataBlock(index: number) {\n\t\tlet pointerIndex = Math.floor(index / this.cachedMaxChunkSize);\n\t\tlet cachedDataBlock = this.cachedFullDataBlock[pointerIndex];\n\t\tif(cachedDataBlock) {\n\t\t\treturn cachedDataBlock;\n\t\t}\n\t\tif(pointerIndex >= this.pointerStack.length) {\n\t\t\t// Only let a single thread append a new chunk at a time - otherwise two concurrent inserts landing in a new\n\t\t\t// chunk would each allocate + push a buffer, orphaning one and leaking memory\n\t\t\tlock(this.growLock);\n\t\t\ttry {\n\t\t\t\t// Re-check under the lock: another thread may have already appended the chunk while we were waiting\n\t\t\t\twhile(pointerIndex >= this.pointerStack.length) {\n\t\t\t\t\tlet newArray = this.memory.allocUI32(this.cachedMaxChunkSize * this.cachedDataLength * this.cachedByteMultipler);\n\t\t\t\t\tthis.pointerStack.push(newArray.pointer);\n\t\t\t\t}\n\t\t\t} finally {\n\t\t\t\tunlock(this.growLock);\n\t\t\t}\n\t\t}\n\n\t\tlet array = new AllocatedMemory(this.memory, getPointer(this.pointerStack.at(pointerIndex), this.memory.positionBits));\n\n\t\tlet blockLength = this.cachedDataLength * this.cachedMaxChunkSize;\n\t\tlet data = makeArrayView(this.cachedType, array.data.buffer, array.bufferByteOffset, blockLength) as T;\n\n\t\tthis.cachedFullDataBlock[pointerIndex] = data;\n\t\treturn data;\n\t}\n\n\tprivate getDataBlock(rawData: T, index: number): T {\n\t\tconst dataLength = this.cachedDataLength;\n\t\tconst start = index * dataLength;\n\t\treturn rawData.subarray(start, start + dataLength) as T;\n\t}\n\n\tfree() {\n\t\tthis.recycleStack.free();\n\n\t\tfor(let pointer of this.pointerStack) {\n\t\t\tlet memory = new AllocatedMemory(this.memory, getPointer(pointer, this.memory.positionBits));\n\t\t\tmemory.free();\n\t\t}\n\t\tthis.pointerStack.free();\n\t\tif(this.ownsFirstBlock) {\n\t\t\tthis.firstBlock.free();\n\t\t}\n\t}\n\n\tgetSharedMemory(): SharedPoolMemory {\n\t\treturn {\n\t\t\tfirstBlock: this.firstBlock.getSharedMemory(),\n\t\t};\n\t}\n}\n\ninterface SharedPoolConfig<T extends NumericArray> {\n\tmaxChunkSize?: number\n\tmaxLength?: number\n\tmaxRecycledLength?: number\n\ttype?: TypedArrayConstructor<T>\n\tdataLength?: number\n}\ninterface SharedPoolMemory {\n\tfirstBlock: SharedAllocatedMemory | AllocatedMemory\n}\n\nexport type { SharedPoolConfig, SharedPoolMemory };\n"],"mappings":"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"}
@@ -82,8 +82,17 @@ var c = 0, l = 1, u = 2, d = 3, f = 4, p = 5, m = 6, h = 7, g = 4, _ = 20, v = 2
82
82
  return t;
83
83
  }
84
84
  constructor(e, t) {
85
- if (this.memory = e, t && "firstBlock" in t) this.firstBlock = t.firstBlock instanceof n ? t.firstBlock : new n(e, t.firstBlock), this.uint16Array = new Uint16Array(this.firstBlock.data.buffer, this.firstBlock.bufferByteOffset + l * Uint32Array.BYTES_PER_ELEMENT, 2), "type" in t || "baseSegmentLength" in t || "maxLength" in t ? (this.initialize(t), this.ownsFirstBlock = !1) : this.ownsFirstBlock = !0;
86
- else {
85
+ if (this.memory = e, t && "firstBlock" in t) {
86
+ if (t.firstBlock instanceof n) this.firstBlock = t.firstBlock;
87
+ else {
88
+ let r = new Uint32Array(e.buffers[t.firstBlock.bufferPosition].buf, t.firstBlock.bufferByteOffset + d * Uint32Array.BYTES_PER_ELEMENT, 1)[0];
89
+ this.firstBlock = new n(e, {
90
+ length: v.BASE_ALLOCATE_COUNT + r,
91
+ ...t.firstBlock
92
+ });
93
+ }
94
+ this.uint16Array = new Uint16Array(this.firstBlock.data.buffer, this.firstBlock.bufferByteOffset + l * Uint32Array.BYTES_PER_ELEMENT, 2), "type" in t || "baseSegmentLength" in t || "maxLength" in t ? (this.initialize(t), this.ownsFirstBlock = !1) : this.ownsFirstBlock = !0;
95
+ } else {
87
96
  let n = i(r(t?.type ?? Uint32Array)), a = t?.baseSegmentLength ?? g, o = this.resolveMaxSegmentLength(a, t?.maxSegmentLength, n), s = this.resolveMaxSegments(t?.maxLength, a, o);
88
97
  this.firstBlock = e.allocUI32(v.BASE_ALLOCATE_COUNT + s), this.uint16Array = new Uint16Array(this.firstBlock.data.buffer, this.firstBlock.bufferByteOffset + l * Uint32Array.BYTES_PER_ELEMENT, 2), this.ownsFirstBlock = !0, this.initialize(t);
89
98
  }