@daneren2005/shared-memory-objects 1.5.1 → 1.6.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/dist/cached-item-list.d.ts +1 -1
- package/dist/lock/read-write-lock.d.ts +1 -0
- package/dist/lock/read-write-lock.js +6 -3
- package/dist/lock/read-write-lock.js.map +1 -1
- package/dist/shared-list.d.ts +5 -1
- package/dist/shared-list.js +91 -47
- package/dist/shared-list.js.map +1 -1
- package/dist/spatial/shared-spatial-map.js +134 -134
- package/dist/utils/16-from-32-array.d.ts +1 -4
- package/dist/utils/16-from-32-array.js +2 -18
- package/dist/utils/16-from-32-float.d.ts +4 -0
- package/dist/utils/16-from-32-float.js +20 -0
- package/dist/utils/16-from-32-float.js.map +1 -0
- package/dist/utils/16-from-32-int.d.ts +4 -0
- package/dist/utils/16-from-32-int.js +18 -0
- package/dist/utils/16-from-32-int.js.map +1 -0
- package/dist/utils/16-from-32-uint.d.ts +4 -0
- package/dist/utils/16-from-32-uint.js +18 -0
- package/dist/utils/16-from-32-uint.js.map +1 -0
- package/dist/utils/16-from-64-array.d.ts +1 -2
- package/dist/utils/16-from-64-array.js +2 -17
- package/dist/utils/16-from-64-float.d.ts +4 -0
- package/dist/utils/16-from-64-float.js +30 -0
- package/dist/utils/16-from-64-float.js.map +1 -0
- package/dist/utils/16-from-64-int.d.ts +4 -0
- package/dist/utils/16-from-64-int.js +23 -0
- package/dist/utils/16-from-64-int.js.map +1 -0
- package/dist/utils/16-from-64-uint.d.ts +4 -0
- package/dist/utils/16-from-64-uint.js +23 -0
- package/dist/utils/16-from-64-uint.js.map +1 -0
- package/dist/utils/32-from-64-float.d.ts +4 -0
- package/dist/utils/32-from-64-float.js +20 -0
- package/dist/utils/32-from-64-float.js.map +1 -0
- package/dist/utils/32-from-64-int.d.ts +4 -0
- package/dist/utils/32-from-64-int.js +18 -0
- package/dist/utils/32-from-64-int.js.map +1 -0
- package/dist/utils/32-from-64-uint.d.ts +4 -0
- package/dist/utils/32-from-64-uint.js +18 -0
- package/dist/utils/32-from-64-uint.js.map +1 -0
- package/dist/utils/typedarray.js.map +1 -1
- package/package.json +9 -9
- package/dist/utils/16-from-32-array.js.map +0 -1
- package/dist/utils/16-from-64-array.js.map +0 -1
|
@@ -5,7 +5,7 @@ export default abstract class CachedItemList<T extends Item> implements Iterable
|
|
|
5
5
|
item: T;
|
|
6
6
|
deleteCurrent: () => void;
|
|
7
7
|
}> {
|
|
8
|
-
static readonly ALLOCATE_COUNT =
|
|
8
|
+
static readonly ALLOCATE_COUNT = 7;
|
|
9
9
|
protected heap: MemoryHeap;
|
|
10
10
|
protected list: SharedList;
|
|
11
11
|
protected cache: Map<number, T>;
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
export declare function readLock(data: Int32Array, index?: number): void;
|
|
2
2
|
export declare function writeLock(data: Int32Array, index?: number): void;
|
|
3
|
+
export declare function tryWriteLock(data: Int32Array, index?: number): boolean;
|
|
3
4
|
export declare function readUnlock(data: Int32Array, index?: number): void;
|
|
4
5
|
export declare function writeUnlock(data: Int32Array, index?: number): void;
|
|
5
6
|
export declare const READ_WRITE_LOCK_ALLOCATE_COUNT = 2;
|
|
@@ -21,13 +21,16 @@ function n(t, n = 0) {
|
|
|
21
21
|
Atomics.sub(t, n + 1, 1);
|
|
22
22
|
}
|
|
23
23
|
function r(e, t = 0) {
|
|
24
|
-
Atomics.
|
|
24
|
+
return Atomics.compareExchange(e, t, 0, -1) === 0;
|
|
25
25
|
}
|
|
26
26
|
function i(e, t = 0) {
|
|
27
|
+
Atomics.sub(e, t, 1) === 1 && Atomics.notify(e, t);
|
|
28
|
+
}
|
|
29
|
+
function a(e, t = 0) {
|
|
27
30
|
Atomics.compareExchange(e, t, -1, 0) !== -1 && console.warn("We are unlocking when it was not write locked!"), Atomics.notify(e, t), Atomics.notify(e, t + 1);
|
|
28
31
|
}
|
|
29
|
-
var
|
|
32
|
+
var o = 2;
|
|
30
33
|
//#endregion
|
|
31
|
-
export {
|
|
34
|
+
export { o as READ_WRITE_LOCK_ALLOCATE_COUNT, t as readLock, i as readUnlock, r as tryWriteLock, n as writeLock, a as writeUnlock };
|
|
32
35
|
|
|
33
36
|
//# sourceMappingURL=read-write-lock.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"read-write-lock.js","names":[],"sources":["../../src/lock/read-write-lock.ts"],"sourcesContent":["const WRITE_LOCKED = -1;\n\nfunction canWait(): boolean {\n\treturn typeof self !== 'undefined' && 'WorkerGlobalScope' in self;\n}\n\nexport function readLock(data: Int32Array, index: number = 0) {\n\t// Pending writers block new readers so a steady stream of queries cannot starve a resize.\n\twhile(true) {\n\t\tlet writers = data[index + 1];\n\t\tif(writers !== 0) {\n\t\t\tif(canWait()) {\n\t\t\t\tAtomics.wait(data, index + 1, writers);\n\t\t\t}\n\t\t\tcontinue;\n\t\t}\n\t\tlet readers = data[index];\n\t\tif(readers !== WRITE_LOCKED && Atomics.compareExchange(data, index, readers, readers + 1) === readers) {\n\t\t\treturn;\n\t\t}\n\t\tif(canWait() && readers === WRITE_LOCKED) {\n\t\t\tAtomics.wait(data, index, WRITE_LOCKED);\n\t\t}\n\t}\n}\nexport function writeLock(data: Int32Array, index: number = 0) {\n\tAtomics.add(data, index + 1, 1);\n\twhile(Atomics.compareExchange(data, index, 0, WRITE_LOCKED) !== 0) {\n\t\tlet state = data[index];\n\t\tif(canWait() && state !== 0) {\n\t\t\tAtomics.wait(data, index, state);\n\t\t}\n\t}\n\tAtomics.sub(data, index + 1, 1);\n}\n\nexport function readUnlock(data: Int32Array, index: number = 0) {\n\tif(Atomics.sub(data, index, 1) === 1) {\n\t\tAtomics.notify(data, index);\n\t}\n}\nexport function writeUnlock(data: Int32Array, index: number = 0) {\n\tif(Atomics.compareExchange(data, index, WRITE_LOCKED, 0) !== WRITE_LOCKED) {\n\t\tconsole.warn('We are unlocking when it was not write locked!');\n\t}\n\tAtomics.notify(data, index);\n\tAtomics.notify(data, index + 1);\n}\n\nexport const READ_WRITE_LOCK_ALLOCATE_COUNT = 2;\n"],"mappings":"AAEA,SAAS,IAAmB;CAC3B,OAAO,OAAO,OAAS,OAAe,uBAAuB;AAC9D;AAEA,SAAgB,EAAS,GAAkB,IAAgB,GAAG;CAE7D,SAAY;EACX,IAAI,IAAU,EAAK,IAAQ;EAC3B,IAAG,MAAY,GAAG;GACjB,AAAG,EAAQ,KACV,QAAQ,KAAK,GAAM,IAAQ,GAAG,CAAO;GAEtC;EACD;EACA,IAAI,IAAU,EAAK;EACnB,IAAG,MAAY,MAAgB,QAAQ,gBAAgB,GAAM,GAAO,GAAS,IAAU,CAAC,MAAM,GAC7F;EAED,AAAG,EAAQ,KAAK,MAAY,MAC3B,QAAQ,KAAK,GAAM,GAAO,EAAY;CAExC;AACD;AACA,SAAgB,EAAU,GAAkB,IAAgB,GAAG;CAE9D,KADA,QAAQ,IAAI,GAAM,IAAQ,GAAG,CAAC,GACxB,QAAQ,gBAAgB,GAAM,GAAO,GAAG,EAAY,MAAM,IAAG;EAClE,IAAI,IAAQ,EAAK;EACjB,AAAG,EAAQ,KAAK,MAAU,KACzB,QAAQ,KAAK,GAAM,GAAO,CAAK;CAEjC;CACA,QAAQ,IAAI,GAAM,IAAQ,GAAG,CAAC;AAC/B;AAEA,SAAgB,EAAW,GAAkB,IAAgB,GAAG;CAC/D,AAAG,QAAQ,IAAI,GAAM,GAAO,CAAC,MAAM,KAClC,QAAQ,OAAO,GAAM,CAAK;AAE5B;AACA,SAAgB,EAAY,GAAkB,IAAgB,GAAG;CAKhE,AAJG,QAAQ,gBAAgB,GAAM,GAAO,IAAc,CAAC,MAAM,MAC5D,QAAQ,KAAK,gDAAgD,GAE9D,QAAQ,OAAO,GAAM,CAAK,GAC1B,QAAQ,OAAO,GAAM,IAAQ,CAAC;AAC/B;AAEA,IAAa,IAAiC"}
|
|
1
|
+
{"version":3,"file":"read-write-lock.js","names":[],"sources":["../../src/lock/read-write-lock.ts"],"sourcesContent":["const WRITE_LOCKED = -1;\n\nfunction canWait(): boolean {\n\treturn typeof self !== 'undefined' && 'WorkerGlobalScope' in self;\n}\n\nexport function readLock(data: Int32Array, index: number = 0) {\n\t// Pending writers block new readers so a steady stream of queries cannot starve a resize.\n\twhile(true) {\n\t\tlet writers = data[index + 1];\n\t\tif(writers !== 0) {\n\t\t\tif(canWait()) {\n\t\t\t\tAtomics.wait(data, index + 1, writers);\n\t\t\t}\n\t\t\tcontinue;\n\t\t}\n\t\tlet readers = data[index];\n\t\tif(readers !== WRITE_LOCKED && Atomics.compareExchange(data, index, readers, readers + 1) === readers) {\n\t\t\treturn;\n\t\t}\n\t\tif(canWait() && readers === WRITE_LOCKED) {\n\t\t\tAtomics.wait(data, index, WRITE_LOCKED);\n\t\t}\n\t}\n}\nexport function writeLock(data: Int32Array, index: number = 0) {\n\tAtomics.add(data, index + 1, 1);\n\twhile(Atomics.compareExchange(data, index, 0, WRITE_LOCKED) !== 0) {\n\t\tlet state = data[index];\n\t\tif(canWait() && state !== 0) {\n\t\t\tAtomics.wait(data, index, state);\n\t\t}\n\t}\n\tAtomics.sub(data, index + 1, 1);\n}\n// Non-blocking write acquire: succeeds only when there is no active reader and no writer. Unlike writeLock it never\n// registers a pending writer, so a failed attempt leaves readers completely untouched - the caller just backs off.\nexport function tryWriteLock(data: Int32Array, index: number = 0): boolean {\n\treturn Atomics.compareExchange(data, index, 0, WRITE_LOCKED) === 0;\n}\n\nexport function readUnlock(data: Int32Array, index: number = 0) {\n\tif(Atomics.sub(data, index, 1) === 1) {\n\t\tAtomics.notify(data, index);\n\t}\n}\nexport function writeUnlock(data: Int32Array, index: number = 0) {\n\tif(Atomics.compareExchange(data, index, WRITE_LOCKED, 0) !== WRITE_LOCKED) {\n\t\tconsole.warn('We are unlocking when it was not write locked!');\n\t}\n\tAtomics.notify(data, index);\n\tAtomics.notify(data, index + 1);\n}\n\nexport const READ_WRITE_LOCK_ALLOCATE_COUNT = 2;\n"],"mappings":"AAEA,SAAS,IAAmB;CAC3B,OAAO,OAAO,OAAS,OAAe,uBAAuB;AAC9D;AAEA,SAAgB,EAAS,GAAkB,IAAgB,GAAG;CAE7D,SAAY;EACX,IAAI,IAAU,EAAK,IAAQ;EAC3B,IAAG,MAAY,GAAG;GACjB,AAAG,EAAQ,KACV,QAAQ,KAAK,GAAM,IAAQ,GAAG,CAAO;GAEtC;EACD;EACA,IAAI,IAAU,EAAK;EACnB,IAAG,MAAY,MAAgB,QAAQ,gBAAgB,GAAM,GAAO,GAAS,IAAU,CAAC,MAAM,GAC7F;EAED,AAAG,EAAQ,KAAK,MAAY,MAC3B,QAAQ,KAAK,GAAM,GAAO,EAAY;CAExC;AACD;AACA,SAAgB,EAAU,GAAkB,IAAgB,GAAG;CAE9D,KADA,QAAQ,IAAI,GAAM,IAAQ,GAAG,CAAC,GACxB,QAAQ,gBAAgB,GAAM,GAAO,GAAG,EAAY,MAAM,IAAG;EAClE,IAAI,IAAQ,EAAK;EACjB,AAAG,EAAQ,KAAK,MAAU,KACzB,QAAQ,KAAK,GAAM,GAAO,CAAK;CAEjC;CACA,QAAQ,IAAI,GAAM,IAAQ,GAAG,CAAC;AAC/B;AAGA,SAAgB,EAAa,GAAkB,IAAgB,GAAY;CAC1E,OAAO,QAAQ,gBAAgB,GAAM,GAAO,GAAG,EAAY,MAAM;AAClE;AAEA,SAAgB,EAAW,GAAkB,IAAgB,GAAG;CAC/D,AAAG,QAAQ,IAAI,GAAM,GAAO,CAAC,MAAM,KAClC,QAAQ,OAAO,GAAM,CAAK;AAE5B;AACA,SAAgB,EAAY,GAAkB,IAAgB,GAAG;CAKhE,AAJG,QAAQ,gBAAgB,GAAM,GAAO,IAAc,CAAC,MAAM,MAC5D,QAAQ,KAAK,gDAAgD,GAE9D,QAAQ,OAAO,GAAM,CAAK,GAC1B,QAAQ,OAAO,GAAM,IAAQ,CAAC;AAC/B;AAEA,IAAa,IAAiC"}
|
package/dist/shared-list.d.ts
CHANGED
|
@@ -2,12 +2,13 @@ import type { SharedAllocatedMemory } from './allocated-memory';
|
|
|
2
2
|
import type MemoryHeap from './memory-heap';
|
|
3
3
|
import { type NumericArray, type TypedArrayConstructor } from './utils/array-type';
|
|
4
4
|
export default class SharedList<T extends NumericArray = Uint32Array> implements Iterable<SharedListIterable<T>> {
|
|
5
|
-
static readonly ALLOCATE_COUNT =
|
|
5
|
+
static readonly ALLOCATE_COUNT = 7;
|
|
6
6
|
private memory;
|
|
7
7
|
private positionBits;
|
|
8
8
|
private positionMask;
|
|
9
9
|
private firstBlock;
|
|
10
10
|
private uint16Array;
|
|
11
|
+
private lockArray;
|
|
11
12
|
private cachedType;
|
|
12
13
|
private cachedDataLength;
|
|
13
14
|
private cachedByteMultipler;
|
|
@@ -27,7 +28,10 @@ export default class SharedList<T extends NumericArray = Uint32Array> implements
|
|
|
27
28
|
deleteIndex(deleteIndex: number): boolean;
|
|
28
29
|
deleteValue(deleteValues: T[number] | Array<T[number]>): boolean;
|
|
29
30
|
compact(): void;
|
|
31
|
+
private autoReclaim;
|
|
32
|
+
private reclaimTombstones;
|
|
30
33
|
clear(): void;
|
|
34
|
+
private clearLocked;
|
|
31
35
|
[Symbol.iterator](): Generator<{
|
|
32
36
|
data: T;
|
|
33
37
|
index: number;
|
package/dist/shared-list.js
CHANGED
|
@@ -1,14 +1,16 @@
|
|
|
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 {
|
|
3
|
+
import { readLock as a, readUnlock as o, tryWriteLock as s, writeLock as c, writeUnlock as l } from "./lock/read-write-lock.js";
|
|
4
|
+
import { getArrayTypeCode as u, getByteMultipler as d, isBigIntType as f, isFloatType as p, makeArrayView as m } from "./utils/array-type.js";
|
|
4
5
|
//#region src/shared-list.ts
|
|
5
|
-
var
|
|
6
|
-
static ALLOCATE_COUNT =
|
|
6
|
+
var h = 7, g = 2, _ = g * Uint32Array.BYTES_PER_ELEMENT, v = 0, y = 1, b = 2, x = 4, S = 5, C = 0, w = 1, T = 16, E = class {
|
|
7
|
+
static ALLOCATE_COUNT = h;
|
|
7
8
|
memory;
|
|
8
9
|
positionBits;
|
|
9
10
|
positionMask;
|
|
10
11
|
firstBlock;
|
|
11
12
|
uint16Array;
|
|
13
|
+
lockArray;
|
|
12
14
|
cachedType;
|
|
13
15
|
cachedDataLength;
|
|
14
16
|
cachedByteMultipler = 1;
|
|
@@ -17,16 +19,16 @@ var u = 4, d = 2, f = d * Uint32Array.BYTES_PER_ELEMENT, p = 0, m = 1, h = 2, g
|
|
|
17
19
|
bufferViews = [];
|
|
18
20
|
onDelete;
|
|
19
21
|
get length() {
|
|
20
|
-
return Atomics.load(this.firstBlock.data,
|
|
22
|
+
return Atomics.load(this.firstBlock.data, b);
|
|
21
23
|
}
|
|
22
24
|
get usedMemory() {
|
|
23
|
-
let t = this.firstBlock.usedMemory, { bufferPosition: n, bufferByteOffset: r } = e(this.firstBlock.data,
|
|
25
|
+
let t = this.firstBlock.usedMemory, { bufferPosition: n, bufferByteOffset: r } = e(this.firstBlock.data, v, this.positionBits);
|
|
24
26
|
for (; r;) {
|
|
25
27
|
let a = new i(this.memory, {
|
|
26
28
|
bufferPosition: n,
|
|
27
29
|
bufferByteOffset: r
|
|
28
30
|
});
|
|
29
|
-
t += a.usedMemory, {bufferPosition: n, bufferByteOffset: r} = e(a.data,
|
|
31
|
+
t += a.usedMemory, {bufferPosition: n, bufferByteOffset: r} = e(a.data, C, this.positionBits);
|
|
30
32
|
}
|
|
31
33
|
return t;
|
|
32
34
|
}
|
|
@@ -43,38 +45,46 @@ var u = 4, d = 2, f = d * Uint32Array.BYTES_PER_ELEMENT, p = 0, m = 1, h = 2, g
|
|
|
43
45
|
Atomics.store(this.uint16Array, 1, e);
|
|
44
46
|
}
|
|
45
47
|
constructor(e, t) {
|
|
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(
|
|
48
|
+
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(h), this.uint16Array = new Uint16Array(this.firstBlock.data.buffer, this.firstBlock.bufferByteOffset + 3 * Uint32Array.BYTES_PER_ELEMENT, 2), r(this.firstBlock.data, y, this.firstBlock.pointer), this.type = u(t?.type ?? Uint32Array), this.dataLength = t?.dataLength ?? 1), this.lockArray = new Int32Array(this.firstBlock.data.buffer, this.firstBlock.bufferByteOffset + S * Uint32Array.BYTES_PER_ELEMENT, 2), this.cachedType = Atomics.load(this.uint16Array, 0), this.cachedDataLength = Math.max(1, Atomics.load(this.uint16Array, 1)), this.cachedByteMultipler = d(this.cachedType), this.cachedIsBigInt = f(this.cachedType), this.cachedIsFloat = p(this.cachedType);
|
|
47
49
|
}
|
|
48
50
|
insert(e) {
|
|
49
51
|
Array.isArray(e) || (e = [e]);
|
|
50
52
|
let r = this.cachedDataLength;
|
|
51
53
|
if (e.length > r) throw Error(`Can't insert ${e.length} array into shared list of ${r} dataLength`);
|
|
52
|
-
let i = this.memory.allocUI32(
|
|
54
|
+
let i = this.memory.allocUI32(g + r * this.cachedByteMultipler), a = this.getDataBlock(i.data.buffer, i.data.byteOffset), o = i.pointer, s = a;
|
|
53
55
|
for (let t = 0; t < e.length; t++) this.cachedIsFloat ? s[t] = e[t] : (this.cachedIsBigInt, Atomics.store(a, t, e[t]));
|
|
54
56
|
let c = this.positionBits, l = this.positionMask;
|
|
55
57
|
for (;;) {
|
|
56
|
-
let e = t(this.firstBlock.data,
|
|
58
|
+
let e = t(this.firstBlock.data, y), r = this.getBufferView(e & l);
|
|
57
59
|
if (!r) continue;
|
|
58
|
-
let i = (e >>> c >>> 2) +
|
|
59
|
-
if (e === t(this.firstBlock.data,
|
|
60
|
+
let i = (e >>> c >>> 2) + C, a = Atomics.load(r, i);
|
|
61
|
+
if (e === t(this.firstBlock.data, y)) {
|
|
60
62
|
if (a === 0) {
|
|
61
63
|
if (Atomics.compareExchange(r, i, 0, o) === 0) {
|
|
62
|
-
n(this.firstBlock.data,
|
|
64
|
+
n(this.firstBlock.data, y, o, e);
|
|
63
65
|
break;
|
|
64
66
|
}
|
|
65
|
-
} else n(this.firstBlock.data,
|
|
67
|
+
} else n(this.firstBlock.data, y, a, e);
|
|
66
68
|
}
|
|
67
69
|
}
|
|
68
|
-
Atomics.add(this.firstBlock.data,
|
|
70
|
+
Atomics.add(this.firstBlock.data, b, 1);
|
|
69
71
|
}
|
|
70
72
|
deleteMatch(e) {
|
|
71
|
-
|
|
72
|
-
|
|
73
|
+
let t = !1;
|
|
74
|
+
for (let { data: n, index: r, deleteCurrent: i } of this) if (e(n, r) && i()) {
|
|
75
|
+
t = !0;
|
|
76
|
+
break;
|
|
77
|
+
}
|
|
78
|
+
return this.autoReclaim(), t;
|
|
73
79
|
}
|
|
74
80
|
deleteIndex(e) {
|
|
75
81
|
if (e >= this.length || e < 0) return !1;
|
|
76
|
-
|
|
77
|
-
|
|
82
|
+
let t = !1;
|
|
83
|
+
for (let n of this) if (n.index === e && n.deleteCurrent()) {
|
|
84
|
+
t = !0;
|
|
85
|
+
break;
|
|
86
|
+
}
|
|
87
|
+
return this.autoReclaim(), t;
|
|
78
88
|
}
|
|
79
89
|
deleteValue(e) {
|
|
80
90
|
return Array.isArray(e) ? this.deleteMatch((t) => {
|
|
@@ -84,45 +94,79 @@ var u = 4, d = 2, f = d * Uint32Array.BYTES_PER_ELEMENT, p = 0, m = 1, h = 2, g
|
|
|
84
94
|
}) : this.deleteMatch((t) => t[0] === e);
|
|
85
95
|
}
|
|
86
96
|
compact() {
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
Atomics.load(l, d + _) ? (r(n, i, f), c.free(u)) : (e = s, n = l, i = d + g), s = f;
|
|
97
|
+
c(this.lockArray);
|
|
98
|
+
try {
|
|
99
|
+
this.reclaimTombstones(!0);
|
|
100
|
+
} finally {
|
|
101
|
+
l(this.lockArray);
|
|
93
102
|
}
|
|
94
|
-
|
|
103
|
+
}
|
|
104
|
+
autoReclaim() {
|
|
105
|
+
let e = Atomics.load(this.firstBlock.data, x);
|
|
106
|
+
if (!(e < T || e < this.length) && s(this.lockArray)) try {
|
|
107
|
+
this.reclaimTombstones(!1);
|
|
108
|
+
} finally {
|
|
109
|
+
l(this.lockArray);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
reclaimTombstones(e) {
|
|
113
|
+
let i = this.firstBlock.pointer, a = this.firstBlock.data, o = C, s = this.positionBits, c = this.positionMask, l = 0, u = t(a, o);
|
|
114
|
+
for (; u;) {
|
|
115
|
+
let t = u & c, d = this.memory.buffers[t];
|
|
116
|
+
if (!d) break;
|
|
117
|
+
let f = this.getBufferView(t), p = u >>> s, m = p >>> 2, h = Atomics.load(f, m + C);
|
|
118
|
+
if (Atomics.load(f, m + w)) {
|
|
119
|
+
if (h === 0 && !e) break;
|
|
120
|
+
r(a, o, h), n(this.firstBlock.data, y, i, u), d.free(p), l++;
|
|
121
|
+
} else i = u, a = f, o = m + C;
|
|
122
|
+
u = h;
|
|
123
|
+
}
|
|
124
|
+
return e && r(this.firstBlock.data, y, i), l && Atomics.sub(this.firstBlock.data, x, l), l;
|
|
95
125
|
}
|
|
96
126
|
clear() {
|
|
127
|
+
c(this.lockArray);
|
|
128
|
+
try {
|
|
129
|
+
this.clearLocked();
|
|
130
|
+
} finally {
|
|
131
|
+
l(this.lockArray);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
clearLocked() {
|
|
97
135
|
let e, i = !1;
|
|
98
136
|
for (; !i;) {
|
|
99
|
-
if (e = t(this.firstBlock.data,
|
|
100
|
-
i = n(this.firstBlock.data,
|
|
137
|
+
if (e = t(this.firstBlock.data, v), !e) return;
|
|
138
|
+
i = n(this.firstBlock.data, v, 0, e);
|
|
101
139
|
}
|
|
102
140
|
if (!e) return;
|
|
103
|
-
r(this.firstBlock.data,
|
|
141
|
+
r(this.firstBlock.data, y, this.firstBlock.pointer);
|
|
104
142
|
let a = 0, o = this.positionBits, s = this.positionMask, c = e;
|
|
105
143
|
for (; c;) {
|
|
106
144
|
let e = c & s, t = this.memory.buffers[e];
|
|
107
145
|
if (!t) break;
|
|
108
146
|
let n = this.getBufferView(e), r = c >>> o, i = r >>> 2;
|
|
109
|
-
c = Atomics.load(n, i +
|
|
147
|
+
c = Atomics.load(n, i + C), Atomics.load(n, i + w) || (a++, this.onDelete && this.onDelete(this.getDataBlock(n.buffer, r))), t.free(r);
|
|
110
148
|
}
|
|
111
|
-
Atomics.sub(this.firstBlock.data,
|
|
149
|
+
Atomics.sub(this.firstBlock.data, b, a), Atomics.store(this.firstBlock.data, x, 0);
|
|
112
150
|
}
|
|
113
151
|
*[Symbol.iterator]() {
|
|
114
|
-
let e = 0, n = this.positionBits, r = this.positionMask
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
152
|
+
let e = 0, n = this.positionBits, r = this.positionMask;
|
|
153
|
+
a(this.lockArray);
|
|
154
|
+
try {
|
|
155
|
+
let i = t(this.firstBlock.data, v);
|
|
156
|
+
for (; i;) {
|
|
157
|
+
let t = i & r, a = this.getBufferView(t);
|
|
158
|
+
if (!a) return;
|
|
159
|
+
let o = i >>> n, s = o >>> 2;
|
|
160
|
+
if (i = Atomics.load(a, s + C), Atomics.load(a, s + w)) continue;
|
|
161
|
+
let c = a.buffer, l = s + w;
|
|
162
|
+
yield {
|
|
163
|
+
data: this.getDataBlock(c, o),
|
|
164
|
+
index: e,
|
|
165
|
+
deleteCurrent: () => Atomics.compareExchange(a, l, 0, 1) === 0 && (Atomics.sub(this.firstBlock.data, b, 1), Atomics.add(this.firstBlock.data, x, 1), this.onDelete && this.onDelete(this.getDataBlock(c, o)), !0)
|
|
166
|
+
}, e++;
|
|
167
|
+
}
|
|
168
|
+
} finally {
|
|
169
|
+
o(this.lockArray);
|
|
126
170
|
}
|
|
127
171
|
}
|
|
128
172
|
forEach(e) {
|
|
@@ -138,22 +182,22 @@ var u = 4, d = 2, f = d * Uint32Array.BYTES_PER_ELEMENT, p = 0, m = 1, h = 2, g
|
|
|
138
182
|
if (n) return t = new Uint32Array(n.buf), this.bufferViews[e] = t, t;
|
|
139
183
|
}
|
|
140
184
|
getDataBlock(e, t) {
|
|
141
|
-
let n = t +
|
|
142
|
-
return
|
|
185
|
+
let n = t + _;
|
|
186
|
+
return m(this.cachedType, e, n, this.cachedDataLength);
|
|
143
187
|
}
|
|
144
188
|
free() {
|
|
145
|
-
let { bufferPosition: t, bufferByteOffset: n } = e(this.firstBlock.data,
|
|
189
|
+
let { bufferPosition: t, bufferByteOffset: n } = e(this.firstBlock.data, v, this.positionBits);
|
|
146
190
|
for (; n;) {
|
|
147
191
|
let r = new i(this.memory, {
|
|
148
192
|
bufferPosition: t,
|
|
149
193
|
bufferByteOffset: n
|
|
150
194
|
});
|
|
151
|
-
({bufferPosition: t, bufferByteOffset: n} = e(r.data,
|
|
195
|
+
({bufferPosition: t, bufferByteOffset: n} = e(r.data, C, this.positionBits)), this.onDelete && !Atomics.load(r.data, w) && this.onDelete(this.getDataBlock(r.data.buffer, r.data.byteOffset)), r.free();
|
|
152
196
|
}
|
|
153
197
|
this.firstBlock.free();
|
|
154
198
|
}
|
|
155
199
|
};
|
|
156
200
|
//#endregion
|
|
157
|
-
export {
|
|
201
|
+
export { E as default };
|
|
158
202
|
|
|
159
203
|
//# sourceMappingURL=shared-list.js.map
|
package/dist/shared-list.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"shared-list.js","names":[],"sources":["../src/shared-list.ts"],"sourcesContent":["import type { SharedAllocatedMemory } from './allocated-memory';\nimport AllocatedMemory from './allocated-memory';\nimport type MemoryHeap from './memory-heap';\nimport { loadPointer, loadRawPointer, 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
|
+
{"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 { readLock, readUnlock, tryWriteLock, writeLock, writeUnlock } from './lock/read-write-lock';\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 and never takes a lock; delete only tombstones a node (a lock-free CAS).\n//\n// Reclamation: freeing a node hands its address back to the allocator, so it is only safe once no thread can still be\n// dereferencing it. insert is proven safe against this by the \"tail rules\" below (it only ever touches a tail node, whose\n// next is 0, and a reclaim never frees such a node), so insert stays completely lock-free. Traversal is NOT safe against a\n// concurrent free (a parked walker would read a recycled node), so iteration takes the SHARED side of a read/write lock and\n// reclaim takes the EXCLUSIVE side. reclaim uses a non-blocking acquire: it runs only when no other thread is mid-iteration\n// (exactly the quiescence we need) and is skipped otherwise, so it never stalls readers. Reclaim is throttled to when dead\n// nodes have caught up to live ones, making it amortized O(1) per delete and keeping the chain within ~2x of live size -\n// the manual compact() is no longer required to bound memory (it remains as a force-everything pass).\nconst FIRST_BLOCK_RECORD_KEEPING_COUNT = 7;\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;\n// index 3 holds type (u16) + dataLength (u16)\nconst DEAD_COUNT_INDEX = 4;\n// Two consecutive u32s (readers, pending-writers) backing the reclaim read/write lock (see lock/read-write-lock).\nconst LOCK_INDEX = 5;\nconst NODE_NEXT_INDEX = 0;\nconst NODE_DELETED_INDEX = 1;\n// Only reclaim once at least this many dead nodes have piled up, so tiny lists don't pay a walk on every delete.\nconst RECLAIM_MIN = 16;\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\t32 index 4 - dead (tombstoned-but-still-linked) node count, drives reclaim throttling\n\t\t32 index 5 - reclaim lock: reader count\n\t\t32 index 6 - reclaim lock: pending-writer count\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// Int32 view over the two reclaim-lock words (readers, pending-writers) inside firstBlock\n\tprivate lockArray: Int32Array;\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 reclaim pass unlinks and frees 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.lockArray = new Int32Array(this.firstBlock.data.buffer, this.firstBlock.bufferByteOffset + LOCK_INDEX * Uint32Array.BYTES_PER_ELEMENT, 2);\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\tlet deleted = false;\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\tdeleted = true;\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t// Reclaim runs after the iterator's read lock is released, so it can win exclusivity when no one else is walking\n\t\tthis.autoReclaim();\n\t\treturn deleted;\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\tlet deleted = false;\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\tdeleted = true;\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tthis.autoReclaim();\n\t\treturn deleted;\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// Force-reclaim every tombstoned node, including a lone tail tombstone that autoReclaim leaves behind. Takes the reclaim\n\t// lock exclusively (blocking), so it is safe against concurrent iterate/delete - but must NOT be called from inside an\n\t// iteration on this same thread, since the walker still holds the read lock and the two would deadlock.\n\tcompact() {\n\t\twriteLock(this.lockArray);\n\t\ttry {\n\t\t\tthis.reclaimTombstones(true);\n\t\t} finally {\n\t\t\twriteUnlock(this.lockArray);\n\t\t}\n\t}\n\n\t// Fires after a delete once dead nodes have caught up to live ones. Grabs the reclaim lock only if no thread is\n\t// mid-iteration (non-blocking) - \"not in the middle of an iteration by another thread\" - and otherwise skips, leaving\n\t// the garbage for the next delete to retry. Amortizes to O(1) per delete while keeping the chain within ~2x of live.\n\tprivate autoReclaim() {\n\t\tlet dead = Atomics.load(this.firstBlock.data, DEAD_COUNT_INDEX);\n\t\tif(dead < RECLAIM_MIN || dead < this.length) {\n\t\t\treturn;\n\t\t}\n\n\t\tif(!tryWriteLock(this.lockArray)) {\n\t\t\treturn;\n\t\t}\n\t\ttry {\n\t\t\tthis.reclaimTombstones(false);\n\t\t} finally {\n\t\t\twriteUnlock(this.lockArray);\n\t\t}\n\t}\n\n\t// Physically unlinks and frees tombstoned nodes. The caller MUST hold the reclaim lock exclusively, so no iterator is\n\t// walking and no other reclaim runs concurrently - that is what rules out freeing a node another walker is parked on and\n\t// the stale-predecessor races of arbitrary lock-free deletion. insert stays lock-free and runs concurrently here, so the\n\t// tail rules keep it safe: a node whose next is 0 might be the enqueue target, so unless freeTail it is left in place, and\n\t// every freed node has the tail hint swung off it first (any insert holding a stale hint then re-checks and discards).\n\tprivate reclaimTombstones(freeTail: boolean): number {\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 freed = 0;\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\tif(followingPointer === 0 && !freeTail) {\n\t\t\t\t\t// Tail tombstone: a concurrent insert may be about to CAS its next, so leave it for compact() or the next\n\t\t\t\t\t// append (which turns it into a safe-to-free interior node)\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t\t// Unlink first so the following node stays reachable, then swing the tail hint off the node before freeing it,\n\t\t\t\t// then hand the memory back. onDelete already fired when the node was tombstoned.\n\t\t\t\tstoreRawPointer(prevView, prevNextIndex, followingPointer);\n\t\t\t\treplaceRawPointer(this.firstBlock.data, TAIL_INDEX, prevPointer, nextPointer);\n\t\t\t\tmemPool.free(byteOffset);\n\t\t\t\tfreed++;\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\tif(freeTail) {\n\t\t\t// Quiescent full pass: the last surviving node (or the sentinel) is unambiguously the tail\n\t\t\tstoreRawPointer(this.firstBlock.data, TAIL_INDEX, prevPointer);\n\t\t}\n\t\tif(freed) {\n\t\t\tAtomics.sub(this.firstBlock.data, DEAD_COUNT_INDEX, freed);\n\t\t}\n\t\treturn freed;\n\t}\n\n\tclear() {\n\t\twriteLock(this.lockArray);\n\t\ttry {\n\t\t\tthis.clearLocked();\n\t\t} finally {\n\t\t\twriteUnlock(this.lockArray);\n\t\t}\n\t}\n\tprivate clearLocked() {\n\t\t// Detach the whole chain, then free it. Reclaim lock is held, so no iterator is walking the chain we are freeing.\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\t// The whole chain (dead nodes included) is gone; no new dead nodes can appear while we hold the reclaim lock\n\t\tAtomics.store(this.firstBlock.data, DEAD_COUNT_INDEX, 0);\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\t// Shared read lock: many walkers (and lock-free inserts) run at once, but it blocks a reclaim from freeing a node out\n\t\t// from under us. Held for the whole walk and released in finally, so an early break/throw still frees it.\n\t\treadLock(this.lockArray);\n\t\ttry {\n\t\t\tlet nextPointer = loadRawPointer(this.firstBlock.data, HEAD_INDEX);\n\t\t\twhile(nextPointer) {\n\t\t\t\tlet position = nextPointer & positionMask;\n\t\t\t\tlet view = this.getBufferView(position);\n\t\t\t\t// Short circuit iterations if we can't access memory\n\t\t\t\tif(!view) {\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tlet byteOffset = nextPointer >>> positionBits;\n\t\t\t\tlet nodeIndex = byteOffset >>> 2;\n\t\t\t\tnextPointer = Atomics.load(view, nodeIndex + NODE_NEXT_INDEX);\n\n\t\t\t\t// Skip tombstoned nodes - they stay linked until a reclaim pass unlinks and frees them\n\t\t\t\tif(Atomics.load(view, nodeIndex + NODE_DELETED_INDEX)) {\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\n\t\t\t\tlet buffer = view.buffer;\n\t\t\t\tlet deletedIndex = nodeIndex + NODE_DELETED_INDEX;\n\t\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\t// slower to allocate than the payload view it would save, so pay for the view up front.\n\t\t\t\tyield {\n\t\t\t\t\tdata: this.getDataBlock(buffer, byteOffset),\n\t\t\t\t\tindex: currentIndex,\n\t\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\t\tdeleteCurrent: (): boolean => {\n\t\t\t\t\t\tif(Atomics.compareExchange(view, deletedIndex, 0, 1) === 0) {\n\t\t\t\t\t\t\tAtomics.sub(this.firstBlock.data, LENGTH_INDEX, 1);\n\t\t\t\t\t\t\t// Count the dead node so autoReclaim knows when garbage has caught up to live nodes\n\t\t\t\t\t\t\tAtomics.add(this.firstBlock.data, DEAD_COUNT_INDEX, 1);\n\t\t\t\t\t\t\tif(this.onDelete) {\n\t\t\t\t\t\t\t\tthis.onDelete(this.getDataBlock(buffer, byteOffset));\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\treturn true;\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\treturn false;\n\t\t\t\t\t},\n\t\t\t\t};\n\n\t\t\t\tcurrentIndex++;\n\t\t\t}\n\t\t} finally {\n\t\t\treadUnlock(this.lockArray);\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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"}
|