@daneren2005/shared-memory-objects 1.3.0 → 1.4.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.
package/README.md CHANGED
@@ -77,18 +77,15 @@ Freshly allocated with the default options:
77
77
  | SharedVector | 32 bytes |
78
78
  | SharedStack | 144 bytes |
79
79
  | SharedPool | 656 bytes |
80
+ | SharedMap | 216 bytes |
80
81
 
81
82
  ## Thread Safety
82
- - Memory allocations is thread safe as long as it does not need to create a new buffer. Right now that can only be done from the main thread.
83
- - SharedList, SharedPool and SharedStack are thread safe
83
+ - Memory allocations is thread safe as long as it does not need to create a new buffer. Right now that can only be done from the main thread. I just make sure there is always an extra empty buffer with SharedHeap.ensureSpareBuffer() before sending more work to worker threads.
84
+ - SharedList, SharedPool, SharedStack, and SharedMap are thread safe
84
85
  - SharedVector is *not* thread safe and probably never will be - it is useful for updating in main thread and then sending off to works for processing
85
- - SharedMap is *not* thread safe and is honestly so slow as to be basically worthless
86
86
  - SharedString is thread safe with a lock on read/write with a cached version of the string so it doesn't need to lock after the first read unless the string has changed.
87
87
  - ConstantString is safe to read from any thread without a lock precisely because it is never written after construction
88
88
 
89
- ## TODO
90
- - Make creating new buffers from allocations possible from multiple threads
91
- - Make map structure that isn't so slow as to be useless
92
89
 
93
90
  ## Performance
94
91
  The tl;dr is that none of these data structures are close to what you can get by just using native data structures, but I wasn't expecting them to be with their overhead.
@@ -199,5 +196,58 @@ local pool
199
196
  985.37x faster than shared list
200
197
  ```
201
198
 
199
+ ### SharedMap vs native Map
200
+ `SharedMap` uses open-addressing with linear probing over a contiguous SharedArrayBuffer table, so it stays within a small constant factor of the native `Map` rather than the order-of-magnitude gap seen elsewhere. Iterating and deleting are the closest to native; sets are the slowest since they can trigger a rehash. Keys of 1000 numbers, values randomized:
201
+
202
+ Shared Map: 1000 sets
203
+ ```
204
+ name hz min max mean p75 p99 p995 p999 rme samples
205
+ shared map 7,565.94 0.1166 1.1183 0.1322 0.1278 0.2565 0.2973 0.3598 ±0.76% 3783
206
+ native map 48,357.73 0.0177 0.2432 0.0207 0.0199 0.0808 0.0934 0.1398 ±0.57% 24179
207
+
208
+ native map
209
+ 6.39x faster than shared map
210
+ ```
211
+
212
+ Shared Map: 1000 overwrites
213
+ ```
214
+ name hz min max mean p75 p99 p995 p999 rme samples
215
+ shared map 16,906.24 0.0551 0.2499 0.0591 0.0569 0.1194 0.1433 0.1940 ±0.43% 8454
216
+ native map 83,196.44 0.0105 0.1686 0.0120 0.0119 0.0197 0.0231 0.0465 ±0.27% 41599
217
+
218
+ native map
219
+ 4.92x faster than shared map
220
+ ```
221
+
222
+ Shared Map: 1000 gets
223
+ ```
224
+ name hz min max mean p75 p99 p995 p999 rme samples
225
+ shared map 16,598.62 0.0494 0.3111 0.0602 0.0593 0.1121 0.1279 0.1718 ±0.70% 8300
226
+ native map 93,483.83 0.0073 2.7586 0.0107 0.0138 0.0222 0.0255 0.0398 ±1.23% 46742
227
+
228
+ native map
229
+ 5.63x faster than shared map
230
+ ```
231
+
232
+ Shared Map: 1000 deletes
233
+ ```
234
+ name hz min max mean p75 p99 p995 p999 rme samples
235
+ shared map 17,590.31 0.0497 0.2837 0.0568 0.0526 0.1252 0.1393 0.1909 ±0.59% 8796
236
+ native map 59,139.95 0.0147 0.2453 0.0169 0.0164 0.0291 0.0349 0.1017 ±0.36% 29570
237
+
238
+ native map
239
+ 3.36x faster than shared map
240
+ ```
241
+
242
+ Shared Map: iterate 1000 entries
243
+ ```
244
+ name hz min max mean p75 p99 p995 p999 rme samples
245
+ shared map 31,900.32 0.0213 0.4626 0.0313 0.0375 0.0552 0.2321 0.3119 ±1.03% 15951
246
+ native map 93,101.22 0.0068 0.1661 0.0107 0.0127 0.0194 0.0224 0.0320 ±0.25% 46551
247
+
248
+ native map
249
+ 2.92x faster than shared map
250
+ ```
251
+
202
252
  ## Credit
203
253
  The entire core of this library is based on a fork of @thi.ng/malloc found at https://github.com/thi-ng/umbrella/blob/develop/packages/malloc. The only big difference between our MemoryBuffer and their MemPool is making allocations/freeing memory thread safe.
@@ -1,6 +1,6 @@
1
- import type { TypedArrayConstructor } from './interfaces/typed-array-constructor';
2
1
  import type MemoryHeap from './memory-heap';
3
- export default class LocalPool<T extends Uint32Array | Int32Array | Float32Array | Float64Array = Uint32Array> implements Iterable<T> {
2
+ import { type NumericArray, type TypedArrayConstructor } from './utils/array-type';
3
+ export default class LocalPool<T extends NumericArray = Uint32Array> implements Iterable<T> {
4
4
  private memory;
5
5
  private pointers;
6
6
  private recycledIndexes;
@@ -14,8 +14,8 @@ export default class LocalPool<T extends Uint32Array | Int32Array | Float32Array
14
14
  get bufferLength(): number;
15
15
  constructor(memory: MemoryHeap, config: LocalPoolConfig<T>);
16
16
  at(index: number): T;
17
- get(index: number, dataIndex?: number): number;
18
- push(values: number | Array<number>): number;
17
+ get(index: number, dataIndex?: number): T[number];
18
+ push(values: T[number] | Array<T[number]>): number;
19
19
  deleteIndex(index: number): void;
20
20
  clear(): void;
21
21
  [Symbol.iterator](): Generator<T, void, unknown>;
@@ -23,7 +23,7 @@ export default class LocalPool<T extends Uint32Array | Int32Array | Float32Array
23
23
  private getDataBlock;
24
24
  free(): void;
25
25
  }
26
- interface LocalPoolConfig<T extends Uint32Array | Int32Array | Float32Array | Float64Array> {
26
+ interface LocalPoolConfig<T extends NumericArray> {
27
27
  maxChunkSize?: number;
28
28
  type: TypedArrayConstructor<T>;
29
29
  dataLength?: number;
@@ -1,7 +1,8 @@
1
1
  import { getPointer as e } from "./utils/pointer.js";
2
2
  import t from "./allocated-memory.js";
3
+ import { getArrayTypeCode as n, getByteMultipler as r } from "./utils/array-type.js";
3
4
  //#region src/local-pool.ts
4
- var n = class {
5
+ var i = class {
5
6
  memory;
6
7
  pointers = [];
7
8
  recycledIndexes = [];
@@ -19,12 +20,12 @@ var n = class {
19
20
  }
20
21
  constructor(e, t) {
21
22
  this.memory = e;
22
- let n = t?.dataLength ?? 1, r = t?.maxChunkSize ?? 100;
23
- this.dataLength = n, this.maxChunkSize = r, this.typeConstructor = t.type, this.typeConstructor === Float64Array && (this.byteMultipler = 2);
24
- let i = e.allocUI32(r * n * this.byteMultipler);
25
- this.pointers.push(i.pointer);
26
- let a = new this.typeConstructor(i.data.buffer, i.bufferByteOffset, this.dataLength * this.maxChunkSize);
27
- this.fullDataBlocks[0] = a;
23
+ let i = t?.dataLength ?? 1, a = t?.maxChunkSize ?? 100;
24
+ this.dataLength = i, this.maxChunkSize = a, this.typeConstructor = t.type, this.byteMultipler = r(n(t.type));
25
+ let o = e.allocUI32(a * i * this.byteMultipler);
26
+ this.pointers.push(o.pointer);
27
+ let s = new this.typeConstructor(o.data.buffer, o.bufferByteOffset, this.dataLength * this.maxChunkSize);
28
+ this.fullDataBlocks[0] = s;
28
29
  }
29
30
  at(e) {
30
31
  let t = this.fullDataBlocks[Math.floor(e / this.maxChunkSize)];
@@ -36,7 +37,7 @@ var n = class {
36
37
  return this.fullDataBlocks[Math.floor(e / this.maxChunkSize)][e % this.maxChunkSize * n + t];
37
38
  }
38
39
  push(e) {
39
- let t = typeof e == "number";
40
+ let t = typeof e != "object";
40
41
  if (!t && e.length > this.dataLength) throw Error(`Can't insert ${e.length} array into shared list of ${this.dataLength} dataLength`);
41
42
  let n = this.recycledIndexes.pop();
42
43
  n === void 0 && (n = this.totalLength++);
@@ -48,7 +49,9 @@ var n = class {
48
49
  this.fullDataBlocks[r] = t;
49
50
  }
50
51
  let i = this.fullDataBlocks[r], a = n % this.maxChunkSize, o = this.dataLength * a;
51
- return t ? i[o] = e : i.set(e, o), n;
52
+ if (t) i[o] = e;
53
+ else for (let t = 0; t < e.length; t++) i[o + t] = e[t];
54
+ return n;
52
55
  }
53
56
  deleteIndex(e) {
54
57
  this.recycledIndexes.push(e);
@@ -85,6 +88,6 @@ var n = class {
85
88
  }
86
89
  };
87
90
  //#endregion
88
- export { n as default };
91
+ export { i as default };
89
92
 
90
93
  //# sourceMappingURL=local-pool.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"local-pool.js","names":[],"sources":["../src/local-pool.ts"],"sourcesContent":["import AllocatedMemory from './allocated-memory';\nimport type { TypedArrayConstructor } from './interfaces/typed-array-constructor';\nimport type MemoryHeap from './memory-heap';\nimport { getPointer } from './utils/pointer';\n\n// Array with stable indexes and maximum contiguous memory sizes (necessary to fit large data sets into max 1MB buffers)\n// Can only do insert/deletions from a single thread - useful if other threads are known to be read-only\nexport default class LocalPool<T extends Uint32Array | Int32Array | Float32Array | Float64Array = Uint32Array> implements Iterable<T> {\n\tprivate memory: MemoryHeap;\n\tprivate pointers: Array<number> = [];\n\tprivate recycledIndexes: Array<number> = [];\n\tprivate totalLength = 0;\n\treadonly maxChunkSize: number = 100;\n\treadonly dataLength: number = 1;\n\treadonly byteMultipler: number = 1;\n\tprivate typeConstructor: TypedArrayConstructor<T>;\n\tprivate fullDataBlocks: { [key: number]: T } = {};\n\n\tget length(): number {\n\t\treturn this.totalLength - this.recycledIndexes.length;\n\t}\n\tget bufferLength(): number {\n\t\treturn this.maxChunkSize * this.pointers.length;\n\t}\n\n\tconstructor(memory: MemoryHeap, config: LocalPoolConfig<T>) {\n\t\tthis.memory = memory;\n\n\t\tconst dataLength = config?.dataLength ?? 1;\n\t\tconst maxLength = config?.maxChunkSize ?? 100;\n\t\tthis.dataLength = dataLength;\n\t\tthis.maxChunkSize = maxLength;\n\n\t\tthis.typeConstructor = config.type;\n\t\t// @ts-expect-error\n\t\tif(this.typeConstructor === Float64Array) {\n\t\t\tthis.byteMultipler = 2;\n\t\t}\n\n\t\tlet firstArray = memory.allocUI32(maxLength * dataLength * this.byteMultipler);\n\t\tthis.pointers.push(firstArray.pointer);\n\t\tlet data: T = new this.typeConstructor(firstArray.data.buffer, firstArray.bufferByteOffset, this.dataLength * this.maxChunkSize);\n\t\tthis.fullDataBlocks[0] = data;\n\t}\n\n\tat(index: number): T {\n\t\tlet dataBlock = this.fullDataBlocks[Math.floor(index / this.maxChunkSize)];\n\t\treturn this.getDataBlock(dataBlock, index % this.maxChunkSize);\n\t}\n\tget(index: number, dataIndex = 0): number {\n\t\tconst dataLength = this.dataLength;\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.fullDataBlocks[Math.floor(index / this.maxChunkSize)];\n\t\treturn dataBlock[(index % this.maxChunkSize) * dataLength + dataIndex];\n\t}\n\n\tpush(values: number | Array<number>): number {\n\t\tconst isSingleNumber = typeof values === 'number';\n\t\tif(!isSingleNumber && values.length > this.dataLength) {\n\t\t\tthrow new Error(`Can't insert ${values.length} array into shared list of ${this.dataLength} dataLength`);\n\t\t}\n\n\t\tlet newIndex = this.recycledIndexes.pop();\n\t\tif(newIndex === undefined) {\n\t\t\tnewIndex = this.totalLength++;\n\t\t}\n\t\t\n\t\t\n\t\tlet pointerIndex = Math.floor(newIndex / this.maxChunkSize);\n\t\tif(pointerIndex >= this.pointers.length) {\n\t\t\tlet newArray = this.memory.allocUI32(this.maxChunkSize * this.dataLength * this.byteMultipler);\n\t\t\tthis.pointers.push(newArray.pointer);\n\t\t\tlet data: T = new this.typeConstructor(newArray.data.buffer, newArray.bufferByteOffset, this.dataLength * this.maxChunkSize);\n\t\t\tthis.fullDataBlocks[pointerIndex] = data;\n\t\t}\n\n\t\tlet dataBlock = this.fullDataBlocks[pointerIndex];\n\t\tlet blockIndex = newIndex % this.maxChunkSize;\n\t\tlet startIndex = this.dataLength * blockIndex;\n\t\tif(isSingleNumber) {\n\t\t\tdataBlock[startIndex] = values;\n\t\t} else {\n\t\t\tdataBlock.set(values, startIndex);\n\t\t}\n\n\t\treturn newIndex;\n\t}\n\n\tdeleteIndex(index: number) {\n\t\tthis.recycledIndexes.push(index);\n\t}\n\n\tclear() {\n\t\tthis.totalLength = 0;\n\t\tthis.recycledIndexes = [];\n\t}\n\n\t*[Symbol.iterator]() {\n\t\tconst recycledValues: { [key: number]: true } = {};\n\t\tfor(let value of this.recycledIndexes) {\n\t\t\trecycledValues[value] = true;\n\t\t}\n\n\t\tlet dataBlock = this.getFullDataBlock(0);\n\t\tlet dataBlockIndex = 0;\n\t\tfor(let i = 0; i < this.totalLength; i++) {\n\t\t\tif(!recycledValues[i]) {\n\t\t\t\tlet newDataBlockIndex = Math.floor(i / this.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 % this.maxChunkSize);\n\t\t\t}\n\t\t}\n\t}\n\n\tprivate getFullDataBlock(index: number) {\n\t\tlet pointerIndex = Math.floor(index / this.maxChunkSize);\n\t\tlet cachedDataBlock = this.fullDataBlocks[pointerIndex];\n\t\tif(cachedDataBlock) {\n\t\t\treturn cachedDataBlock;\n\t\t}\n\t\tif(pointerIndex >= this.pointers.length) {\n\t\t\tlet newArray = this.memory.allocUI32(this.maxChunkSize * this.dataLength * this.byteMultipler);\n\t\t\tthis.pointers.push(newArray.pointer);\n\t\t}\n\n\t\tlet array = new AllocatedMemory(this.memory, getPointer(this.pointers[pointerIndex]));\n\t\tlet data: T = new this.typeConstructor(array.data.buffer, array.bufferByteOffset, this.dataLength * this.maxChunkSize);\n\t\tthis.fullDataBlocks[pointerIndex] = data;\n\t\treturn data;\n\t}\n\n\tprivate getDataBlock(rawData: T, index: number): T {\n\t\tconst start = index * this.dataLength;\n\t\treturn rawData.subarray(start, start + this.dataLength) as T;\n\t}\n\n\tfree() {\n\t\tfor(let pointer of this.pointers) {\n\t\t\tlet memory = new AllocatedMemory(this.memory, getPointer(pointer));\n\t\t\tmemory.free();\n\t\t}\n\t\tthis.pointers = [];\n\t\tthis.recycledIndexes = [];\n\t\tthis.totalLength = 0;\n\t\tthis.fullDataBlocks = {};\n\t}\n}\n\ninterface LocalPoolConfig<T extends Uint32Array | Int32Array | Float32Array | Float64Array> {\n\tmaxChunkSize?: number\n\ttype: TypedArrayConstructor<T>\n\tdataLength?: number\n}\nexport type { LocalPoolConfig };"],"mappings":"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"}
1
+ {"version":3,"file":"local-pool.js","names":[],"sources":["../src/local-pool.ts"],"sourcesContent":["import AllocatedMemory from './allocated-memory';\nimport type MemoryHeap from './memory-heap';\nimport { getPointer } from './utils/pointer';\nimport { getArrayTypeCode, getByteMultipler, type NumericArray, type NumericArrayIO, type TypedArrayConstructor } from './utils/array-type';\n\n// Array with stable indexes and maximum contiguous memory sizes (necessary to fit large data sets into max 1MB buffers)\n// Can only do insert/deletions from a single thread - useful if other threads are known to be read-only\nexport default class LocalPool<T extends NumericArray = Uint32Array> implements Iterable<T> {\n\tprivate memory: MemoryHeap;\n\tprivate pointers: Array<number> = [];\n\tprivate recycledIndexes: Array<number> = [];\n\tprivate totalLength = 0;\n\treadonly maxChunkSize: number = 100;\n\treadonly dataLength: number = 1;\n\treadonly byteMultipler: number = 1;\n\tprivate typeConstructor: TypedArrayConstructor<T>;\n\tprivate fullDataBlocks: { [key: number]: T } = {};\n\n\tget length(): number {\n\t\treturn this.totalLength - this.recycledIndexes.length;\n\t}\n\tget bufferLength(): number {\n\t\treturn this.maxChunkSize * this.pointers.length;\n\t}\n\n\tconstructor(memory: MemoryHeap, config: LocalPoolConfig<T>) {\n\t\tthis.memory = memory;\n\n\t\tconst dataLength = config?.dataLength ?? 1;\n\t\tconst maxLength = config?.maxChunkSize ?? 100;\n\t\tthis.dataLength = dataLength;\n\t\tthis.maxChunkSize = maxLength;\n\n\t\tthis.typeConstructor = config.type;\n\t\tthis.byteMultipler = getByteMultipler(getArrayTypeCode(config.type));\n\n\t\tlet firstArray = memory.allocUI32(maxLength * dataLength * this.byteMultipler);\n\t\tthis.pointers.push(firstArray.pointer);\n\t\tlet data: T = new this.typeConstructor(firstArray.data.buffer, firstArray.bufferByteOffset, this.dataLength * this.maxChunkSize);\n\t\tthis.fullDataBlocks[0] = data;\n\t}\n\n\tat(index: number): T {\n\t\tlet dataBlock = this.fullDataBlocks[Math.floor(index / this.maxChunkSize)];\n\t\treturn this.getDataBlock(dataBlock, index % this.maxChunkSize);\n\t}\n\tget(index: number, dataIndex = 0): T[number] {\n\t\tconst dataLength = this.dataLength;\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.fullDataBlocks[Math.floor(index / this.maxChunkSize)] as NumericArrayIO;\n\t\treturn dataBlock[(index % this.maxChunkSize) * dataLength + dataIndex];\n\t}\n\n\tpush(values: T[number] | Array<T[number]>): number {\n\t\tconst isSingleValue = typeof values !== 'object';\n\t\tif(!isSingleValue && values.length > this.dataLength) {\n\t\t\tthrow new Error(`Can't insert ${values.length} array into shared list of ${this.dataLength} dataLength`);\n\t\t}\n\n\t\tlet newIndex = this.recycledIndexes.pop();\n\t\tif(newIndex === undefined) {\n\t\t\tnewIndex = this.totalLength++;\n\t\t}\n\n\n\t\tlet pointerIndex = Math.floor(newIndex / this.maxChunkSize);\n\t\tif(pointerIndex >= this.pointers.length) {\n\t\t\tlet newArray = this.memory.allocUI32(this.maxChunkSize * this.dataLength * this.byteMultipler);\n\t\t\tthis.pointers.push(newArray.pointer);\n\t\t\tlet data: T = new this.typeConstructor(newArray.data.buffer, newArray.bufferByteOffset, this.dataLength * this.maxChunkSize);\n\t\t\tthis.fullDataBlocks[pointerIndex] = data;\n\t\t}\n\n\t\tlet dataBlock = this.fullDataBlocks[pointerIndex] as NumericArrayIO;\n\t\tlet blockIndex = newIndex % this.maxChunkSize;\n\t\tlet startIndex = this.dataLength * blockIndex;\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\tdeleteIndex(index: number) {\n\t\tthis.recycledIndexes.push(index);\n\t}\n\n\tclear() {\n\t\tthis.totalLength = 0;\n\t\tthis.recycledIndexes = [];\n\t}\n\n\t*[Symbol.iterator]() {\n\t\tconst recycledValues: { [key: number]: true } = {};\n\t\tfor(let value of this.recycledIndexes) {\n\t\t\trecycledValues[value] = true;\n\t\t}\n\n\t\tlet dataBlock = this.getFullDataBlock(0);\n\t\tlet dataBlockIndex = 0;\n\t\tfor(let i = 0; i < this.totalLength; i++) {\n\t\t\tif(!recycledValues[i]) {\n\t\t\t\tlet newDataBlockIndex = Math.floor(i / this.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 % this.maxChunkSize);\n\t\t\t}\n\t\t}\n\t}\n\n\tprivate getFullDataBlock(index: number) {\n\t\tlet pointerIndex = Math.floor(index / this.maxChunkSize);\n\t\tlet cachedDataBlock = this.fullDataBlocks[pointerIndex];\n\t\tif(cachedDataBlock) {\n\t\t\treturn cachedDataBlock;\n\t\t}\n\t\tif(pointerIndex >= this.pointers.length) {\n\t\t\tlet newArray = this.memory.allocUI32(this.maxChunkSize * this.dataLength * this.byteMultipler);\n\t\t\tthis.pointers.push(newArray.pointer);\n\t\t}\n\n\t\tlet array = new AllocatedMemory(this.memory, getPointer(this.pointers[pointerIndex]));\n\t\tlet data: T = new this.typeConstructor(array.data.buffer, array.bufferByteOffset, this.dataLength * this.maxChunkSize);\n\t\tthis.fullDataBlocks[pointerIndex] = data;\n\t\treturn data;\n\t}\n\n\tprivate getDataBlock(rawData: T, index: number): T {\n\t\tconst start = index * this.dataLength;\n\t\treturn rawData.subarray(start, start + this.dataLength) as T;\n\t}\n\n\tfree() {\n\t\tfor(let pointer of this.pointers) {\n\t\t\tlet memory = new AllocatedMemory(this.memory, getPointer(pointer));\n\t\t\tmemory.free();\n\t\t}\n\t\tthis.pointers = [];\n\t\tthis.recycledIndexes = [];\n\t\tthis.totalLength = 0;\n\t\tthis.fullDataBlocks = {};\n\t}\n}\n\ninterface LocalPoolConfig<T extends NumericArray> {\n\tmaxChunkSize?: number\n\ttype: TypedArrayConstructor<T>\n\tdataLength?: number\n}\nexport type { LocalPoolConfig };"],"mappings":"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"}
@@ -1,8 +1,13 @@
1
1
  function e(e, t = 0) {
2
- for (; Atomics.compareExchange(e, t, 0, 1) !== 0;) "WorkerGlobalScope" in self && Atomics.wait(e, t, 1);
2
+ let n = Atomics.compareExchange(e, t, 0, 1);
3
+ if (n !== 0) {
4
+ n !== 2 && (n = Atomics.exchange(e, t, 2));
5
+ let r = typeof self < "u" && "WorkerGlobalScope" in self;
6
+ for (; n !== 0;) r && Atomics.wait(e, t, 2), n = Atomics.exchange(e, t, 2);
7
+ }
3
8
  }
4
9
  function t(e, t = 0) {
5
- Atomics.compareExchange(e, t, 1, 0) !== 1 && console.warn("We are unlocking when it was not locked!"), Atomics.notify(e, t);
10
+ Atomics.exchange(e, t, 0) === 2 && Atomics.notify(e, t, 1);
6
11
  }
7
12
  var n = 1;
8
13
  //#endregion
@@ -1 +1 @@
1
- {"version":3,"file":"simple-lock.js","names":[],"sources":["../../src/lock/simple-lock.ts"],"sourcesContent":["const UNLOCKED = 0;\nconst LOCKED = 1;\nexport function lock(data: Int32Array, index: number = 0) {\n\t// Wait over and over again until we are one who set this from UNLOCKED to LOCKED\n\twhile(Atomics.compareExchange(data, index, UNLOCKED, LOCKED) !== UNLOCKED) {\n\t\tif('WorkerGlobalScope' in self) {\n\t\t\tAtomics.wait(data, index, LOCKED);\n\t\t} else {\n\t\t\t// TODO: Spin-locks suck....\n\t\t}\n\t}\n}\nexport function unlock(data: Int32Array, index: number = 0) {\n\tif(Atomics.compareExchange(data, index, LOCKED, UNLOCKED) !== LOCKED) {\n\t\tconsole.warn('We are unlocking when it was not locked!');\n\t}\n\n\tAtomics.notify(data, index);\n}\n\nexport const SIMPLE_LOCK_ALLOCATE_COUNT = 1;"],"mappings":"AAEA,SAAgB,EAAK,GAAkB,IAAgB,GAAG;CAEzD,OAAM,QAAQ,gBAAgB,GAAM,GAAO,GAAU,CAAM,MAAM,IAChE,AAAG,uBAAuB,QACzB,QAAQ,KAAK,GAAM,GAAO,CAAM;AAKnC;AACA,SAAgB,EAAO,GAAkB,IAAgB,GAAG;CAK3D,AAJG,QAAQ,gBAAgB,GAAM,GAAO,GAAQ,CAAQ,MAAM,KAC7D,QAAQ,KAAK,0CAA0C,GAGxD,QAAQ,OAAO,GAAM,CAAK;AAC3B;AAEA,IAAa,IAA6B"}
1
+ {"version":3,"file":"simple-lock.js","names":[],"sources":["../../src/lock/simple-lock.ts"],"sourcesContent":["// Three-state futex mutex (Drepper / Lars T Hansen): 0 = unlocked, 1 = locked with no waiters, 2 = locked and contended.\n// Tracking contention lets unlock skip Atomics.notify entirely on the common uncontended path (notify is expensive even\n// with zero waiters), while still waking a waiter whenever one parked itself by moving the state to CONTENDED.\nconst UNLOCKED = 0;\nconst LOCKED = 1;\nconst CONTENDED = 2;\n\nexport function lock(data: Int32Array, index: number = 0) {\n\tlet c = Atomics.compareExchange(data, index, UNLOCKED, LOCKED);\n\tif(c !== UNLOCKED) {\n\t\t// Mark the lock contended so the current holder knows to notify us on release\n\t\tif(c !== CONTENDED) {\n\t\t\tc = Atomics.exchange(data, index, CONTENDED);\n\t\t}\n\t\tlet isWorker = typeof self !== 'undefined' && 'WorkerGlobalScope' in self;\n\t\twhile(c !== UNLOCKED) {\n\t\t\tif(isWorker) {\n\t\t\t\tAtomics.wait(data, index, CONTENDED);\n\t\t\t}\n\t\t\t// On the main thread Atomics.wait throws, so this loop degrades to a spin re-acquire\n\t\t\tc = Atomics.exchange(data, index, CONTENDED);\n\t\t}\n\t}\n}\nexport function unlock(data: Int32Array, index: number = 0) {\n\t// If the lock was contended a waiter may be parked, so publish the release and wake exactly one of them\n\tif(Atomics.exchange(data, index, UNLOCKED) === CONTENDED) {\n\t\tAtomics.notify(data, index, 1);\n\t}\n}\n\nexport const SIMPLE_LOCK_ALLOCATE_COUNT = 1;\n"],"mappings":"AAOA,SAAgB,EAAK,GAAkB,IAAgB,GAAG;CACzD,IAAI,IAAI,QAAQ,gBAAgB,GAAM,GAAO,GAAU,CAAM;CAC7D,IAAG,MAAM,GAAU;EAElB,AAAG,MAAM,MACR,IAAI,QAAQ,SAAS,GAAM,GAAO,CAAS;EAE5C,IAAI,IAAW,OAAO,OAAS,OAAe,uBAAuB;EACrE,OAAM,MAAM,IAKX,AAJG,KACF,QAAQ,KAAK,GAAM,GAAO,CAAS,GAGpC,IAAI,QAAQ,SAAS,GAAM,GAAO,CAAS;CAE7C;AACD;AACA,SAAgB,EAAO,GAAkB,IAAgB,GAAG;CAE3D,AAAG,QAAQ,SAAS,GAAM,GAAO,CAAQ,MAAM,KAC9C,QAAQ,OAAO,GAAM,GAAO,CAAC;AAE/B;AAEA,IAAa,IAA6B"}
@@ -1,13 +1,16 @@
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
7
  private firstBlock;
8
8
  private uint16Array;
9
9
  private cachedType;
10
10
  private cachedDataLength;
11
+ private cachedByteMultipler;
12
+ private cachedIsBigInt;
13
+ private cachedIsFloat;
11
14
  private bufferViews;
12
15
  onDelete?: (data: T) => void;
13
16
  get length(): number;
@@ -17,10 +20,10 @@ export default class SharedList<T extends Uint32Array | Int32Array | Float32Arra
17
20
  get dataLength(): number;
18
21
  private set dataLength(value);
19
22
  constructor(memory: MemoryHeap, config?: SharedListConfig<T> | SharedListMemory);
20
- insert(values: number | Array<number>): void;
23
+ insert(values: T[number] | Array<T[number]>): void;
21
24
  deleteMatch(callback: (values: T, index: number) => boolean): boolean;
22
25
  deleteIndex(deleteIndex: number): boolean;
23
- deleteValue(deleteValues: number | Array<number>): boolean;
26
+ deleteValue(deleteValues: T[number] | Array<T[number]>): boolean;
24
27
  compact(): void;
25
28
  clear(): void;
26
29
  [Symbol.iterator](): Generator<{
@@ -34,7 +37,7 @@ export default class SharedList<T extends Uint32Array | Int32Array | Float32Arra
34
37
  private getDataBlock;
35
38
  free(): void;
36
39
  }
37
- interface SharedListConfig<T extends Uint32Array | Int32Array | Float32Array> {
40
+ interface SharedListConfig<T extends NumericArray> {
38
41
  initWithBlock?: SharedAllocatedMemory;
39
42
  type?: TypedArrayConstructor<T>;
40
43
  dataLength?: number;
@@ -42,7 +45,7 @@ interface SharedListConfig<T extends Uint32Array | Int32Array | Float32Array> {
42
45
  interface SharedListMemory {
43
46
  firstBlock: SharedAllocatedMemory;
44
47
  }
45
- interface SharedListIterable<T extends Uint32Array | Int32Array | Float32Array> {
48
+ interface SharedListIterable<T extends NumericArray> {
46
49
  data: T;
47
50
  index: number;
48
51
  deleteCurrent: () => boolean;
@@ -1,26 +1,30 @@
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 = 4095, y = class {
6
+ static ALLOCATE_COUNT = u;
6
7
  memory;
7
8
  firstBlock;
8
9
  uint16Array;
9
10
  cachedType;
10
11
  cachedDataLength;
12
+ cachedByteMultipler = 1;
13
+ cachedIsBigInt = !1;
14
+ cachedIsFloat = !1;
11
15
  bufferViews = [];
12
16
  onDelete;
13
17
  get length() {
14
- return Atomics.load(this.firstBlock.data, u);
18
+ return Atomics.load(this.firstBlock.data, h);
15
19
  }
16
20
  get usedMemory() {
17
- let t = this.firstBlock.usedMemory, { bufferPosition: n, bufferByteOffset: r } = e(this.firstBlock.data, c);
21
+ let t = this.firstBlock.usedMemory, { bufferPosition: n, bufferByteOffset: r } = e(this.firstBlock.data, p);
18
22
  for (; r;) {
19
23
  let a = new i(this.memory, {
20
24
  bufferPosition: n,
21
25
  bufferByteOffset: r
22
26
  });
23
- t += a.usedMemory, {bufferPosition: n, bufferByteOffset: r} = e(a.data, d);
27
+ t += a.usedMemory, {bufferPosition: n, bufferByteOffset: r} = e(a.data, g);
24
28
  }
25
29
  return t;
26
30
  }
@@ -37,34 +41,28 @@ var a = 4, o = 2, s = o * Uint32Array.BYTES_PER_ELEMENT, c = 0, l = 1, u = 2, d
37
41
  Atomics.store(this.uint16Array, 1, e);
38
42
  }
39
43
  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));
44
+ this.memory = e, t && "firstBlock" in t ? (this.firstBlock = new i(e, t.firstBlock), this.uint16Array = new Uint16Array(this.firstBlock.data.buffer, this.firstBlock.bufferByteOffset + 3 * Uint32Array.BYTES_PER_ELEMENT, 2)) : (this.firstBlock = t && t.initWithBlock ? new i(e, t.initWithBlock) : e.allocUI32(u), this.uint16Array = new Uint16Array(this.firstBlock.data.buffer, this.firstBlock.bufferByteOffset + 3 * Uint32Array.BYTES_PER_ELEMENT, 2), r(this.firstBlock.data, m, this.firstBlock.pointer), this.type = a(t?.type ?? Uint32Array), this.dataLength = t?.dataLength ?? 1), this.cachedType = Atomics.load(this.uint16Array, 0), this.cachedDataLength = Math.max(1, Atomics.load(this.uint16Array, 1)), this.cachedByteMultipler = o(this.cachedType), this.cachedIsBigInt = s(this.cachedType), this.cachedIsFloat = c(this.cachedType);
47
45
  }
48
46
  insert(e) {
49
- typeof e == "number" && (e = [e]);
47
+ Array.isArray(e) || (e = [e]);
50
48
  let r = this.cachedDataLength;
51
49
  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];
50
+ let i = this.memory.allocUI32(d + r * this.cachedByteMultipler), a = this.getDataBlock(i.data.buffer, i.data.byteOffset), o = i.pointer, s = a;
51
+ for (let t = 0; t < e.length; t++) this.cachedIsFloat ? s[t] = e[t] : (this.cachedIsBigInt, Atomics.store(a, t, e[t]));
54
52
  for (;;) {
55
- let e = t(this.firstBlock.data, l), r = this.getBufferView(e & p);
53
+ let e = t(this.firstBlock.data, m), r = this.getBufferView(e & v);
56
54
  if (!r) continue;
57
- let i = (e >>> 12 >>> 2) + d, a = Atomics.load(r, i);
58
- if (e === t(this.firstBlock.data, l)) {
55
+ let i = (e >>> 12 >>> 2) + g, a = Atomics.load(r, i);
56
+ if (e === t(this.firstBlock.data, m)) {
59
57
  if (a === 0) {
60
- if (Atomics.compareExchange(r, i, 0, s) === 0) {
61
- n(this.firstBlock.data, l, s, e);
58
+ if (Atomics.compareExchange(r, i, 0, o) === 0) {
59
+ n(this.firstBlock.data, m, o, e);
62
60
  break;
63
61
  }
64
- } else n(this.firstBlock.data, l, a, e);
62
+ } else n(this.firstBlock.data, m, a, e);
65
63
  }
66
64
  }
67
- Atomics.add(this.firstBlock.data, u, 1);
65
+ Atomics.add(this.firstBlock.data, h, 1);
68
66
  }
69
67
  deleteMatch(e) {
70
68
  for (let { data: t, index: n, deleteCurrent: r } of this) if (e(t, n) && r()) return !0;
@@ -76,51 +74,51 @@ var a = 4, o = 2, s = o * Uint32Array.BYTES_PER_ELEMENT, c = 0, l = 1, u = 2, d
76
74
  return !1;
77
75
  }
78
76
  deleteValue(e) {
79
- return typeof e == "number" ? this.deleteMatch((t) => t[0] === e) : this.deleteMatch((t) => {
77
+ return Array.isArray(e) ? this.deleteMatch((t) => {
80
78
  if (t.length !== e.length) return !1;
81
79
  for (let n = 0; n < t.length; n++) if (t[n] !== e[n]) return !1;
82
80
  return !0;
83
- });
81
+ }) : this.deleteMatch((t) => t[0] === e);
84
82
  }
85
83
  compact() {
86
- let e = this.firstBlock.pointer, n = this.firstBlock.data, i = d, a = t(n, i);
84
+ let e = this.firstBlock.pointer, n = this.firstBlock.data, i = g, a = t(n, i);
87
85
  for (; a;) {
88
- let t = a & p, o = this.memory.buffers[t];
86
+ let t = a & v, o = this.memory.buffers[t];
89
87
  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;
88
+ let s = this.getBufferView(t), c = a >>> 12, l = c >>> 2, u = Atomics.load(s, l + g);
89
+ Atomics.load(s, l + _) ? (r(n, i, u), o.free(c)) : (e = a, n = s, i = l + g), a = u;
92
90
  }
93
- r(this.firstBlock.data, l, e);
91
+ r(this.firstBlock.data, m, e);
94
92
  }
95
93
  clear() {
96
94
  let e, i = !1;
97
95
  for (; !i;) {
98
- if (e = t(this.firstBlock.data, c), !e) return;
99
- i = n(this.firstBlock.data, c, 0, e);
96
+ if (e = t(this.firstBlock.data, p), !e) return;
97
+ i = n(this.firstBlock.data, p, 0, e);
100
98
  }
101
99
  if (!e) return;
102
- r(this.firstBlock.data, l, this.firstBlock.pointer);
100
+ r(this.firstBlock.data, m, this.firstBlock.pointer);
103
101
  let a = 0, o = e;
104
102
  for (; o;) {
105
- let e = o & p, t = this.memory.buffers[e];
103
+ let e = o & v, t = this.memory.buffers[e];
106
104
  if (!t) break;
107
105
  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);
106
+ o = Atomics.load(n, i + g), Atomics.load(n, i + _) || (a++, this.onDelete && this.onDelete(this.getDataBlock(n.buffer, r))), t.free(r);
109
107
  }
110
- Atomics.sub(this.firstBlock.data, u, a);
108
+ Atomics.sub(this.firstBlock.data, h, a);
111
109
  }
112
110
  *[Symbol.iterator]() {
113
- let e = 0, n = t(this.firstBlock.data, c);
111
+ let e = 0, n = t(this.firstBlock.data, p);
114
112
  for (; n;) {
115
- let t = n & p, r = this.getBufferView(t);
113
+ let t = n & v, r = this.getBufferView(t);
116
114
  if (!r) return;
117
115
  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;
116
+ if (n = Atomics.load(r, a + g), Atomics.load(r, a + _)) continue;
117
+ let o = r.buffer, s = a + _;
120
118
  yield {
121
119
  data: this.getDataBlock(o, i),
122
120
  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)
121
+ deleteCurrent: () => Atomics.compareExchange(r, s, 0, 1) === 0 && (Atomics.sub(this.firstBlock.data, h, 1), this.onDelete && this.onDelete(this.getDataBlock(o, i)), !0)
124
122
  }, e++;
125
123
  }
126
124
  }
@@ -137,27 +135,22 @@ var a = 4, o = 2, s = o * Uint32Array.BYTES_PER_ELEMENT, c = 0, l = 1, u = 2, d
137
135
  if (n) return t = new Uint32Array(n.buf), this.bufferViews[e] = t, t;
138
136
  }
139
137
  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
- }
138
+ let n = t + f;
139
+ return l(this.cachedType, e, n, this.cachedDataLength);
147
140
  }
148
141
  free() {
149
- let { bufferPosition: t, bufferByteOffset: n } = e(this.firstBlock.data, c);
142
+ let { bufferPosition: t, bufferByteOffset: n } = e(this.firstBlock.data, p);
150
143
  for (; n;) {
151
144
  let r = new i(this.memory, {
152
145
  bufferPosition: t,
153
146
  bufferByteOffset: n
154
147
  });
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();
148
+ ({bufferPosition: t, bufferByteOffset: n} = e(r.data, g)), this.onDelete && !Atomics.load(r.data, _) && this.onDelete(this.getDataBlock(r.data.buffer, r.data.byteOffset)), r.free();
156
149
  }
157
150
  this.firstBlock.free();
158
151
  }
159
152
  };
160
153
  //#endregion
161
- export { m as default };
154
+ export { y as default };
162
155
 
163
156
  //# 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, POSITION_BIT_COUNT, replaceRawPointer, storeRawPointer } from './utils/pointer';\nimport { getArrayTypeCode, getByteMultipler, isBigIntType, isFloatType, makeArrayView, type NumericArray, type NumericArrayIO, type TypedArrayConstructor } from './utils/array-type';\n\n// Lock-free singly-linked list. firstBlock doubles as an immortal sentinel node whose next pointer (index 0) is the head.\n// insert appends with a Michael-Scott CAS enqueue; delete only tombstones a node (never unlinks/frees). Memory is\n// reclaimed exclusively by the quiescent compact()/clear()/free() passes. That deferral is what makes the CAS loops\n// safe without hazard pointers: a live node's address is never handed back to the allocator, so no append can ever\n// observe a recycled address (ABA) while another thread is mid-traversal.\nconst FIRST_BLOCK_RECORD_KEEPING_COUNT = 4;\nconst DATA_BLOCK_RECORD_KEEPING_COUNT = 2;\nconst DATA_BLOCK_BYTE_OFFSET = DATA_BLOCK_RECORD_KEEPING_COUNT * Uint32Array.BYTES_PER_ELEMENT;\nconst HEAD_INDEX = 0;\nconst TAIL_INDEX = 1;\nconst LENGTH_INDEX = 2;\nconst NODE_NEXT_INDEX = 0;\nconst NODE_DELETED_INDEX = 1;\n// A pointer packs the buffer position in its low bits and the byte offset in the high bits (see utils/pointer)\nconst POSITION_MASK = (1 << POSITION_BIT_COUNT) - 1;\nexport default class SharedList<T extends NumericArray = Uint32Array> implements Iterable<SharedListIterable<T>> {\n\tstatic readonly ALLOCATE_COUNT = FIRST_BLOCK_RECORD_KEEPING_COUNT;\n\n\tprivate memory: MemoryHeap;\n\t/* First block (also the sentinel node - index 0 is its next pointer = list head)\n\t\t32 index 0\n\t\tuint16 0 - head buffer position | sentinel.next\n\t\tuint16 1 - head buffer index\n\t\t32 index 1\n\t\tuint16 2 - tail hint buffer position\n\t\tuint16 3 - tail hint buffer index\n\t\t32 index 2\n\t\tuint32 4 - length\n\t\t32 index 3\n\t\tuint16 6 - type\n\t\tuint16 7 - data length (defaults to 1 number per data)\n\t*/\n\t/* Other blocks\n\t\t32 index 0 - next buffer pointer\n\t\t32 index 1 - deleted flag (0 = live, 1 = tombstoned)\n\t\t32 index 2 => data\n\t*/\n\tprivate firstBlock: AllocatedMemory;\n\tprivate uint16Array: Uint16Array;\n\t// type and dataLength are written once at init and never change, so cache them instead of atomic-loading per node\n\tprivate cachedType: number;\n\tprivate cachedDataLength: number;\n\t// u32 units per element: 1 for 32-bit types, 2 for 64-bit (float64/int64/uint64). Node data blocks scale by this.\n\tprivate cachedByteMultipler = 1;\n\t// Whether the value type is a BigInt view (int64/uint64), so insert stores bigint via Atomics; cached to keep the\n\t// per-insert branch off an instanceof check\n\tprivate cachedIsBigInt = false;\n\t// Float views can't be published with Atomics, so insert falls back to a plain store for them\n\tprivate cachedIsFloat = false;\n\t// One full-buffer Uint32Array per buffer position, so traversal indexes shared memory directly instead of\n\t// allocating a fresh view (and a getPointer object) for every node it touches\n\tprivate bufferViews: Array<Uint32Array> = [];\n\tonDelete?: (data: T) => void;\n\n\tget length(): number {\n\t\treturn Atomics.load(this.firstBlock.data, LENGTH_INDEX);\n\t}\n\n\t// Own internal memory plus every allocated node still linked in the chain (tombstoned nodes included, since they stay\n\t// allocated until a compact()/clear() reclaims them).\n\tget usedMemory(): number {\n\t\tlet total = this.firstBlock.usedMemory;\n\t\tlet { bufferPosition, bufferByteOffset } = loadPointer(this.firstBlock.data, HEAD_INDEX);\n\t\twhile(bufferByteOffset) {\n\t\t\tlet node = new AllocatedMemory(this.memory, { bufferPosition, bufferByteOffset });\n\t\t\ttotal += node.usedMemory;\n\t\t\t({ bufferPosition, bufferByteOffset } = loadPointer(node.data, NODE_NEXT_INDEX));\n\t\t}\n\t\treturn total;\n\t}\n\n\tget type(): number {\n\t\treturn this.cachedType;\n\t}\n\tprivate set type(value: number) {\n\t\tAtomics.store(this.uint16Array, 0, value);\n\t}\n\tget dataLength(): number {\n\t\treturn this.cachedDataLength;\n\t}\n\tprivate set dataLength(value: number) {\n\t\tAtomics.store(this.uint16Array, 1, value);\n\t}\n\n\tconstructor(memory: MemoryHeap, config?: SharedListConfig<T> | SharedListMemory) {\n\t\tthis.memory = memory;\n\n\t\tif(config && 'firstBlock' in config) {\n\t\t\t// TODO: How to handle referencing memory we don't have access to yet because buffer not synced from worker?\n\t\t\tthis.firstBlock = new AllocatedMemory(memory, config.firstBlock);\n\t\t\tthis.uint16Array = new Uint16Array(this.firstBlock.data.buffer, this.firstBlock.bufferByteOffset + (LENGTH_INDEX + 1) * Uint32Array.BYTES_PER_ELEMENT, 2);\n\t\t} else {\n\t\t\tif(config && config.initWithBlock) {\n\t\t\t\tthis.firstBlock = new AllocatedMemory(memory, config.initWithBlock);\n\t\t\t} else {\n\t\t\t\tthis.firstBlock = memory.allocUI32(FIRST_BLOCK_RECORD_KEEPING_COUNT);\n\t\t\t}\n\t\t\tthis.uint16Array = new Uint16Array(this.firstBlock.data.buffer, this.firstBlock.bufferByteOffset + (LENGTH_INDEX + 1) * Uint32Array.BYTES_PER_ELEMENT, 2);\n\n\t\t\t// Empty list: the tail hint points at the sentinel (firstBlock) so insert never special-cases the first node\n\t\t\tstoreRawPointer(this.firstBlock.data, TAIL_INDEX, this.firstBlock.pointer);\n\n\t\t\tthis.type = getArrayTypeCode(config?.type ?? Uint32Array);\n\t\t\tthis.dataLength = config?.dataLength ?? 1;\n\t\t}\n\n\t\tthis.cachedType = Atomics.load(this.uint16Array, 0);\n\t\t// dataLength defaults to at least one even when initialized from raw memory that was never explicitly set\n\t\tthis.cachedDataLength = Math.max(1, Atomics.load(this.uint16Array, 1));\n\t\tthis.cachedByteMultipler = getByteMultipler(this.cachedType);\n\t\tthis.cachedIsBigInt = isBigIntType(this.cachedType);\n\t\tthis.cachedIsFloat = isFloatType(this.cachedType);\n\t}\n\n\tinsert(values: T[number] | Array<T[number]>) {\n\t\tif(!Array.isArray(values)) {\n\t\t\tvalues = [values];\n\t\t}\n\n\t\tlet dataLength = this.cachedDataLength;\n\t\tif(values.length > dataLength) {\n\t\t\tthrow new Error(`Can't insert ${values.length} array into shared list of ${dataLength} dataLength`);\n\t\t}\n\t\tlet newBlock = this.memory.allocUI32(DATA_BLOCK_RECORD_KEEPING_COUNT + dataLength * this.cachedByteMultipler);\n\t\tlet newData = this.getDataBlock(newBlock.data.buffer, newBlock.data.byteOffset);\n\t\tlet newBlockPointer = newBlock.pointer;\n\n\t\tlet io = newData as NumericArrayIO;\n\t\tfor(let i = 0; i < values.length; i++) {\n\t\t\tif(this.cachedIsFloat) {\n\t\t\t\t// Float views don't support Atomics; the enqueue CAS below still provides the cross-thread publish\n\t\t\t\tio[i] = values[i];\n\t\t\t} else if(this.cachedIsBigInt) {\n\t\t\t\tAtomics.store(newData as BigInt64Array, i, values[i] as bigint);\n\t\t\t} else {\n\t\t\t\tAtomics.store(newData as Int32Array, i, values[i] as number);\n\t\t\t}\n\t\t}\n\n\t\t// Michael-Scott enqueue: link the node onto tail.next BEFORE swinging the tail hint, so a committed node is never\n\t\t// invisible to a concurrent traversal. The tail hint may lag by one node; enqueuers help advance it.\n\t\t// eslint-disable-next-line no-constant-condition\n\t\twhile(true) {\n\t\t\tlet tailPointer = loadRawPointer(this.firstBlock.data, TAIL_INDEX);\n\t\t\tlet view = this.getBufferView(tailPointer & POSITION_MASK);\n\t\t\t// Tail node lives in a buffer this thread hasn't synced yet - retry until it appears\n\t\t\tif(!view) {\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tlet tailNextIndex = ((tailPointer >>> POSITION_BIT_COUNT) >>> 2) + NODE_NEXT_INDEX;\n\t\t\tlet nextPointer = Atomics.load(view, tailNextIndex);\n\n\t\t\t// Re-check the hint hasn't moved out from under us before acting on what we read\n\t\t\tif(tailPointer !== loadRawPointer(this.firstBlock.data, TAIL_INDEX)) {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tif(nextPointer === 0) {\n\t\t\t\tif(Atomics.compareExchange(view, tailNextIndex, 0, newBlockPointer) === 0) {\n\t\t\t\t\t// Linked. Try to swing the hint forward - failure is harmless, the next enqueuer will help.\n\t\t\t\t\treplaceRawPointer(this.firstBlock.data, TAIL_INDEX, newBlockPointer, tailPointer);\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\t// Tail hint is lagging behind a linked node - help it catch up, then retry\n\t\t\t\treplaceRawPointer(this.firstBlock.data, TAIL_INDEX, nextPointer, tailPointer);\n\t\t\t}\n\t\t}\n\n\t\tAtomics.add(this.firstBlock.data, LENGTH_INDEX, 1);\n\t}\n\n\tdeleteMatch(callback: (values: T, index: number) => boolean): boolean {\n\t\tfor(let { data, index, deleteCurrent } of this) {\n\t\t\tif(callback(data, index)) {\n\t\t\t\tif(deleteCurrent()) {\n\t\t\t\t\treturn true;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\treturn false;\n\t}\n\tdeleteIndex(deleteIndex: number): boolean {\n\t\tif(deleteIndex >= this.length || deleteIndex < 0) {\n\t\t\treturn false;\n\t\t}\n\n\t\t// Compare on index only so the payload view is never materialized for an index-based delete\n\t\tfor(let item of this) {\n\t\t\tif(item.index === deleteIndex) {\n\t\t\t\tif(item.deleteCurrent()) {\n\t\t\t\t\treturn true;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\treturn false;\n\t}\n\tdeleteValue(deleteValues: T[number] | Array<T[number]>) {\n\t\tif(!Array.isArray(deleteValues)) {\n\t\t\treturn this.deleteMatch(values => values[0] === deleteValues);\n\t\t} else {\n\t\t\treturn this.deleteMatch(values => {\n\t\t\t\tif(values.length !== deleteValues.length) {\n\t\t\t\t\treturn false;\n\t\t\t\t} else {\n\t\t\t\t\tfor(let i = 0; i < values.length; i++) {\n\t\t\t\t\t\tif(values[i] !== deleteValues[i]) {\n\t\t\t\t\t\t\treturn false;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t\treturn true;\n\t\t\t\t}\n\t\t\t});\n\t\t}\n\t}\n\n\t// Reclaims tombstoned nodes: physically unlinks and frees them. Not safe to run concurrently with insert/delete.\n\tcompact() {\n\t\tlet prevPointer = this.firstBlock.pointer;\n\t\tlet prevView = this.firstBlock.data;\n\t\tlet prevNextIndex = NODE_NEXT_INDEX;\n\t\tlet nextPointer = loadRawPointer(prevView, prevNextIndex);\n\t\twhile(nextPointer) {\n\t\t\tlet position = nextPointer & POSITION_MASK;\n\t\t\tlet memPool = this.memory.buffers[position];\n\t\t\tif(!memPool) {\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tlet view = this.getBufferView(position)!;\n\t\t\tlet byteOffset = nextPointer >>> POSITION_BIT_COUNT;\n\t\t\tlet nodeIndex = byteOffset >>> 2;\n\t\t\tlet followingPointer = Atomics.load(view, nodeIndex + NODE_NEXT_INDEX);\n\n\t\t\tif(Atomics.load(view, nodeIndex + NODE_DELETED_INDEX)) {\n\t\t\t\t// Skip over the tombstone and free it. onDelete already fired when the node was logically deleted.\n\t\t\t\tstoreRawPointer(prevView, prevNextIndex, followingPointer);\n\t\t\t\tmemPool.free(byteOffset);\n\t\t\t} else {\n\t\t\t\tprevPointer = nextPointer;\n\t\t\t\tprevView = view;\n\t\t\t\tprevNextIndex = nodeIndex + NODE_NEXT_INDEX;\n\t\t\t}\n\n\t\t\tnextPointer = followingPointer;\n\t\t}\n\n\t\t// The last surviving node (or the sentinel when everything is gone) is the new tail\n\t\tstoreRawPointer(this.firstBlock.data, TAIL_INDEX, prevPointer);\n\t}\n\n\tclear() {\n\t\t// Detach the whole chain, then free it. Not safe to run concurrently with insert/delete.\n\t\tlet firstBlockPointer;\n\t\tlet updateWorked = false;\n\t\twhile(!updateWorked) {\n\t\t\tfirstBlockPointer = loadRawPointer(this.firstBlock.data, HEAD_INDEX);\n\t\t\t// Already cleared\n\t\t\tif(!firstBlockPointer) {\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tupdateWorked = replaceRawPointer(this.firstBlock.data, HEAD_INDEX, 0, firstBlockPointer);\n\t\t}\n\t\t// Shouldn't be possible to hit: making Typescript happy\n\t\tif(!firstBlockPointer) {\n\t\t\treturn;\n\t\t}\n\n\t\t// Reset the tail hint back to the sentinel\n\t\tstoreRawPointer(this.firstBlock.data, TAIL_INDEX, this.firstBlock.pointer);\n\n\t\tlet liveItems = 0;\n\t\tlet nextBlockPointer = firstBlockPointer;\n\t\twhile(nextBlockPointer) {\n\t\t\tlet position = nextBlockPointer & POSITION_MASK;\n\t\t\tlet memPool = this.memory.buffers[position];\n\t\t\t// Short circuit iterations if we can't access memory\n\t\t\tif(!memPool) {\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\tlet view = this.getBufferView(position)!;\n\t\t\tlet byteOffset = nextBlockPointer >>> POSITION_BIT_COUNT;\n\t\t\tlet nodeIndex = byteOffset >>> 2;\n\t\t\tnextBlockPointer = Atomics.load(view, nodeIndex + NODE_NEXT_INDEX);\n\n\t\t\tif(!Atomics.load(view, nodeIndex + NODE_DELETED_INDEX)) {\n\t\t\t\tliveItems++;\n\t\t\t\tif(this.onDelete) {\n\t\t\t\t\tthis.onDelete(this.getDataBlock(view.buffer, byteOffset));\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tmemPool.free(byteOffset);\n\t\t}\n\n\t\t// Only subtract the live nodes we detached so a concurrent insert's increment stays accurate\n\t\tAtomics.sub(this.firstBlock.data, LENGTH_INDEX, liveItems);\n\t}\n\n\t*[Symbol.iterator]() {\n\t\tlet currentIndex = 0;\n\t\tlet nextPointer = loadRawPointer(this.firstBlock.data, HEAD_INDEX);\n\t\twhile(nextPointer) {\n\t\t\tlet position = nextPointer & POSITION_MASK;\n\t\t\tlet view = this.getBufferView(position);\n\t\t\t// Short circuit iterations if we can't access memory\n\t\t\tif(!view) {\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tlet byteOffset = nextPointer >>> POSITION_BIT_COUNT;\n\t\t\tlet nodeIndex = byteOffset >>> 2;\n\t\t\tnextPointer = Atomics.load(view, nodeIndex + NODE_NEXT_INDEX);\n\n\t\t\t// Skip tombstoned nodes - they stay linked until a quiescent compact()/clear()\n\t\t\tif(Atomics.load(view, nodeIndex + NODE_DELETED_INDEX)) {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tlet buffer = view.buffer;\n\t\t\tlet deletedIndex = nodeIndex + NODE_DELETED_INDEX;\n\t\t\t// Plain object with an eager data view: V8 keeps this monomorphic and fast. A lazy `get data()` accessor is far\n\t\t\t// slower to allocate than the payload view it would save, so pay for the view up front.\n\t\t\tyield {\n\t\t\t\tdata: this.getDataBlock(buffer, byteOffset),\n\t\t\t\tindex: currentIndex,\n\t\t\t\t// Logical delete: tombstone the node and drop length. Returns false if another thread beat us to it.\n\t\t\t\tdeleteCurrent: (): boolean => {\n\t\t\t\t\tif(Atomics.compareExchange(view, deletedIndex, 0, 1) === 0) {\n\t\t\t\t\t\tAtomics.sub(this.firstBlock.data, LENGTH_INDEX, 1);\n\t\t\t\t\t\tif(this.onDelete) {\n\t\t\t\t\t\t\tthis.onDelete(this.getDataBlock(buffer, byteOffset));\n\t\t\t\t\t\t}\n\t\t\t\t\t\treturn true;\n\t\t\t\t\t}\n\n\t\t\t\t\treturn false;\n\t\t\t\t},\n\t\t\t};\n\n\t\t\tcurrentIndex++;\n\t\t}\n\t}\n\n\tforEach(callback: (data: T) => void) {\n\t\tfor(let value of this) {\n\t\t\tcallback(value.data);\n\t\t}\n\t}\n\n\tgetSharedMemory(): SharedListMemory {\n\t\treturn {\n\t\t\tfirstBlock: this.firstBlock.getSharedMemory(),\n\t\t};\n\t}\n\n\tprivate getBufferView(position: number): Uint32Array | undefined {\n\t\tlet view = this.bufferViews[position];\n\t\tif(view) {\n\t\t\treturn view;\n\t\t}\n\n\t\tlet buffer = this.memory.buffers[position];\n\t\tif(!buffer) {\n\t\t\treturn undefined;\n\t\t}\n\n\t\tview = new Uint32Array(buffer.buf);\n\t\tthis.bufferViews[position] = view;\n\t\treturn view;\n\t}\n\n\tprivate getDataBlock(buffer: ArrayBufferLike, nodeByteOffset: number): T {\n\t\tconst startIndex = nodeByteOffset + DATA_BLOCK_BYTE_OFFSET;\n\t\treturn makeArrayView(this.cachedType, buffer, startIndex, this.cachedDataLength) as T;\n\t}\n\n\tfree() {\n\t\tlet { bufferPosition: nextBlockPosition, bufferByteOffset: nextBlockByteOffset } = loadPointer(this.firstBlock.data, HEAD_INDEX);\n\t\twhile(nextBlockByteOffset) {\n\t\t\tlet allocatedMemory = new AllocatedMemory(this.memory, {\n\t\t\t\tbufferPosition: nextBlockPosition,\n\t\t\t\tbufferByteOffset: nextBlockByteOffset,\n\t\t\t});\n\n\t\t\t({ bufferPosition: nextBlockPosition, bufferByteOffset: nextBlockByteOffset } = loadPointer(allocatedMemory.data, NODE_NEXT_INDEX));\n\n\t\t\tif(this.onDelete && !Atomics.load(allocatedMemory.data, NODE_DELETED_INDEX)) {\n\t\t\t\tthis.onDelete(this.getDataBlock(allocatedMemory.data.buffer, allocatedMemory.data.byteOffset));\n\t\t\t}\n\n\t\t\tallocatedMemory.free();\n\t\t}\n\n\t\tthis.firstBlock.free();\n\t}\n}\n\ninterface SharedListConfig<T extends NumericArray> {\n\tinitWithBlock?: SharedAllocatedMemory\n\ttype?: TypedArrayConstructor<T>\n\tdataLength?: number\n}\ninterface SharedListMemory {\n\tfirstBlock: SharedAllocatedMemory\n}\n\ninterface SharedListIterable<T extends NumericArray> {\n\tdata: T\n\tindex: number\n\tdeleteCurrent: () => boolean\n}\n\nexport { type SharedListMemory };\n"],"mappings":"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"}