@daneren2005/shared-memory-objects 1.3.0 → 1.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (61) hide show
  1. package/README.md +192 -13
  2. package/dist/allocated-memory.js +1 -1
  3. package/dist/allocated-memory.js.map +1 -1
  4. package/dist/constant-string.js +2 -2
  5. package/dist/constant-string.js.map +1 -1
  6. package/dist/local-pool.d.ts +5 -5
  7. package/dist/local-pool.js +15 -12
  8. package/dist/local-pool.js.map +1 -1
  9. package/dist/lock/read-write-lock.js +25 -11
  10. package/dist/lock/read-write-lock.js.map +1 -1
  11. package/dist/lock/simple-lock.js +7 -2
  12. package/dist/lock/simple-lock.js.map +1 -1
  13. package/dist/memory-heap.d.ts +2 -0
  14. package/dist/memory-heap.js +28 -23
  15. package/dist/memory-heap.js.map +1 -1
  16. package/dist/serialize-object.js +1 -1
  17. package/dist/serialize-object.js.map +1 -1
  18. package/dist/shared-list.d.ts +11 -6
  19. package/dist/shared-list.js +55 -59
  20. package/dist/shared-list.js.map +1 -1
  21. package/dist/shared-map.d.ts +35 -12
  22. package/dist/shared-map.js +185 -82
  23. package/dist/shared-map.js.map +1 -1
  24. package/dist/shared-pointer-list.js +3 -3
  25. package/dist/shared-pointer-list.js.map +1 -1
  26. package/dist/shared-pool.d.ts +16 -7
  27. package/dist/shared-pool.js +74 -57
  28. package/dist/shared-pool.js.map +1 -1
  29. package/dist/shared-stack.d.ts +9 -8
  30. package/dist/shared-stack.js +68 -79
  31. package/dist/shared-stack.js.map +1 -1
  32. package/dist/shared-string.js +3 -3
  33. package/dist/shared-string.js.map +1 -1
  34. package/dist/shared-vector.d.ts +8 -6
  35. package/dist/shared-vector.js +34 -57
  36. package/dist/shared-vector.js.map +1 -1
  37. package/dist/spatial/shared-quadtree.d.ts +67 -0
  38. package/dist/spatial/shared-quadtree.js +361 -0
  39. package/dist/spatial/shared-quadtree.js.map +1 -0
  40. package/dist/spatial/shared-spatial-grid.d.ts +78 -0
  41. package/dist/spatial/shared-spatial-grid.js +426 -0
  42. package/dist/spatial/shared-spatial-grid.js.map +1 -0
  43. package/dist/spatial/shared-spatial-map.d.ts +87 -0
  44. package/dist/spatial/shared-spatial-map.js +555 -0
  45. package/dist/spatial/shared-spatial-map.js.map +1 -0
  46. package/dist/spatial/spatial-entity.d.ts +11 -0
  47. package/dist/spatial/spatial-entity.js +19 -0
  48. package/dist/spatial/spatial-entity.js.map +1 -0
  49. package/dist/spatial/spatial-neighbors.d.ts +21 -0
  50. package/dist/spatial/spatial-neighbors.js +55 -0
  51. package/dist/spatial/spatial-neighbors.js.map +1 -0
  52. package/dist/utils/array-type.d.ts +20 -0
  53. package/dist/utils/array-type.js +36 -0
  54. package/dist/utils/array-type.js.map +1 -0
  55. package/dist/utils/hash.d.ts +3 -0
  56. package/dist/utils/hash.js +21 -0
  57. package/dist/utils/hash.js.map +1 -0
  58. package/dist/utils/pointer.d.ts +8 -6
  59. package/dist/utils/pointer.js +22 -17
  60. package/dist/utils/pointer.js.map +1 -1
  61. package/package.json +8 -2
@@ -1,40 +1,45 @@
1
- import { MAX_BYTE_OFFSET_LENGTH as e, MAX_POSITION_LENGTH as t } from "./utils/pointer.js";
1
+ import { MAX_BYTE_OFFSET_LENGTH as e, positionBitsForBufferSize as t } from "./utils/pointer.js";
2
2
  import n from "./allocated-memory.js";
3
3
  import r from "./memory-buffer.js";
4
4
  //#region src/memory-heap.ts
5
- var i = e, a = 0, o = 1, s = 2, c = class {
5
+ var i = e, a = 2 ** 31, o = 0, s = 1, c = 2, l = 3, u = 4, d = class {
6
6
  buffers;
7
7
  onGrowBufferHandlers = [];
8
8
  isClone;
9
9
  memory;
10
+ positionBits;
11
+ get maxBuffers() {
12
+ return 2 ** this.positionBits;
13
+ }
10
14
  get bufferSize() {
11
- return this.memory.data[a];
15
+ return this.memory.data[o];
12
16
  }
13
17
  get maxAllocationLength() {
14
18
  return Math.floor((this.bufferSize - 32 - 8) / Uint32Array.BYTES_PER_ELEMENT);
15
19
  }
16
- constructor(t) {
17
- if (t && "buffers" in t) this.buffers = t.buffers.map((e) => new r({
20
+ constructor(e) {
21
+ if (e && "buffers" in e) this.buffers = e.buffers.map((e) => new r({
18
22
  buf: e,
19
23
  skipInitialization: !0
20
24
  })), this.memory = new n(this, {
21
25
  bufferPosition: 0,
22
26
  bufferByteOffset: 40
23
- }), this.isClone = !0;
27
+ }), this.positionBits = this.memory.data[l], this.isClone = !0;
24
28
  else {
25
29
  "SharedArrayBuffer" in globalThis || console.warn("SharedArrayBuffer is not working: falling back to ArrayBuffer");
26
- let r = t?.bufferSize ?? i;
27
- if (r > e) throw Error(`Buffer size ${r} is greater than max ${e} that we can reference with pointers`);
28
- let c = this.createBuffer(r);
29
- this.buffers = [c];
30
- let l = c.callocAs("u32", 3);
31
- if (l) this.memory = new n(this, {
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`);
32
+ this.positionBits = t(r);
33
+ let d = this.createBuffer(r);
34
+ this.buffers = [d];
35
+ let f = d.callocAs("u32", u);
36
+ if (f) this.memory = new n(this, {
32
37
  bufferPosition: 0,
33
- bufferByteOffset: l.byteOffset
38
+ bufferByteOffset: f.byteOffset
34
39
  });
35
40
  else throw Error("Failed to initialize first byte from buffer");
36
- this.memory.data[a] = r, this.memory.data[o] = 1, this.memory.data[s] = t?.autoGrowSize ?? 100, this.isClone = !1;
37
- for (let e = 1; e < (t?.initialBuffers ?? 1); e++) this.buffers.push(this.createBuffer(r));
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
+ for (let t = 1; t < (e?.initialBuffers ?? 1); t++) this.buffers.push(this.createBuffer(r));
38
43
  }
39
44
  }
40
45
  addSharedBuffer(e) {
@@ -44,7 +49,7 @@ var i = e, a = 0, o = 1, s = 2, c = class {
44
49
  });
45
50
  }
46
51
  growBuffer() {
47
- let e = this.createBuffer(), t = Atomics.add(this.memory.data, o, 1);
52
+ let e = this.createBuffer(), t = Atomics.add(this.memory.data, s, 1);
48
53
  return this.buffers[t] = e, this.onGrowBufferHandlers.forEach((n) => n({
49
54
  bufferPosition: t,
50
55
  buffer: e.buf
@@ -74,16 +79,16 @@ var i = e, a = 0, o = 1, s = 2, c = class {
74
79
  let r = this.buffers[t];
75
80
  if (!r) continue;
76
81
  let i = r.callocAs("u32", e);
77
- if (i) return t === this.buffers.length - 1 && Atomics.load(this.memory.data, o) === this.buffers.length && this.memory.data[s] < 100 && this.memory.data[s] > 0 && r.top / r.end > this.memory.data[s] / 100 && this.growBuffer(), new n(this, {
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, {
78
83
  data: i,
79
84
  buffer: r
80
85
  });
81
86
  }
82
- if (this.buffers.length >= t) throw Error(`Can't initialize a new buffer since it would have a position greater than the max of ${t}`);
83
- let r = this.growBuffer(), i = r.callocAs("u32", e);
84
- if (i) return new n(this, {
85
- data: i,
86
- buffer: r
87
+ 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
87
92
  });
88
93
  throw Error(`Unable to allocate ${e} numbers even after adding a new buffer`);
89
94
  }
@@ -101,6 +106,6 @@ var i = e, a = 0, o = 1, s = 2, c = class {
101
106
  }
102
107
  };
103
108
  //#endregion
104
- export { c as default };
109
+ export { d as default };
105
110
 
106
111
  //# 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, MAX_POSITION_LENGTH } from './utils/pointer';\nimport MemoryBuffer, { SIZEOF_MEM_BLOCK, SIZEOF_STATE } from './memory-buffer';\n\nconst DEFAULT_BUFFER_SIZE = MAX_BYTE_OFFSET_LENGTH;\nconst BUFFER_SIZE_INDEX = 0;\nconst BUFFER_COUNT_INDEX = 1;\nconst BUFFER_AUTO_GROW_INDEX = 2;\nexport default class MemoryHeap {\n\tbuffers: Array<MemoryBuffer>;\n\tprivate onGrowBufferHandlers: Array<OnGrowBuffer> = [];\n\tisClone: boolean;\n\tprivate memory: AllocatedMemory;\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.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_BYTE_OFFSET_LENGTH) {\n\t\t\t\tthrow new Error(`Buffer size ${bufferSize} is greater than max ${MAX_BYTE_OFFSET_LENGTH} that we can reference with pointers`);\n\t\t\t}\n\t\t\t\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', 3);\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.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 >= MAX_POSITION_LENGTH) {\n\t\t\tthrow new Error(`Can't initialize a new buffer since it would have a position greater than the max of ${MAX_POSITION_LENGTH}`);\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 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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"}
@@ -16,7 +16,7 @@ function r(e, t) {
16
16
  return s.getArray(Float32Array, 0, r.length).set(r), s;
17
17
  }
18
18
  function i(n, r) {
19
- return a(new t(n, e(r)));
19
+ return a(new t(n, e(r, n.positionBits)));
20
20
  }
21
21
  function a(e) {
22
22
  let t = new Float32Array(e.data.buffer, e.bufferByteOffset);
@@ -1 +1 @@
1
- {"version":3,"file":"serialize-object.js","names":[],"sources":["../src/serialize-object.ts"],"sourcesContent":["import AllocatedMemory from './allocated-memory';\nimport type MemoryHeap from './memory-heap';\nimport { getPointer } from './utils/pointer';\n\nenum VALUE_TYPE {\n\tUNDEFINED,\n\tNUMBER,\n\tSTRING,\n}\n\n// Be able to serialize a simple object into memory and re-create the object in another thread\n// This does NOT support updating objects across threads\nconst MAGIC_NUMBER = 52361700;\nexport function serializeObjectToMemory<T extends object>(heap: MemoryHeap, object: T): AllocatedMemory {\n\tconst data: Array<number> = [];\n\tconst keys = Object.keys(object);\n\tdata.push(MAGIC_NUMBER);\n\tdata.push(keys.length);\n\n\t// Create index first so we can quickly construct index\n\tlet keyIndexes: { [key:string]:number } = {};\n\tkeys.forEach(key => {\n\t\tdata.push(key.length);\n\t\taddCharCodes(data, key);\n\n\t\tkeyIndexes[key] = data.length;\n\t\tdata.push(0);\n\t});\n\n\tkeys.forEach(key => {\n\t\tlet keyIndex = keyIndexes[key];\n\t\t// @ts-expect-error\n\t\tlet value = object[key];\n\t\tif(Number.isFinite(value)) {\n\t\t\tdata[keyIndex] = data.length;\n\t\t\tdata.push(VALUE_TYPE.NUMBER);\n\t\t\tdata.push(value);\n\t\t} else if(typeof value === 'string') {\n\t\t\tdata[keyIndex] = data.length;\n\t\t\tdata.push(VALUE_TYPE.STRING);\n\t\t\tdata.push(value.length);\n\t\t\taddCharCodes(data, value);\n\t\t}\n\t});\n\t\n\tlet memory = heap.allocUI32(data.length);\n\tlet sharedData = memory.getArray(Float32Array, 0, data.length);\n\tsharedData.set(data);\n\n\treturn memory;\n}\n\nexport function createObjectFromPointer<T extends object>(heap: MemoryHeap, pointer: number) {\n\tlet memory = new AllocatedMemory(heap, getPointer(pointer));\n\treturn createObjectFromMemory<T>(memory);\n}\nexport function createObjectFromMemory<T extends object>(memory: AllocatedMemory): T {\n\tlet sharedData = new Float32Array(memory.data.buffer, memory.bufferByteOffset);\n\tif(sharedData[0] !== MAGIC_NUMBER) {\n\t\tthrow new Error('Trying to create object from invalid memory location');\n\t}\n\n\tconst sharedObject: { [key:string]:any } = {};\n\tconst keyCount = sharedData[1];\n\tlet keyStartIndex = 2;\n\tfor(let i = 0; i < keyCount; i++) {\n\t\tlet keyLength = sharedData[keyStartIndex];\n\t\tlet keyData = new Float32Array(memory.data.buffer, memory.bufferByteOffset + (keyStartIndex + 1) * sharedData.BYTES_PER_ELEMENT, keyLength);\n\t\tlet key = String.fromCharCode.apply(null, [...keyData]);\n\t\tlet valueIndex = sharedData[keyStartIndex + keyLength + 1];\n\t\tlet valueType = sharedData[valueIndex];\n\t\tswitch(valueType) {\n\t\t\tcase VALUE_TYPE.NUMBER: {\n\t\t\t\tsharedObject[key] = sharedData[valueIndex + 1];\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tcase VALUE_TYPE.STRING: {\n\t\t\t\tlet length = sharedData[valueIndex + 1];\n\t\t\t\tlet valueData = new Float32Array(memory.data.buffer, memory.bufferByteOffset + (valueIndex + 2) * sharedData.BYTES_PER_ELEMENT, length);\n\t\t\t\tsharedObject[key] = String.fromCharCode.apply(null, [...valueData]);\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\n\t\tkeyStartIndex += 2 + keyLength;\n\t}\n\n\treturn sharedObject as T;\n}\n\nfunction addCharCodes(data: Array<number>, value: string) {\n\tfor(let i = 0; i < value.length; i++) {\n\t\tdata.push(value.charCodeAt(i));\n\t}\n}"],"mappings":";;;AAYA,IAAM,IAAe;AACrB,SAAgB,EAA0C,GAAkB,GAA4B;CACvG,IAAM,IAAsB,CAAC,GACvB,IAAO,OAAO,KAAK,CAAM;CAE/B,AADA,EAAK,KAAK,CAAY,GACtB,EAAK,KAAK,EAAK,MAAM;CAGrB,IAAI,IAAsC,CAAC;CAS3C,AARA,EAAK,SAAQ,MAAO;EAKnB,AAJA,EAAK,KAAK,EAAI,MAAM,GACpB,EAAa,GAAM,CAAG,GAEtB,EAAW,KAAO,EAAK,QACvB,EAAK,KAAK,CAAC;CACZ,CAAC,GAED,EAAK,SAAQ,MAAO;EACnB,IAAI,IAAW,EAAW,IAEtB,IAAQ,EAAO;EACnB,AAAG,OAAO,SAAS,CAAK,KACvB,EAAK,KAAY,EAAK,QACtB,EAAK,KAAA,CAAsB,GAC3B,EAAK,KAAK,CAAK,KACN,OAAO,KAAU,aAC1B,EAAK,KAAY,EAAK,QACtB,EAAK,KAAA,CAAsB,GAC3B,EAAK,KAAK,EAAM,MAAM,GACtB,EAAa,GAAM,CAAK;CAE1B,CAAC;CAED,IAAI,IAAS,EAAK,UAAU,EAAK,MAAM;CAIvC,OAFA,EADwB,SAAS,cAAc,GAAG,EAAK,MACvD,CAAA,CAAW,IAAI,CAAI,GAEZ;AACR;AAEA,SAAgB,EAA0C,GAAkB,GAAiB;CAE5F,OAAO,EAA0B,IADhB,EAAgB,GAAM,EAAW,CAAO,CACxB,CAAM;AACxC;AACA,SAAgB,EAAyC,GAA4B;CACpF,IAAI,IAAa,IAAI,aAAa,EAAO,KAAK,QAAQ,EAAO,gBAAgB;CAC7E,IAAG,EAAW,OAAO,GACpB,MAAU,MAAM,sDAAsD;CAGvE,IAAM,IAAqC,CAAC,GACtC,IAAW,EAAW,IACxB,IAAgB;CACpB,KAAI,IAAI,IAAI,GAAG,IAAI,GAAU,KAAK;EACjC,IAAI,IAAY,EAAW,IACvB,IAAU,IAAI,aAAa,EAAO,KAAK,QAAQ,EAAO,oBAAoB,IAAgB,KAAK,EAAW,mBAAmB,CAAS,GACtI,IAAM,OAAO,aAAa,MAAM,MAAM,CAAC,GAAG,CAAO,CAAC,GAClD,IAAa,EAAW,IAAgB,IAAY;EAExD,QADgB,EAAW,IAC3B;GACC,KAAA;IACC,EAAa,KAAO,EAAW,IAAa;IAC5C;GAED,KAAA,GAAwB;IACvB,IAAI,IAAS,EAAW,IAAa,IACjC,IAAY,IAAI,aAAa,EAAO,KAAK,QAAQ,EAAO,oBAAoB,IAAa,KAAK,EAAW,mBAAmB,CAAM;IACtI,EAAa,KAAO,OAAO,aAAa,MAAM,MAAM,CAAC,GAAG,CAAS,CAAC;IAClE;GACD;EACD;EAEA,KAAiB,IAAI;CACtB;CAEA,OAAO;AACR;AAEA,SAAS,EAAa,GAAqB,GAAe;CACzD,KAAI,IAAI,IAAI,GAAG,IAAI,EAAM,QAAQ,KAChC,EAAK,KAAK,EAAM,WAAW,CAAC,CAAC;AAE/B"}
1
+ {"version":3,"file":"serialize-object.js","names":[],"sources":["../src/serialize-object.ts"],"sourcesContent":["import AllocatedMemory from './allocated-memory';\nimport type MemoryHeap from './memory-heap';\nimport { getPointer } from './utils/pointer';\n\nenum VALUE_TYPE {\n\tUNDEFINED,\n\tNUMBER,\n\tSTRING,\n}\n\n// Be able to serialize a simple object into memory and re-create the object in another thread\n// This does NOT support updating objects across threads\nconst MAGIC_NUMBER = 52361700;\nexport function serializeObjectToMemory<T extends object>(heap: MemoryHeap, object: T): AllocatedMemory {\n\tconst data: Array<number> = [];\n\tconst keys = Object.keys(object);\n\tdata.push(MAGIC_NUMBER);\n\tdata.push(keys.length);\n\n\t// Create index first so we can quickly construct index\n\tlet keyIndexes: { [key:string]:number } = {};\n\tkeys.forEach(key => {\n\t\tdata.push(key.length);\n\t\taddCharCodes(data, key);\n\n\t\tkeyIndexes[key] = data.length;\n\t\tdata.push(0);\n\t});\n\n\tkeys.forEach(key => {\n\t\tlet keyIndex = keyIndexes[key];\n\t\t// @ts-expect-error\n\t\tlet value = object[key];\n\t\tif(Number.isFinite(value)) {\n\t\t\tdata[keyIndex] = data.length;\n\t\t\tdata.push(VALUE_TYPE.NUMBER);\n\t\t\tdata.push(value);\n\t\t} else if(typeof value === 'string') {\n\t\t\tdata[keyIndex] = data.length;\n\t\t\tdata.push(VALUE_TYPE.STRING);\n\t\t\tdata.push(value.length);\n\t\t\taddCharCodes(data, value);\n\t\t}\n\t});\n\t\n\tlet memory = heap.allocUI32(data.length);\n\tlet sharedData = memory.getArray(Float32Array, 0, data.length);\n\tsharedData.set(data);\n\n\treturn memory;\n}\n\nexport function createObjectFromPointer<T extends object>(heap: MemoryHeap, pointer: number) {\n\tlet memory = new AllocatedMemory(heap, getPointer(pointer, heap.positionBits));\n\treturn createObjectFromMemory<T>(memory);\n}\nexport function createObjectFromMemory<T extends object>(memory: AllocatedMemory): T {\n\tlet sharedData = new Float32Array(memory.data.buffer, memory.bufferByteOffset);\n\tif(sharedData[0] !== MAGIC_NUMBER) {\n\t\tthrow new Error('Trying to create object from invalid memory location');\n\t}\n\n\tconst sharedObject: { [key:string]:any } = {};\n\tconst keyCount = sharedData[1];\n\tlet keyStartIndex = 2;\n\tfor(let i = 0; i < keyCount; i++) {\n\t\tlet keyLength = sharedData[keyStartIndex];\n\t\tlet keyData = new Float32Array(memory.data.buffer, memory.bufferByteOffset + (keyStartIndex + 1) * sharedData.BYTES_PER_ELEMENT, keyLength);\n\t\tlet key = String.fromCharCode.apply(null, [...keyData]);\n\t\tlet valueIndex = sharedData[keyStartIndex + keyLength + 1];\n\t\tlet valueType = sharedData[valueIndex];\n\t\tswitch(valueType) {\n\t\t\tcase VALUE_TYPE.NUMBER: {\n\t\t\t\tsharedObject[key] = sharedData[valueIndex + 1];\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tcase VALUE_TYPE.STRING: {\n\t\t\t\tlet length = sharedData[valueIndex + 1];\n\t\t\t\tlet valueData = new Float32Array(memory.data.buffer, memory.bufferByteOffset + (valueIndex + 2) * sharedData.BYTES_PER_ELEMENT, length);\n\t\t\t\tsharedObject[key] = String.fromCharCode.apply(null, [...valueData]);\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\n\t\tkeyStartIndex += 2 + keyLength;\n\t}\n\n\treturn sharedObject as T;\n}\n\nfunction addCharCodes(data: Array<number>, value: string) {\n\tfor(let i = 0; i < value.length; i++) {\n\t\tdata.push(value.charCodeAt(i));\n\t}\n}"],"mappings":";;;AAYA,IAAM,IAAe;AACrB,SAAgB,EAA0C,GAAkB,GAA4B;CACvG,IAAM,IAAsB,CAAC,GACvB,IAAO,OAAO,KAAK,CAAM;CAE/B,AADA,EAAK,KAAK,CAAY,GACtB,EAAK,KAAK,EAAK,MAAM;CAGrB,IAAI,IAAsC,CAAC;CAS3C,AARA,EAAK,SAAQ,MAAO;EAKnB,AAJA,EAAK,KAAK,EAAI,MAAM,GACpB,EAAa,GAAM,CAAG,GAEtB,EAAW,KAAO,EAAK,QACvB,EAAK,KAAK,CAAC;CACZ,CAAC,GAED,EAAK,SAAQ,MAAO;EACnB,IAAI,IAAW,EAAW,IAEtB,IAAQ,EAAO;EACnB,AAAG,OAAO,SAAS,CAAK,KACvB,EAAK,KAAY,EAAK,QACtB,EAAK,KAAA,CAAsB,GAC3B,EAAK,KAAK,CAAK,KACN,OAAO,KAAU,aAC1B,EAAK,KAAY,EAAK,QACtB,EAAK,KAAA,CAAsB,GAC3B,EAAK,KAAK,EAAM,MAAM,GACtB,EAAa,GAAM,CAAK;CAE1B,CAAC;CAED,IAAI,IAAS,EAAK,UAAU,EAAK,MAAM;CAIvC,OAFA,EADwB,SAAS,cAAc,GAAG,EAAK,MACvD,CAAA,CAAW,IAAI,CAAI,GAEZ;AACR;AAEA,SAAgB,EAA0C,GAAkB,GAAiB;CAE5F,OAAO,EAA0B,IADhB,EAAgB,GAAM,EAAW,GAAS,EAAK,YAAY,CAC3C,CAAM;AACxC;AACA,SAAgB,EAAyC,GAA4B;CACpF,IAAI,IAAa,IAAI,aAAa,EAAO,KAAK,QAAQ,EAAO,gBAAgB;CAC7E,IAAG,EAAW,OAAO,GACpB,MAAU,MAAM,sDAAsD;CAGvE,IAAM,IAAqC,CAAC,GACtC,IAAW,EAAW,IACxB,IAAgB;CACpB,KAAI,IAAI,IAAI,GAAG,IAAI,GAAU,KAAK;EACjC,IAAI,IAAY,EAAW,IACvB,IAAU,IAAI,aAAa,EAAO,KAAK,QAAQ,EAAO,oBAAoB,IAAgB,KAAK,EAAW,mBAAmB,CAAS,GACtI,IAAM,OAAO,aAAa,MAAM,MAAM,CAAC,GAAG,CAAO,CAAC,GAClD,IAAa,EAAW,IAAgB,IAAY;EAExD,QADgB,EAAW,IAC3B;GACC,KAAA;IACC,EAAa,KAAO,EAAW,IAAa;IAC5C;GAED,KAAA,GAAwB;IACvB,IAAI,IAAS,EAAW,IAAa,IACjC,IAAY,IAAI,aAAa,EAAO,KAAK,QAAQ,EAAO,oBAAoB,IAAa,KAAK,EAAW,mBAAmB,CAAM;IACtI,EAAa,KAAO,OAAO,aAAa,MAAM,MAAM,CAAC,GAAG,CAAS,CAAC;IAClE;GACD;EACD;EAEA,KAAiB,IAAI;CACtB;CAEA,OAAO;AACR;AAEA,SAAS,EAAa,GAAqB,GAAe;CACzD,KAAI,IAAI,IAAI,GAAG,IAAI,EAAM,QAAQ,KAChC,EAAK,KAAK,EAAM,WAAW,CAAC,CAAC;AAE/B"}
@@ -1,13 +1,18 @@
1
1
  import type { SharedAllocatedMemory } from './allocated-memory';
2
- import type { TypedArrayConstructor } from './interfaces/typed-array-constructor';
3
2
  import type MemoryHeap from './memory-heap';
4
- export default class SharedList<T extends Uint32Array | Int32Array | Float32Array = Uint32Array> implements Iterable<SharedListIterable<T>> {
3
+ import { type NumericArray, type TypedArrayConstructor } from './utils/array-type';
4
+ export default class SharedList<T extends NumericArray = Uint32Array> implements Iterable<SharedListIterable<T>> {
5
5
  static readonly ALLOCATE_COUNT = 4;
6
6
  private memory;
7
+ private positionBits;
8
+ private positionMask;
7
9
  private firstBlock;
8
10
  private uint16Array;
9
11
  private cachedType;
10
12
  private cachedDataLength;
13
+ private cachedByteMultipler;
14
+ private cachedIsBigInt;
15
+ private cachedIsFloat;
11
16
  private bufferViews;
12
17
  onDelete?: (data: T) => void;
13
18
  get length(): number;
@@ -17,10 +22,10 @@ export default class SharedList<T extends Uint32Array | Int32Array | Float32Arra
17
22
  get dataLength(): number;
18
23
  private set dataLength(value);
19
24
  constructor(memory: MemoryHeap, config?: SharedListConfig<T> | SharedListMemory);
20
- insert(values: number | Array<number>): void;
25
+ insert(values: T[number] | Array<T[number]>): void;
21
26
  deleteMatch(callback: (values: T, index: number) => boolean): boolean;
22
27
  deleteIndex(deleteIndex: number): boolean;
23
- deleteValue(deleteValues: number | Array<number>): boolean;
28
+ deleteValue(deleteValues: T[number] | Array<T[number]>): boolean;
24
29
  compact(): void;
25
30
  clear(): void;
26
31
  [Symbol.iterator](): Generator<{
@@ -34,7 +39,7 @@ export default class SharedList<T extends Uint32Array | Int32Array | Float32Arra
34
39
  private getDataBlock;
35
40
  free(): void;
36
41
  }
37
- interface SharedListConfig<T extends Uint32Array | Int32Array | Float32Array> {
42
+ interface SharedListConfig<T extends NumericArray> {
38
43
  initWithBlock?: SharedAllocatedMemory;
39
44
  type?: TypedArrayConstructor<T>;
40
45
  dataLength?: number;
@@ -42,7 +47,7 @@ interface SharedListConfig<T extends Uint32Array | Int32Array | Float32Array> {
42
47
  interface SharedListMemory {
43
48
  firstBlock: SharedAllocatedMemory;
44
49
  }
45
- interface SharedListIterable<T extends Uint32Array | Int32Array | Float32Array> {
50
+ interface SharedListIterable<T extends NumericArray> {
46
51
  data: T;
47
52
  index: number;
48
53
  deleteCurrent: () => boolean;
@@ -1,26 +1,32 @@
1
1
  import { loadPointer as e, loadRawPointer as t, replaceRawPointer as n, storeRawPointer as r } from "./utils/pointer.js";
2
2
  import i from "./allocated-memory.js";
3
+ import { getArrayTypeCode as a, getByteMultipler as o, isBigIntType as s, isFloatType as c, makeArrayView as l } from "./utils/array-type.js";
3
4
  //#region src/shared-list.ts
4
- var a = 4, o = 2, s = o * Uint32Array.BYTES_PER_ELEMENT, c = 0, l = 1, u = 2, d = 0, f = 1, p = 4095, m = class {
5
- static ALLOCATE_COUNT = a;
5
+ var u = 4, d = 2, f = d * Uint32Array.BYTES_PER_ELEMENT, p = 0, m = 1, h = 2, g = 0, _ = 1, v = class {
6
+ static ALLOCATE_COUNT = u;
6
7
  memory;
8
+ positionBits;
9
+ positionMask;
7
10
  firstBlock;
8
11
  uint16Array;
9
12
  cachedType;
10
13
  cachedDataLength;
14
+ cachedByteMultipler = 1;
15
+ cachedIsBigInt = !1;
16
+ cachedIsFloat = !1;
11
17
  bufferViews = [];
12
18
  onDelete;
13
19
  get length() {
14
- return Atomics.load(this.firstBlock.data, u);
20
+ return Atomics.load(this.firstBlock.data, h);
15
21
  }
16
22
  get usedMemory() {
17
- let t = this.firstBlock.usedMemory, { bufferPosition: n, bufferByteOffset: r } = e(this.firstBlock.data, c);
23
+ let t = this.firstBlock.usedMemory, { bufferPosition: n, bufferByteOffset: r } = e(this.firstBlock.data, p, this.positionBits);
18
24
  for (; r;) {
19
25
  let a = new i(this.memory, {
20
26
  bufferPosition: n,
21
27
  bufferByteOffset: r
22
28
  });
23
- t += a.usedMemory, {bufferPosition: n, bufferByteOffset: r} = e(a.data, d);
29
+ t += a.usedMemory, {bufferPosition: n, bufferByteOffset: r} = e(a.data, g, this.positionBits);
24
30
  }
25
31
  return t;
26
32
  }
@@ -37,34 +43,29 @@ var a = 4, o = 2, s = o * Uint32Array.BYTES_PER_ELEMENT, c = 0, l = 1, u = 2, d
37
43
  Atomics.store(this.uint16Array, 1, e);
38
44
  }
39
45
  constructor(e, t) {
40
- if (this.memory = e, t && "firstBlock" in t) this.firstBlock = new i(e, t.firstBlock), this.uint16Array = new Uint16Array(this.firstBlock.data.buffer, this.firstBlock.bufferByteOffset + 3 * Uint32Array.BYTES_PER_ELEMENT, 2);
41
- else {
42
- this.firstBlock = t && t.initWithBlock ? new i(e, t.initWithBlock) : e.allocUI32(a), this.uint16Array = new Uint16Array(this.firstBlock.data.buffer, this.firstBlock.bufferByteOffset + 3 * Uint32Array.BYTES_PER_ELEMENT, 2), r(this.firstBlock.data, l, this.firstBlock.pointer);
43
- let n = t?.type ?? Uint32Array;
44
- n === Uint32Array ? this.type = 0 : n === Int32Array ? this.type = 1 : n === Float32Array && (this.type = 2), this.dataLength = t?.dataLength ?? 1;
45
- }
46
- this.cachedType = Atomics.load(this.uint16Array, 0), this.cachedDataLength = Math.max(1, Atomics.load(this.uint16Array, 1));
46
+ this.memory = e, this.positionBits = e.positionBits, this.positionMask = (1 << this.positionBits) - 1, t && "firstBlock" in t ? (this.firstBlock = new i(e, t.firstBlock), this.uint16Array = new Uint16Array(this.firstBlock.data.buffer, this.firstBlock.bufferByteOffset + 3 * Uint32Array.BYTES_PER_ELEMENT, 2)) : (this.firstBlock = t && t.initWithBlock ? new i(e, t.initWithBlock) : e.allocUI32(u), this.uint16Array = new Uint16Array(this.firstBlock.data.buffer, this.firstBlock.bufferByteOffset + 3 * Uint32Array.BYTES_PER_ELEMENT, 2), r(this.firstBlock.data, m, this.firstBlock.pointer), this.type = a(t?.type ?? Uint32Array), this.dataLength = t?.dataLength ?? 1), this.cachedType = Atomics.load(this.uint16Array, 0), this.cachedDataLength = Math.max(1, Atomics.load(this.uint16Array, 1)), this.cachedByteMultipler = o(this.cachedType), this.cachedIsBigInt = s(this.cachedType), this.cachedIsFloat = c(this.cachedType);
47
47
  }
48
48
  insert(e) {
49
- typeof e == "number" && (e = [e]);
49
+ Array.isArray(e) || (e = [e]);
50
50
  let r = this.cachedDataLength;
51
51
  if (e.length > r) throw Error(`Can't insert ${e.length} array into shared list of ${r} dataLength`);
52
- let i = this.memory.allocUI32(o + r), a = this.getDataBlock(i.data.buffer, i.data.byteOffset), s = i.pointer;
53
- for (let t = 0; t < e.length; t++) a instanceof Int32Array || a instanceof Uint32Array ? Atomics.store(a, t, e[t]) : a[t] = e[t];
52
+ let i = this.memory.allocUI32(d + r * this.cachedByteMultipler), a = this.getDataBlock(i.data.buffer, i.data.byteOffset), o = i.pointer, s = a;
53
+ for (let t = 0; t < e.length; t++) this.cachedIsFloat ? s[t] = e[t] : (this.cachedIsBigInt, Atomics.store(a, t, e[t]));
54
+ let c = this.positionBits, l = this.positionMask;
54
55
  for (;;) {
55
- let e = t(this.firstBlock.data, l), r = this.getBufferView(e & p);
56
+ let e = t(this.firstBlock.data, m), r = this.getBufferView(e & l);
56
57
  if (!r) continue;
57
- let i = (e >>> 12 >>> 2) + d, a = Atomics.load(r, i);
58
- if (e === t(this.firstBlock.data, l)) {
58
+ let i = (e >>> c >>> 2) + g, a = Atomics.load(r, i);
59
+ if (e === t(this.firstBlock.data, m)) {
59
60
  if (a === 0) {
60
- if (Atomics.compareExchange(r, i, 0, s) === 0) {
61
- n(this.firstBlock.data, l, s, e);
61
+ if (Atomics.compareExchange(r, i, 0, o) === 0) {
62
+ n(this.firstBlock.data, m, o, e);
62
63
  break;
63
64
  }
64
- } else n(this.firstBlock.data, l, a, e);
65
+ } else n(this.firstBlock.data, m, a, e);
65
66
  }
66
67
  }
67
- Atomics.add(this.firstBlock.data, u, 1);
68
+ Atomics.add(this.firstBlock.data, h, 1);
68
69
  }
69
70
  deleteMatch(e) {
70
71
  for (let { data: t, index: n, deleteCurrent: r } of this) if (e(t, n) && r()) return !0;
@@ -76,51 +77,51 @@ var a = 4, o = 2, s = o * Uint32Array.BYTES_PER_ELEMENT, c = 0, l = 1, u = 2, d
76
77
  return !1;
77
78
  }
78
79
  deleteValue(e) {
79
- return typeof e == "number" ? this.deleteMatch((t) => t[0] === e) : this.deleteMatch((t) => {
80
+ return Array.isArray(e) ? this.deleteMatch((t) => {
80
81
  if (t.length !== e.length) return !1;
81
82
  for (let n = 0; n < t.length; n++) if (t[n] !== e[n]) return !1;
82
83
  return !0;
83
- });
84
+ }) : this.deleteMatch((t) => t[0] === e);
84
85
  }
85
86
  compact() {
86
- let e = this.firstBlock.pointer, n = this.firstBlock.data, i = d, a = t(n, i);
87
- for (; a;) {
88
- let t = a & p, o = this.memory.buffers[t];
89
- if (!o) break;
90
- let s = this.getBufferView(t), c = a >>> 12, l = c >>> 2, u = Atomics.load(s, l + d);
91
- Atomics.load(s, l + f) ? (r(n, i, u), o.free(c)) : (e = a, n = s, i = l + d), a = u;
87
+ let e = this.firstBlock.pointer, n = this.firstBlock.data, i = g, a = this.positionBits, o = this.positionMask, s = t(n, i);
88
+ for (; s;) {
89
+ let t = s & o, c = this.memory.buffers[t];
90
+ if (!c) break;
91
+ let l = this.getBufferView(t), u = s >>> a, d = u >>> 2, f = Atomics.load(l, d + g);
92
+ Atomics.load(l, d + _) ? (r(n, i, f), c.free(u)) : (e = s, n = l, i = d + g), s = f;
92
93
  }
93
- r(this.firstBlock.data, l, e);
94
+ r(this.firstBlock.data, m, e);
94
95
  }
95
96
  clear() {
96
97
  let e, i = !1;
97
98
  for (; !i;) {
98
- if (e = t(this.firstBlock.data, c), !e) return;
99
- i = n(this.firstBlock.data, c, 0, e);
99
+ if (e = t(this.firstBlock.data, p), !e) return;
100
+ i = n(this.firstBlock.data, p, 0, e);
100
101
  }
101
102
  if (!e) return;
102
- r(this.firstBlock.data, l, this.firstBlock.pointer);
103
- let a = 0, o = e;
104
- for (; o;) {
105
- let e = o & p, t = this.memory.buffers[e];
103
+ r(this.firstBlock.data, m, this.firstBlock.pointer);
104
+ let a = 0, o = this.positionBits, s = this.positionMask, c = e;
105
+ for (; c;) {
106
+ let e = c & s, t = this.memory.buffers[e];
106
107
  if (!t) break;
107
- let n = this.getBufferView(e), r = o >>> 12, i = r >>> 2;
108
- o = Atomics.load(n, i + d), Atomics.load(n, i + f) || (a++, this.onDelete && this.onDelete(this.getDataBlock(n.buffer, r))), t.free(r);
108
+ let n = this.getBufferView(e), r = c >>> o, i = r >>> 2;
109
+ c = Atomics.load(n, i + g), Atomics.load(n, i + _) || (a++, this.onDelete && this.onDelete(this.getDataBlock(n.buffer, r))), t.free(r);
109
110
  }
110
- Atomics.sub(this.firstBlock.data, u, a);
111
+ Atomics.sub(this.firstBlock.data, h, a);
111
112
  }
112
113
  *[Symbol.iterator]() {
113
- let e = 0, n = t(this.firstBlock.data, c);
114
- for (; n;) {
115
- let t = n & p, r = this.getBufferView(t);
116
- if (!r) return;
117
- let i = n >>> 12, a = i >>> 2;
118
- if (n = Atomics.load(r, a + d), Atomics.load(r, a + f)) continue;
119
- let o = r.buffer, s = a + f;
114
+ let e = 0, n = this.positionBits, r = this.positionMask, i = t(this.firstBlock.data, p);
115
+ for (; i;) {
116
+ let t = i & r, a = this.getBufferView(t);
117
+ if (!a) return;
118
+ let o = i >>> n, s = o >>> 2;
119
+ if (i = Atomics.load(a, s + g), Atomics.load(a, s + _)) continue;
120
+ let c = a.buffer, l = s + _;
120
121
  yield {
121
- data: this.getDataBlock(o, i),
122
+ data: this.getDataBlock(c, o),
122
123
  index: e,
123
- deleteCurrent: () => Atomics.compareExchange(r, s, 0, 1) === 0 && (Atomics.sub(this.firstBlock.data, u, 1), this.onDelete && this.onDelete(this.getDataBlock(o, i)), !0)
124
+ deleteCurrent: () => Atomics.compareExchange(a, l, 0, 1) === 0 && (Atomics.sub(this.firstBlock.data, h, 1), this.onDelete && this.onDelete(this.getDataBlock(c, o)), !0)
124
125
  }, e++;
125
126
  }
126
127
  }
@@ -137,27 +138,22 @@ var a = 4, o = 2, s = o * Uint32Array.BYTES_PER_ELEMENT, c = 0, l = 1, u = 2, d
137
138
  if (n) return t = new Uint32Array(n.buf), this.bufferViews[e] = t, t;
138
139
  }
139
140
  getDataBlock(e, t) {
140
- let n = t + s;
141
- switch (this.cachedType) {
142
- case 1: return new Int32Array(e, n, this.cachedDataLength);
143
- case 0: return new Uint32Array(e, n, this.cachedDataLength);
144
- case 2: return new Float32Array(e, n, this.cachedDataLength);
145
- default: throw Error(`Unknown data block type ${this.cachedType}`);
146
- }
141
+ let n = t + f;
142
+ return l(this.cachedType, e, n, this.cachedDataLength);
147
143
  }
148
144
  free() {
149
- let { bufferPosition: t, bufferByteOffset: n } = e(this.firstBlock.data, c);
145
+ let { bufferPosition: t, bufferByteOffset: n } = e(this.firstBlock.data, p, this.positionBits);
150
146
  for (; n;) {
151
147
  let r = new i(this.memory, {
152
148
  bufferPosition: t,
153
149
  bufferByteOffset: n
154
150
  });
155
- ({bufferPosition: t, bufferByteOffset: n} = e(r.data, d)), this.onDelete && !Atomics.load(r.data, f) && this.onDelete(this.getDataBlock(r.data.buffer, r.data.byteOffset)), r.free();
151
+ ({bufferPosition: t, bufferByteOffset: n} = e(r.data, g, this.positionBits)), this.onDelete && !Atomics.load(r.data, _) && this.onDelete(this.getDataBlock(r.data.buffer, r.data.byteOffset)), r.free();
156
152
  }
157
153
  this.firstBlock.free();
158
154
  }
159
155
  };
160
156
  //#endregion
161
- export { m as default };
157
+ export { v as default };
162
158
 
163
159
  //# sourceMappingURL=shared-list.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"shared-list.js","names":[],"sources":["../src/shared-list.ts"],"sourcesContent":["import type { SharedAllocatedMemory } from './allocated-memory';\nimport AllocatedMemory from './allocated-memory';\nimport type { TypedArrayConstructor } from './interfaces/typed-array-constructor';\nimport type MemoryHeap from './memory-heap';\nimport { loadPointer, loadRawPointer, POSITION_BIT_COUNT, replaceRawPointer, storeRawPointer } from './utils/pointer';\n\nenum TYPE {\n\tuint32,\n\tint32,\n\tfloat32,\n}\n\n// Lock-free singly-linked list. firstBlock doubles as an immortal sentinel node whose next pointer (index 0) is the head.\n// insert appends with a Michael-Scott CAS enqueue; delete only tombstones a node (never unlinks/frees). Memory is\n// reclaimed exclusively by the quiescent compact()/clear()/free() passes. That deferral is what makes the CAS loops\n// safe without hazard pointers: a live node's address is never handed back to the allocator, so no append can ever\n// observe a recycled address (ABA) while another thread is mid-traversal.\nconst FIRST_BLOCK_RECORD_KEEPING_COUNT = 4;\nconst DATA_BLOCK_RECORD_KEEPING_COUNT = 2;\nconst DATA_BLOCK_BYTE_OFFSET = DATA_BLOCK_RECORD_KEEPING_COUNT * Uint32Array.BYTES_PER_ELEMENT;\nconst HEAD_INDEX = 0;\nconst TAIL_INDEX = 1;\nconst LENGTH_INDEX = 2;\nconst NODE_NEXT_INDEX = 0;\nconst NODE_DELETED_INDEX = 1;\n// A pointer packs the buffer position in its low bits and the byte offset in the high bits (see utils/pointer)\nconst POSITION_MASK = (1 << POSITION_BIT_COUNT) - 1;\nexport default class SharedList<T extends Uint32Array | Int32Array | Float32Array = Uint32Array> implements Iterable<SharedListIterable<T>> {\n\tstatic readonly ALLOCATE_COUNT = FIRST_BLOCK_RECORD_KEEPING_COUNT;\n\n\tprivate memory: MemoryHeap;\n\t/* First block (also the sentinel node - index 0 is its next pointer = list head)\n\t\t32 index 0\n\t\tuint16 0 - head buffer position | sentinel.next\n\t\tuint16 1 - head buffer index\n\t\t32 index 1\n\t\tuint16 2 - tail hint buffer position\n\t\tuint16 3 - tail hint buffer index\n\t\t32 index 2\n\t\tuint32 4 - length\n\t\t32 index 3\n\t\tuint16 6 - type\n\t\tuint16 7 - data length (defaults to 1 number per data)\n\t*/\n\t/* Other blocks\n\t\t32 index 0 - next buffer pointer\n\t\t32 index 1 - deleted flag (0 = live, 1 = tombstoned)\n\t\t32 index 2 => data\n\t*/\n\tprivate firstBlock: AllocatedMemory;\n\tprivate uint16Array: Uint16Array;\n\t// type and dataLength are written once at init and never change, so cache them instead of atomic-loading per node\n\tprivate cachedType: number;\n\tprivate cachedDataLength: number;\n\t// One full-buffer Uint32Array per buffer position, so traversal indexes shared memory directly instead of\n\t// allocating a fresh view (and a getPointer object) for every node it touches\n\tprivate bufferViews: Array<Uint32Array> = [];\n\tonDelete?: (data: T) => void;\n\n\tget length(): number {\n\t\treturn Atomics.load(this.firstBlock.data, LENGTH_INDEX);\n\t}\n\n\t// Own internal memory plus every allocated node still linked in the chain (tombstoned nodes included, since they stay\n\t// allocated until a compact()/clear() reclaims them).\n\tget usedMemory(): number {\n\t\tlet total = this.firstBlock.usedMemory;\n\t\tlet { bufferPosition, bufferByteOffset } = loadPointer(this.firstBlock.data, HEAD_INDEX);\n\t\twhile(bufferByteOffset) {\n\t\t\tlet node = new AllocatedMemory(this.memory, { bufferPosition, bufferByteOffset });\n\t\t\ttotal += node.usedMemory;\n\t\t\t({ bufferPosition, bufferByteOffset } = loadPointer(node.data, NODE_NEXT_INDEX));\n\t\t}\n\t\treturn total;\n\t}\n\n\tget type(): number {\n\t\treturn this.cachedType;\n\t}\n\tprivate set type(value: number) {\n\t\tAtomics.store(this.uint16Array, 0, value);\n\t}\n\tget dataLength(): number {\n\t\treturn this.cachedDataLength;\n\t}\n\tprivate set dataLength(value: number) {\n\t\tAtomics.store(this.uint16Array, 1, value);\n\t}\n\n\tconstructor(memory: MemoryHeap, config?: SharedListConfig<T> | SharedListMemory) {\n\t\tthis.memory = memory;\n\n\t\tif(config && 'firstBlock' in config) {\n\t\t\t// TODO: How to handle referencing memory we don't have access to yet because buffer not synced from worker?\n\t\t\tthis.firstBlock = new AllocatedMemory(memory, config.firstBlock);\n\t\t\tthis.uint16Array = new Uint16Array(this.firstBlock.data.buffer, this.firstBlock.bufferByteOffset + (LENGTH_INDEX + 1) * Uint32Array.BYTES_PER_ELEMENT, 2);\n\t\t} else {\n\t\t\tif(config && config.initWithBlock) {\n\t\t\t\tthis.firstBlock = new AllocatedMemory(memory, config.initWithBlock);\n\t\t\t} else {\n\t\t\t\tthis.firstBlock = memory.allocUI32(FIRST_BLOCK_RECORD_KEEPING_COUNT);\n\t\t\t}\n\t\t\tthis.uint16Array = new Uint16Array(this.firstBlock.data.buffer, this.firstBlock.bufferByteOffset + (LENGTH_INDEX + 1) * Uint32Array.BYTES_PER_ELEMENT, 2);\n\n\t\t\t// Empty list: the tail hint points at the sentinel (firstBlock) so insert never special-cases the first node\n\t\t\tstoreRawPointer(this.firstBlock.data, TAIL_INDEX, this.firstBlock.pointer);\n\n\t\t\tconst type = config?.type ?? Uint32Array;\n\t\t\tif(type === Uint32Array) {\n\t\t\t\tthis.type = TYPE.uint32;\n\t\t\t// @ts-expect-error\n\t\t\t} else if(type === Int32Array) {\n\t\t\t\tthis.type = TYPE.int32;\n\t\t\t// @ts-expect-error\n\t\t\t} else if(type === Float32Array) {\n\t\t\t\tthis.type = TYPE.float32;\n\t\t\t}\n\t\t\tthis.dataLength = config?.dataLength ?? 1;\n\t\t}\n\n\t\tthis.cachedType = Atomics.load(this.uint16Array, 0);\n\t\t// dataLength defaults to at least one even when initialized from raw memory that was never explicitly set\n\t\tthis.cachedDataLength = Math.max(1, Atomics.load(this.uint16Array, 1));\n\t}\n\n\tinsert(values: number | Array<number>) {\n\t\tif(typeof values === 'number') {\n\t\t\tvalues = [values];\n\t\t}\n\n\t\tlet dataLength = this.cachedDataLength;\n\t\tif(values.length > dataLength) {\n\t\t\tthrow new Error(`Can't insert ${values.length} array into shared list of ${dataLength} dataLength`);\n\t\t}\n\t\tlet newBlock = this.memory.allocUI32(DATA_BLOCK_RECORD_KEEPING_COUNT + dataLength);\n\t\tlet newData = this.getDataBlock(newBlock.data.buffer, newBlock.data.byteOffset);\n\t\tlet newBlockPointer = newBlock.pointer;\n\n\t\tfor(let i = 0; i < values.length; i++) {\n\t\t\tif(newData instanceof Int32Array || newData instanceof Uint32Array) {\n\t\t\t\tAtomics.store(newData, i, values[i]);\n\t\t\t} else {\n\t\t\t\t// TODO: Should we replace with pass thru float32 conversion -> store?\n\t\t\t\tnewData[i] = values[i];\n\t\t\t}\n\t\t}\n\n\t\t// Michael-Scott enqueue: link the node onto tail.next BEFORE swinging the tail hint, so a committed node is never\n\t\t// invisible to a concurrent traversal. The tail hint may lag by one node; enqueuers help advance it.\n\t\t// eslint-disable-next-line no-constant-condition\n\t\twhile(true) {\n\t\t\tlet tailPointer = loadRawPointer(this.firstBlock.data, TAIL_INDEX);\n\t\t\tlet view = this.getBufferView(tailPointer & POSITION_MASK);\n\t\t\t// Tail node lives in a buffer this thread hasn't synced yet - retry until it appears\n\t\t\tif(!view) {\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tlet tailNextIndex = ((tailPointer >>> POSITION_BIT_COUNT) >>> 2) + NODE_NEXT_INDEX;\n\t\t\tlet nextPointer = Atomics.load(view, tailNextIndex);\n\n\t\t\t// Re-check the hint hasn't moved out from under us before acting on what we read\n\t\t\tif(tailPointer !== loadRawPointer(this.firstBlock.data, TAIL_INDEX)) {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tif(nextPointer === 0) {\n\t\t\t\tif(Atomics.compareExchange(view, tailNextIndex, 0, newBlockPointer) === 0) {\n\t\t\t\t\t// Linked. Try to swing the hint forward - failure is harmless, the next enqueuer will help.\n\t\t\t\t\treplaceRawPointer(this.firstBlock.data, TAIL_INDEX, newBlockPointer, tailPointer);\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\t// Tail hint is lagging behind a linked node - help it catch up, then retry\n\t\t\t\treplaceRawPointer(this.firstBlock.data, TAIL_INDEX, nextPointer, tailPointer);\n\t\t\t}\n\t\t}\n\n\t\tAtomics.add(this.firstBlock.data, LENGTH_INDEX, 1);\n\t}\n\n\tdeleteMatch(callback: (values: T, index: number) => boolean): boolean {\n\t\tfor(let { data, index, deleteCurrent } of this) {\n\t\t\tif(callback(data, index)) {\n\t\t\t\tif(deleteCurrent()) {\n\t\t\t\t\treturn true;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\treturn false;\n\t}\n\tdeleteIndex(deleteIndex: number): boolean {\n\t\tif(deleteIndex >= this.length || deleteIndex < 0) {\n\t\t\treturn false;\n\t\t}\n\n\t\t// Compare on index only so the payload view is never materialized for an index-based delete\n\t\tfor(let item of this) {\n\t\t\tif(item.index === deleteIndex) {\n\t\t\t\tif(item.deleteCurrent()) {\n\t\t\t\t\treturn true;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\treturn false;\n\t}\n\tdeleteValue(deleteValues: number | Array<number>) {\n\t\tif(typeof deleteValues === 'number') {\n\t\t\treturn this.deleteMatch(values => values[0] === deleteValues);\n\t\t} else {\n\t\t\treturn this.deleteMatch(values => {\n\t\t\t\tif(values.length !== deleteValues.length) {\n\t\t\t\t\treturn false;\n\t\t\t\t} else {\n\t\t\t\t\tfor(let i = 0; i < values.length; i++) {\n\t\t\t\t\t\tif(values[i] !== deleteValues[i]) {\n\t\t\t\t\t\t\treturn false;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t\treturn true;\n\t\t\t\t}\n\t\t\t});\n\t\t}\n\t}\n\n\t// Reclaims tombstoned nodes: physically unlinks and frees them. Not safe to run concurrently with insert/delete.\n\tcompact() {\n\t\tlet prevPointer = this.firstBlock.pointer;\n\t\tlet prevView = this.firstBlock.data;\n\t\tlet prevNextIndex = NODE_NEXT_INDEX;\n\t\tlet nextPointer = loadRawPointer(prevView, prevNextIndex);\n\t\twhile(nextPointer) {\n\t\t\tlet position = nextPointer & POSITION_MASK;\n\t\t\tlet memPool = this.memory.buffers[position];\n\t\t\tif(!memPool) {\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tlet view = this.getBufferView(position)!;\n\t\t\tlet byteOffset = nextPointer >>> POSITION_BIT_COUNT;\n\t\t\tlet nodeIndex = byteOffset >>> 2;\n\t\t\tlet followingPointer = Atomics.load(view, nodeIndex + NODE_NEXT_INDEX);\n\n\t\t\tif(Atomics.load(view, nodeIndex + NODE_DELETED_INDEX)) {\n\t\t\t\t// Skip over the tombstone and free it. onDelete already fired when the node was logically deleted.\n\t\t\t\tstoreRawPointer(prevView, prevNextIndex, followingPointer);\n\t\t\t\tmemPool.free(byteOffset);\n\t\t\t} else {\n\t\t\t\tprevPointer = nextPointer;\n\t\t\t\tprevView = view;\n\t\t\t\tprevNextIndex = nodeIndex + NODE_NEXT_INDEX;\n\t\t\t}\n\n\t\t\tnextPointer = followingPointer;\n\t\t}\n\n\t\t// The last surviving node (or the sentinel when everything is gone) is the new tail\n\t\tstoreRawPointer(this.firstBlock.data, TAIL_INDEX, prevPointer);\n\t}\n\n\tclear() {\n\t\t// Detach the whole chain, then free it. Not safe to run concurrently with insert/delete.\n\t\tlet firstBlockPointer;\n\t\tlet updateWorked = false;\n\t\twhile(!updateWorked) {\n\t\t\tfirstBlockPointer = loadRawPointer(this.firstBlock.data, HEAD_INDEX);\n\t\t\t// Already cleared\n\t\t\tif(!firstBlockPointer) {\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tupdateWorked = replaceRawPointer(this.firstBlock.data, HEAD_INDEX, 0, firstBlockPointer);\n\t\t}\n\t\t// Shouldn't be possible to hit: making Typescript happy\n\t\tif(!firstBlockPointer) {\n\t\t\treturn;\n\t\t}\n\n\t\t// Reset the tail hint back to the sentinel\n\t\tstoreRawPointer(this.firstBlock.data, TAIL_INDEX, this.firstBlock.pointer);\n\n\t\tlet liveItems = 0;\n\t\tlet nextBlockPointer = firstBlockPointer;\n\t\twhile(nextBlockPointer) {\n\t\t\tlet position = nextBlockPointer & POSITION_MASK;\n\t\t\tlet memPool = this.memory.buffers[position];\n\t\t\t// Short circuit iterations if we can't access memory\n\t\t\tif(!memPool) {\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\tlet view = this.getBufferView(position)!;\n\t\t\tlet byteOffset = nextBlockPointer >>> POSITION_BIT_COUNT;\n\t\t\tlet nodeIndex = byteOffset >>> 2;\n\t\t\tnextBlockPointer = Atomics.load(view, nodeIndex + NODE_NEXT_INDEX);\n\n\t\t\tif(!Atomics.load(view, nodeIndex + NODE_DELETED_INDEX)) {\n\t\t\t\tliveItems++;\n\t\t\t\tif(this.onDelete) {\n\t\t\t\t\tthis.onDelete(this.getDataBlock(view.buffer, byteOffset));\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tmemPool.free(byteOffset);\n\t\t}\n\n\t\t// Only subtract the live nodes we detached so a concurrent insert's increment stays accurate\n\t\tAtomics.sub(this.firstBlock.data, LENGTH_INDEX, liveItems);\n\t}\n\n\t*[Symbol.iterator]() {\n\t\tlet currentIndex = 0;\n\t\tlet nextPointer = loadRawPointer(this.firstBlock.data, HEAD_INDEX);\n\t\twhile(nextPointer) {\n\t\t\tlet position = nextPointer & POSITION_MASK;\n\t\t\tlet view = this.getBufferView(position);\n\t\t\t// Short circuit iterations if we can't access memory\n\t\t\tif(!view) {\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tlet byteOffset = nextPointer >>> POSITION_BIT_COUNT;\n\t\t\tlet nodeIndex = byteOffset >>> 2;\n\t\t\tnextPointer = Atomics.load(view, nodeIndex + NODE_NEXT_INDEX);\n\n\t\t\t// Skip tombstoned nodes - they stay linked until a quiescent compact()/clear()\n\t\t\tif(Atomics.load(view, nodeIndex + NODE_DELETED_INDEX)) {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tlet buffer = view.buffer;\n\t\t\tlet deletedIndex = nodeIndex + NODE_DELETED_INDEX;\n\t\t\t// Plain object with an eager data view: V8 keeps this monomorphic and fast. A lazy `get data()` accessor is far\n\t\t\t// slower to allocate than the payload view it would save, so pay for the view up front.\n\t\t\tyield {\n\t\t\t\tdata: this.getDataBlock(buffer, byteOffset),\n\t\t\t\tindex: currentIndex,\n\t\t\t\t// Logical delete: tombstone the node and drop length. Returns false if another thread beat us to it.\n\t\t\t\tdeleteCurrent: (): boolean => {\n\t\t\t\t\tif(Atomics.compareExchange(view, deletedIndex, 0, 1) === 0) {\n\t\t\t\t\t\tAtomics.sub(this.firstBlock.data, LENGTH_INDEX, 1);\n\t\t\t\t\t\tif(this.onDelete) {\n\t\t\t\t\t\t\tthis.onDelete(this.getDataBlock(buffer, byteOffset));\n\t\t\t\t\t\t}\n\t\t\t\t\t\treturn true;\n\t\t\t\t\t}\n\n\t\t\t\t\treturn false;\n\t\t\t\t},\n\t\t\t};\n\n\t\t\tcurrentIndex++;\n\t\t}\n\t}\n\n\tforEach(callback: (data: T) => void) {\n\t\tfor(let value of this) {\n\t\t\tcallback(value.data);\n\t\t}\n\t}\n\n\tgetSharedMemory(): SharedListMemory {\n\t\treturn {\n\t\t\tfirstBlock: this.firstBlock.getSharedMemory(),\n\t\t};\n\t}\n\n\tprivate getBufferView(position: number): Uint32Array | undefined {\n\t\tlet view = this.bufferViews[position];\n\t\tif(view) {\n\t\t\treturn view;\n\t\t}\n\n\t\tlet buffer = this.memory.buffers[position];\n\t\tif(!buffer) {\n\t\t\treturn undefined;\n\t\t}\n\n\t\tview = new Uint32Array(buffer.buf);\n\t\tthis.bufferViews[position] = view;\n\t\treturn view;\n\t}\n\n\tprivate getDataBlock(buffer: ArrayBufferLike, nodeByteOffset: number): T {\n\t\tconst startIndex = nodeByteOffset + DATA_BLOCK_BYTE_OFFSET;\n\n\t\tswitch(this.cachedType) {\n\t\t\tcase TYPE.int32:\n\t\t\t\treturn new Int32Array(buffer, startIndex, this.cachedDataLength) as T;\n\t\t\tcase TYPE.uint32:\n\t\t\t\treturn new Uint32Array(buffer, startIndex, this.cachedDataLength) as T;\n\t\t\tcase TYPE.float32:\n\t\t\t\treturn new Float32Array(buffer, startIndex, this.cachedDataLength) as T;\n\t\t\tdefault:\n\t\t\t\tthrow new Error(`Unknown data block type ${this.cachedType}`);\n\t\t}\n\t}\n\n\tfree() {\n\t\tlet { bufferPosition: nextBlockPosition, bufferByteOffset: nextBlockByteOffset } = loadPointer(this.firstBlock.data, HEAD_INDEX);\n\t\twhile(nextBlockByteOffset) {\n\t\t\tlet allocatedMemory = new AllocatedMemory(this.memory, {\n\t\t\t\tbufferPosition: nextBlockPosition,\n\t\t\t\tbufferByteOffset: nextBlockByteOffset,\n\t\t\t});\n\n\t\t\t({ bufferPosition: nextBlockPosition, bufferByteOffset: nextBlockByteOffset } = loadPointer(allocatedMemory.data, NODE_NEXT_INDEX));\n\n\t\t\tif(this.onDelete && !Atomics.load(allocatedMemory.data, NODE_DELETED_INDEX)) {\n\t\t\t\tthis.onDelete(this.getDataBlock(allocatedMemory.data.buffer, allocatedMemory.data.byteOffset));\n\t\t\t}\n\n\t\t\tallocatedMemory.free();\n\t\t}\n\n\t\tthis.firstBlock.free();\n\t}\n}\n\ninterface SharedListConfig<T extends Uint32Array | Int32Array | Float32Array> {\n\tinitWithBlock?: SharedAllocatedMemory\n\ttype?: TypedArrayConstructor<T>\n\tdataLength?: number\n}\ninterface SharedListMemory {\n\tfirstBlock: SharedAllocatedMemory\n}\n\ninterface SharedListIterable<T extends Uint32Array | Int32Array | Float32Array> {\n\tdata: T\n\tindex: number\n\tdeleteCurrent: () => boolean\n}\n\nexport { type SharedListMemory };\n"],"mappings":"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"}
1
+ {"version":3,"file":"shared-list.js","names":[],"sources":["../src/shared-list.ts"],"sourcesContent":["import type { SharedAllocatedMemory } from './allocated-memory';\nimport AllocatedMemory from './allocated-memory';\nimport type MemoryHeap from './memory-heap';\nimport { loadPointer, loadRawPointer, replaceRawPointer, storeRawPointer } from './utils/pointer';\nimport { getArrayTypeCode, getByteMultipler, isBigIntType, isFloatType, makeArrayView, type NumericArray, type NumericArrayIO, type TypedArrayConstructor } from './utils/array-type';\n\n// Lock-free singly-linked list. firstBlock doubles as an immortal sentinel node whose next pointer (index 0) is the head.\n// insert appends with a Michael-Scott CAS enqueue; delete only tombstones a node (never unlinks/frees). Memory is\n// reclaimed exclusively by the quiescent compact()/clear()/free() passes. That deferral is what makes the CAS loops\n// safe without hazard pointers: a live node's address is never handed back to the allocator, so no append can ever\n// observe a recycled address (ABA) while another thread is mid-traversal.\nconst FIRST_BLOCK_RECORD_KEEPING_COUNT = 4;\nconst DATA_BLOCK_RECORD_KEEPING_COUNT = 2;\nconst DATA_BLOCK_BYTE_OFFSET = DATA_BLOCK_RECORD_KEEPING_COUNT * Uint32Array.BYTES_PER_ELEMENT;\nconst HEAD_INDEX = 0;\nconst TAIL_INDEX = 1;\nconst LENGTH_INDEX = 2;\nconst NODE_NEXT_INDEX = 0;\nconst NODE_DELETED_INDEX = 1;\n// A pointer packs the buffer position in its low `positionBits` bits and the byte offset in the high bits (see\n// utils/pointer). The split is per-heap, so it is read from the heap and cached rather than a module constant.\nexport default class SharedList<T extends NumericArray = Uint32Array> implements Iterable<SharedListIterable<T>> {\n\tstatic readonly ALLOCATE_COUNT = FIRST_BLOCK_RECORD_KEEPING_COUNT;\n\n\tprivate memory: MemoryHeap;\n\t// Per-heap pointer split, cached from the heap. Hoisted into locals inside the hot loops below.\n\tprivate positionBits: number;\n\tprivate positionMask: number;\n\t/* First block (also the sentinel node - index 0 is its next pointer = list head)\n\t\t32 index 0\n\t\tuint16 0 - head buffer position | sentinel.next\n\t\tuint16 1 - head buffer index\n\t\t32 index 1\n\t\tuint16 2 - tail hint buffer position\n\t\tuint16 3 - tail hint buffer index\n\t\t32 index 2\n\t\tuint32 4 - length\n\t\t32 index 3\n\t\tuint16 6 - type\n\t\tuint16 7 - data length (defaults to 1 number per data)\n\t*/\n\t/* Other blocks\n\t\t32 index 0 - next buffer pointer\n\t\t32 index 1 - deleted flag (0 = live, 1 = tombstoned)\n\t\t32 index 2 => data\n\t*/\n\tprivate firstBlock: AllocatedMemory;\n\tprivate uint16Array: Uint16Array;\n\t// type and dataLength are written once at init and never change, so cache them instead of atomic-loading per node\n\tprivate cachedType: number;\n\tprivate cachedDataLength: number;\n\t// u32 units per element: 1 for 32-bit types, 2 for 64-bit (float64/int64/uint64). Node data blocks scale by this.\n\tprivate cachedByteMultipler = 1;\n\t// Whether the value type is a BigInt view (int64/uint64), so insert stores bigint via Atomics; cached to keep the\n\t// per-insert branch off an instanceof check\n\tprivate cachedIsBigInt = false;\n\t// Float views can't be published with Atomics, so insert falls back to a plain store for them\n\tprivate cachedIsFloat = false;\n\t// One full-buffer Uint32Array per buffer position, so traversal indexes shared memory directly instead of\n\t// allocating a fresh view (and a getPointer object) for every node it touches\n\tprivate bufferViews: Array<Uint32Array> = [];\n\tonDelete?: (data: T) => void;\n\n\tget length(): number {\n\t\treturn Atomics.load(this.firstBlock.data, LENGTH_INDEX);\n\t}\n\n\t// Own internal memory plus every allocated node still linked in the chain (tombstoned nodes included, since they stay\n\t// allocated until a compact()/clear() reclaims them).\n\tget usedMemory(): number {\n\t\tlet total = this.firstBlock.usedMemory;\n\t\tlet { bufferPosition, bufferByteOffset } = loadPointer(this.firstBlock.data, HEAD_INDEX, this.positionBits);\n\t\twhile(bufferByteOffset) {\n\t\t\tlet node = new AllocatedMemory(this.memory, { bufferPosition, bufferByteOffset });\n\t\t\ttotal += node.usedMemory;\n\t\t\t({ bufferPosition, bufferByteOffset } = loadPointer(node.data, NODE_NEXT_INDEX, this.positionBits));\n\t\t}\n\t\treturn total;\n\t}\n\n\tget type(): number {\n\t\treturn this.cachedType;\n\t}\n\tprivate set type(value: number) {\n\t\tAtomics.store(this.uint16Array, 0, value);\n\t}\n\tget dataLength(): number {\n\t\treturn this.cachedDataLength;\n\t}\n\tprivate set dataLength(value: number) {\n\t\tAtomics.store(this.uint16Array, 1, value);\n\t}\n\n\tconstructor(memory: MemoryHeap, config?: SharedListConfig<T> | SharedListMemory) {\n\t\tthis.memory = memory;\n\t\tthis.positionBits = memory.positionBits;\n\t\tthis.positionMask = (1 << this.positionBits) - 1;\n\n\t\tif(config && 'firstBlock' in config) {\n\t\t\t// TODO: How to handle referencing memory we don't have access to yet because buffer not synced from worker?\n\t\t\tthis.firstBlock = new AllocatedMemory(memory, config.firstBlock);\n\t\t\tthis.uint16Array = new Uint16Array(this.firstBlock.data.buffer, this.firstBlock.bufferByteOffset + (LENGTH_INDEX + 1) * Uint32Array.BYTES_PER_ELEMENT, 2);\n\t\t} else {\n\t\t\tif(config && config.initWithBlock) {\n\t\t\t\tthis.firstBlock = new AllocatedMemory(memory, config.initWithBlock);\n\t\t\t} else {\n\t\t\t\tthis.firstBlock = memory.allocUI32(FIRST_BLOCK_RECORD_KEEPING_COUNT);\n\t\t\t}\n\t\t\tthis.uint16Array = new Uint16Array(this.firstBlock.data.buffer, this.firstBlock.bufferByteOffset + (LENGTH_INDEX + 1) * Uint32Array.BYTES_PER_ELEMENT, 2);\n\n\t\t\t// Empty list: the tail hint points at the sentinel (firstBlock) so insert never special-cases the first node\n\t\t\tstoreRawPointer(this.firstBlock.data, TAIL_INDEX, this.firstBlock.pointer);\n\n\t\t\tthis.type = getArrayTypeCode(config?.type ?? Uint32Array);\n\t\t\tthis.dataLength = config?.dataLength ?? 1;\n\t\t}\n\n\t\tthis.cachedType = Atomics.load(this.uint16Array, 0);\n\t\t// dataLength defaults to at least one even when initialized from raw memory that was never explicitly set\n\t\tthis.cachedDataLength = Math.max(1, Atomics.load(this.uint16Array, 1));\n\t\tthis.cachedByteMultipler = getByteMultipler(this.cachedType);\n\t\tthis.cachedIsBigInt = isBigIntType(this.cachedType);\n\t\tthis.cachedIsFloat = isFloatType(this.cachedType);\n\t}\n\n\tinsert(values: T[number] | Array<T[number]>) {\n\t\tif(!Array.isArray(values)) {\n\t\t\tvalues = [values];\n\t\t}\n\n\t\tlet dataLength = this.cachedDataLength;\n\t\tif(values.length > dataLength) {\n\t\t\tthrow new Error(`Can't insert ${values.length} array into shared list of ${dataLength} dataLength`);\n\t\t}\n\t\tlet newBlock = this.memory.allocUI32(DATA_BLOCK_RECORD_KEEPING_COUNT + dataLength * this.cachedByteMultipler);\n\t\tlet newData = this.getDataBlock(newBlock.data.buffer, newBlock.data.byteOffset);\n\t\tlet newBlockPointer = newBlock.pointer;\n\n\t\tlet io = newData as NumericArrayIO;\n\t\tfor(let i = 0; i < values.length; i++) {\n\t\t\tif(this.cachedIsFloat) {\n\t\t\t\t// Float views don't support Atomics; the enqueue CAS below still provides the cross-thread publish\n\t\t\t\tio[i] = values[i];\n\t\t\t} else if(this.cachedIsBigInt) {\n\t\t\t\tAtomics.store(newData as BigInt64Array, i, values[i] as bigint);\n\t\t\t} else {\n\t\t\t\tAtomics.store(newData as Int32Array, i, values[i] as number);\n\t\t\t}\n\t\t}\n\n\t\t// Michael-Scott enqueue: link the node onto tail.next BEFORE swinging the tail hint, so a committed node is never\n\t\t// invisible to a concurrent traversal. The tail hint may lag by one node; enqueuers help advance it.\n\t\tlet positionBits = this.positionBits;\n\t\tlet positionMask = this.positionMask;\n\t\t// eslint-disable-next-line no-constant-condition\n\t\twhile(true) {\n\t\t\tlet tailPointer = loadRawPointer(this.firstBlock.data, TAIL_INDEX);\n\t\t\tlet view = this.getBufferView(tailPointer & positionMask);\n\t\t\t// Tail node lives in a buffer this thread hasn't synced yet - retry until it appears\n\t\t\tif(!view) {\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tlet tailNextIndex = ((tailPointer >>> positionBits) >>> 2) + NODE_NEXT_INDEX;\n\t\t\tlet nextPointer = Atomics.load(view, tailNextIndex);\n\n\t\t\t// Re-check the hint hasn't moved out from under us before acting on what we read\n\t\t\tif(tailPointer !== loadRawPointer(this.firstBlock.data, TAIL_INDEX)) {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tif(nextPointer === 0) {\n\t\t\t\tif(Atomics.compareExchange(view, tailNextIndex, 0, newBlockPointer) === 0) {\n\t\t\t\t\t// Linked. Try to swing the hint forward - failure is harmless, the next enqueuer will help.\n\t\t\t\t\treplaceRawPointer(this.firstBlock.data, TAIL_INDEX, newBlockPointer, tailPointer);\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\t// Tail hint is lagging behind a linked node - help it catch up, then retry\n\t\t\t\treplaceRawPointer(this.firstBlock.data, TAIL_INDEX, nextPointer, tailPointer);\n\t\t\t}\n\t\t}\n\n\t\tAtomics.add(this.firstBlock.data, LENGTH_INDEX, 1);\n\t}\n\n\tdeleteMatch(callback: (values: T, index: number) => boolean): boolean {\n\t\tfor(let { data, index, deleteCurrent } of this) {\n\t\t\tif(callback(data, index)) {\n\t\t\t\tif(deleteCurrent()) {\n\t\t\t\t\treturn true;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\treturn false;\n\t}\n\tdeleteIndex(deleteIndex: number): boolean {\n\t\tif(deleteIndex >= this.length || deleteIndex < 0) {\n\t\t\treturn false;\n\t\t}\n\n\t\t// Compare on index only so the payload view is never materialized for an index-based delete\n\t\tfor(let item of this) {\n\t\t\tif(item.index === deleteIndex) {\n\t\t\t\tif(item.deleteCurrent()) {\n\t\t\t\t\treturn true;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\treturn false;\n\t}\n\tdeleteValue(deleteValues: T[number] | Array<T[number]>) {\n\t\tif(!Array.isArray(deleteValues)) {\n\t\t\treturn this.deleteMatch(values => values[0] === deleteValues);\n\t\t} else {\n\t\t\treturn this.deleteMatch(values => {\n\t\t\t\tif(values.length !== deleteValues.length) {\n\t\t\t\t\treturn false;\n\t\t\t\t} else {\n\t\t\t\t\tfor(let i = 0; i < values.length; i++) {\n\t\t\t\t\t\tif(values[i] !== deleteValues[i]) {\n\t\t\t\t\t\t\treturn false;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t\treturn true;\n\t\t\t\t}\n\t\t\t});\n\t\t}\n\t}\n\n\t// Reclaims tombstoned nodes: physically unlinks and frees them. Not safe to run concurrently with insert/delete.\n\tcompact() {\n\t\tlet prevPointer = this.firstBlock.pointer;\n\t\tlet prevView = this.firstBlock.data;\n\t\tlet prevNextIndex = NODE_NEXT_INDEX;\n\t\tlet positionBits = this.positionBits;\n\t\tlet positionMask = this.positionMask;\n\t\tlet nextPointer = loadRawPointer(prevView, prevNextIndex);\n\t\twhile(nextPointer) {\n\t\t\tlet position = nextPointer & positionMask;\n\t\t\tlet memPool = this.memory.buffers[position];\n\t\t\tif(!memPool) {\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tlet view = this.getBufferView(position)!;\n\t\t\tlet byteOffset = nextPointer >>> positionBits;\n\t\t\tlet nodeIndex = byteOffset >>> 2;\n\t\t\tlet followingPointer = Atomics.load(view, nodeIndex + NODE_NEXT_INDEX);\n\n\t\t\tif(Atomics.load(view, nodeIndex + NODE_DELETED_INDEX)) {\n\t\t\t\t// Skip over the tombstone and free it. onDelete already fired when the node was logically deleted.\n\t\t\t\tstoreRawPointer(prevView, prevNextIndex, followingPointer);\n\t\t\t\tmemPool.free(byteOffset);\n\t\t\t} else {\n\t\t\t\tprevPointer = nextPointer;\n\t\t\t\tprevView = view;\n\t\t\t\tprevNextIndex = nodeIndex + NODE_NEXT_INDEX;\n\t\t\t}\n\n\t\t\tnextPointer = followingPointer;\n\t\t}\n\n\t\t// The last surviving node (or the sentinel when everything is gone) is the new tail\n\t\tstoreRawPointer(this.firstBlock.data, TAIL_INDEX, prevPointer);\n\t}\n\n\tclear() {\n\t\t// Detach the whole chain, then free it. Not safe to run concurrently with insert/delete.\n\t\tlet firstBlockPointer;\n\t\tlet updateWorked = false;\n\t\twhile(!updateWorked) {\n\t\t\tfirstBlockPointer = loadRawPointer(this.firstBlock.data, HEAD_INDEX);\n\t\t\t// Already cleared\n\t\t\tif(!firstBlockPointer) {\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tupdateWorked = replaceRawPointer(this.firstBlock.data, HEAD_INDEX, 0, firstBlockPointer);\n\t\t}\n\t\t// Shouldn't be possible to hit: making Typescript happy\n\t\tif(!firstBlockPointer) {\n\t\t\treturn;\n\t\t}\n\n\t\t// Reset the tail hint back to the sentinel\n\t\tstoreRawPointer(this.firstBlock.data, TAIL_INDEX, this.firstBlock.pointer);\n\n\t\tlet liveItems = 0;\n\t\tlet positionBits = this.positionBits;\n\t\tlet positionMask = this.positionMask;\n\t\tlet nextBlockPointer = firstBlockPointer;\n\t\twhile(nextBlockPointer) {\n\t\t\tlet position = nextBlockPointer & positionMask;\n\t\t\tlet memPool = this.memory.buffers[position];\n\t\t\t// Short circuit iterations if we can't access memory\n\t\t\tif(!memPool) {\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\tlet view = this.getBufferView(position)!;\n\t\t\tlet byteOffset = nextBlockPointer >>> positionBits;\n\t\t\tlet nodeIndex = byteOffset >>> 2;\n\t\t\tnextBlockPointer = Atomics.load(view, nodeIndex + NODE_NEXT_INDEX);\n\n\t\t\tif(!Atomics.load(view, nodeIndex + NODE_DELETED_INDEX)) {\n\t\t\t\tliveItems++;\n\t\t\t\tif(this.onDelete) {\n\t\t\t\t\tthis.onDelete(this.getDataBlock(view.buffer, byteOffset));\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tmemPool.free(byteOffset);\n\t\t}\n\n\t\t// Only subtract the live nodes we detached so a concurrent insert's increment stays accurate\n\t\tAtomics.sub(this.firstBlock.data, LENGTH_INDEX, liveItems);\n\t}\n\n\t*[Symbol.iterator]() {\n\t\tlet currentIndex = 0;\n\t\tlet positionBits = this.positionBits;\n\t\tlet positionMask = this.positionMask;\n\t\tlet nextPointer = loadRawPointer(this.firstBlock.data, HEAD_INDEX);\n\t\twhile(nextPointer) {\n\t\t\tlet position = nextPointer & positionMask;\n\t\t\tlet view = this.getBufferView(position);\n\t\t\t// Short circuit iterations if we can't access memory\n\t\t\tif(!view) {\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tlet byteOffset = nextPointer >>> positionBits;\n\t\t\tlet nodeIndex = byteOffset >>> 2;\n\t\t\tnextPointer = Atomics.load(view, nodeIndex + NODE_NEXT_INDEX);\n\n\t\t\t// Skip tombstoned nodes - they stay linked until a quiescent compact()/clear()\n\t\t\tif(Atomics.load(view, nodeIndex + NODE_DELETED_INDEX)) {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tlet buffer = view.buffer;\n\t\t\tlet deletedIndex = nodeIndex + NODE_DELETED_INDEX;\n\t\t\t// Plain object with an eager data view: V8 keeps this monomorphic and fast. A lazy `get data()` accessor is far\n\t\t\t// slower to allocate than the payload view it would save, so pay for the view up front.\n\t\t\tyield {\n\t\t\t\tdata: this.getDataBlock(buffer, byteOffset),\n\t\t\t\tindex: currentIndex,\n\t\t\t\t// Logical delete: tombstone the node and drop length. Returns false if another thread beat us to it.\n\t\t\t\tdeleteCurrent: (): boolean => {\n\t\t\t\t\tif(Atomics.compareExchange(view, deletedIndex, 0, 1) === 0) {\n\t\t\t\t\t\tAtomics.sub(this.firstBlock.data, LENGTH_INDEX, 1);\n\t\t\t\t\t\tif(this.onDelete) {\n\t\t\t\t\t\t\tthis.onDelete(this.getDataBlock(buffer, byteOffset));\n\t\t\t\t\t\t}\n\t\t\t\t\t\treturn true;\n\t\t\t\t\t}\n\n\t\t\t\t\treturn false;\n\t\t\t\t},\n\t\t\t};\n\n\t\t\tcurrentIndex++;\n\t\t}\n\t}\n\n\tforEach(callback: (data: T) => void) {\n\t\tfor(let value of this) {\n\t\t\tcallback(value.data);\n\t\t}\n\t}\n\n\tgetSharedMemory(): SharedListMemory {\n\t\treturn {\n\t\t\tfirstBlock: this.firstBlock.getSharedMemory(),\n\t\t};\n\t}\n\n\tprivate getBufferView(position: number): Uint32Array | undefined {\n\t\tlet view = this.bufferViews[position];\n\t\tif(view) {\n\t\t\treturn view;\n\t\t}\n\n\t\tlet buffer = this.memory.buffers[position];\n\t\tif(!buffer) {\n\t\t\treturn undefined;\n\t\t}\n\n\t\tview = new Uint32Array(buffer.buf);\n\t\tthis.bufferViews[position] = view;\n\t\treturn view;\n\t}\n\n\tprivate getDataBlock(buffer: ArrayBufferLike, nodeByteOffset: number): T {\n\t\tconst startIndex = nodeByteOffset + DATA_BLOCK_BYTE_OFFSET;\n\t\treturn makeArrayView(this.cachedType, buffer, startIndex, this.cachedDataLength) as T;\n\t}\n\n\tfree() {\n\t\tlet { bufferPosition: nextBlockPosition, bufferByteOffset: nextBlockByteOffset } = loadPointer(this.firstBlock.data, HEAD_INDEX, this.positionBits);\n\t\twhile(nextBlockByteOffset) {\n\t\t\tlet allocatedMemory = new AllocatedMemory(this.memory, {\n\t\t\t\tbufferPosition: nextBlockPosition,\n\t\t\t\tbufferByteOffset: nextBlockByteOffset,\n\t\t\t});\n\n\t\t\t({ bufferPosition: nextBlockPosition, bufferByteOffset: nextBlockByteOffset } = loadPointer(allocatedMemory.data, NODE_NEXT_INDEX, this.positionBits));\n\n\t\t\tif(this.onDelete && !Atomics.load(allocatedMemory.data, NODE_DELETED_INDEX)) {\n\t\t\t\tthis.onDelete(this.getDataBlock(allocatedMemory.data.buffer, allocatedMemory.data.byteOffset));\n\t\t\t}\n\n\t\t\tallocatedMemory.free();\n\t\t}\n\n\t\tthis.firstBlock.free();\n\t}\n}\n\ninterface SharedListConfig<T extends NumericArray> {\n\tinitWithBlock?: SharedAllocatedMemory\n\ttype?: TypedArrayConstructor<T>\n\tdataLength?: number\n}\ninterface SharedListMemory {\n\tfirstBlock: SharedAllocatedMemory\n}\n\ninterface SharedListIterable<T extends NumericArray> {\n\tdata: T\n\tindex: number\n\tdeleteCurrent: () => boolean\n}\n\nexport { type SharedListMemory };\n"],"mappings":"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"}
@@ -1,25 +1,48 @@
1
- import { type SharedAllocatedMemory } from './allocated-memory';
1
+ import AllocatedMemory, { type SharedAllocatedMemory } from './allocated-memory';
2
2
  import type MemoryHeap from './memory-heap';
3
- export default class SharedMap<K extends string | number> {
4
- static readonly ALLOCATE_COUNT = 4;
3
+ import { type NumericArray, type TypedArrayConstructor } from './utils/array-type';
4
+ export default class SharedMap<K extends string | number, V extends NumericArray = Uint32Array> {
5
+ static readonly ALLOCATE_COUNT = 6;
5
6
  private memory;
6
7
  private pointerMemory;
8
+ private ownsFirstBlock;
7
9
  private lock;
8
- private get hashMemory();
10
+ private cachedType;
11
+ private cachedValueUnits;
12
+ private cachedTablePointer;
13
+ private cachedTableMemory?;
14
+ private cachedSlots?;
15
+ private cachedValues?;
16
+ private cachedCapacity;
17
+ private ensureTable;
18
+ private makeValuesView;
19
+ private tableLength;
9
20
  get length(): number;
21
+ get capacity(): number;
10
22
  get maxHash(): number;
11
- constructor(memory: MemoryHeap, clone?: SharedMapMemory);
12
- set(key: K, value: number): void;
13
- private setHashKey;
14
- get(key: K): number | undefined;
23
+ get type(): number;
24
+ get usedMemory(): number;
25
+ constructor(memory: MemoryHeap, config?: SharedMapConfig<V> | SharedMapMemory | (SharedMapMemory & SharedMapConfig<V>));
26
+ private initialize;
27
+ set(key: K, value: V[number]): void;
28
+ setAll(entries: Iterable<readonly [K, V[number]]>): void;
29
+ private setNoLock;
30
+ update(key: K, updater: (current: V[number] | undefined) => V[number]): V[number];
31
+ get(key: K): V[number] | undefined;
32
+ getAll(keys: Iterable<K>): Array<V[number] | undefined>;
33
+ private getNoLock;
15
34
  has(key: K): boolean;
16
35
  delete(key: K): boolean;
17
- private growHashTable;
18
- private hash;
36
+ [Symbol.iterator](): IterableIterator<[number, V[number]]>;
37
+ private resize;
19
38
  free(): void;
20
39
  getSharedMemory(): SharedMapMemory;
21
40
  }
41
+ interface SharedMapConfig<V extends NumericArray> {
42
+ type?: TypedArrayConstructor<V>;
43
+ capacity?: number;
44
+ }
22
45
  interface SharedMapMemory {
23
- firstBlock: SharedAllocatedMemory;
46
+ firstBlock: SharedAllocatedMemory | AllocatedMemory;
24
47
  }
25
- export type { SharedMapMemory };
48
+ export type { SharedMapConfig, SharedMapMemory };