@daneren2005/shared-memory-objects 1.5.1 → 1.5.2
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/package.json +1 -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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"}
|
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
import { getPointer as e, loadRawPointer as t, storeRawPointer as n } from "../utils/pointer.js";
|
|
2
2
|
import r from "../allocated-memory.js";
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import
|
|
6
|
-
import
|
|
7
|
-
import
|
|
8
|
-
import
|
|
3
|
+
import { readLock as i, readUnlock as a, writeLock as o, writeUnlock as s } from "../lock/read-write-lock.js";
|
|
4
|
+
import { ARRAY_TYPE as c, getArrayTypeCode as l, getByteMultipler as u, makeArrayView as d } from "../utils/array-type.js";
|
|
5
|
+
import { lock as f, unlock as p } from "../lock/simple-lock.js";
|
|
6
|
+
import m from "../shared-map.js";
|
|
7
|
+
import h from "../shared-stack.js";
|
|
8
|
+
import g from "../shared-pool.js";
|
|
9
9
|
import { addCandidate as _, candidateLimit as v, distanceSquaredToRect as y } from "./spatial-neighbors.js";
|
|
10
10
|
import { getSpatialMapCapacity as ee, getSpatialPoolChunkSize as te } from "./spatial-entity.js";
|
|
11
11
|
//#region src/spatial/shared-spatial-map.ts
|
|
12
|
-
var b = 0, x = 1, S = 2, C = 3, w = 4, T = 5, E = 6, D = 8, ne = 10, O = 0, k = 1, A = 2, j = 8, M = 256, re = 255, N = 0, P = 1, F = 2, I = 3, L = 4, R = 5, z = 6, B = 7, V = 0, H = 1, U = 2, W = 3, G = 4, K = 5, q = 6, J = 7, Y = 8, X = 16777216, ie = X, ae = 4294967295, oe = 73856093, se = 19349663, ce = 50, le = 256, Z = 1, Q = 2, ue = 1e6, de = 4, fe = Float32Array, pe = class
|
|
12
|
+
var b = 0, x = 1, S = 2, C = 3, w = 4, T = 5, E = 6, D = 8, ne = 10, O = 0, k = 1, A = 2, j = 8, M = 256, re = 255, N = 0, P = 1, F = 2, I = 3, L = 4, R = 5, z = 6, B = 7, V = 0, H = 1, U = 2, W = 3, G = 4, K = 5, q = 6, J = 7, Y = 8, X = 16777216, ie = X, ae = 4294967295, oe = 73856093, se = 19349663, ce = 50, le = 256, Z = 1, Q = 2, ue = 1e6, de = 4, fe = Float32Array, pe = class l {
|
|
13
13
|
static ALLOCATE_COUNT = 11;
|
|
14
14
|
memory;
|
|
15
15
|
firstBlock;
|
|
@@ -60,46 +60,46 @@ var b = 0, x = 1, S = 2, C = 3, w = 4, T = 5, E = 6, D = 8, ne = 10, O = 0, k =
|
|
|
60
60
|
get type() {
|
|
61
61
|
return this.coordType;
|
|
62
62
|
}
|
|
63
|
-
constructor(
|
|
64
|
-
if (this.memory =
|
|
63
|
+
constructor(i, a) {
|
|
64
|
+
if (this.memory = i, a && "firstBlock" in a) this.firstBlock = new r(i, a.firstBlock), this.coordType = this.firstBlock.data[T], this.bucketPointers = new h(i, { firstBlock: e(t(this.firstBlock.data, b), i.positionBits) }), this.entityPool = new g(i, { firstBlock: e(t(this.firstBlock.data, x), i.positionBits) }), this.slotPool = new g(i, { firstBlock: e(t(this.firstBlock.data, S), i.positionBits) }), this.idMap = new m(i, { firstBlock: e(t(this.firstBlock.data, C), i.positionBits) }), this.bucketCount = Atomics.load(this.firstBlock.data, w), this.maxBucketCount = this.firstBlock.data[ne], this.nullIndex = ge(this.coordType);
|
|
65
65
|
else {
|
|
66
|
-
let e =
|
|
67
|
-
this.bucketCount = Math.min(1073741824, Math.max(1, Math.floor(
|
|
68
|
-
let t =
|
|
69
|
-
if (this.maxBucketCount = Math.min(1073741824, Math.max(this.bucketCount, Math.floor(
|
|
70
|
-
let
|
|
66
|
+
let e = a?.gridSize ?? ce;
|
|
67
|
+
this.bucketCount = Math.min(1073741824, Math.max(1, Math.floor(a?.buckets ?? le)));
|
|
68
|
+
let t = a?.maxEntities ?? ue, r = a?.maxSlots ?? t * de;
|
|
69
|
+
if (this.maxBucketCount = Math.min(1073741824, Math.max(this.bucketCount, Math.floor(a?.maxBuckets ?? _e(r * 2)))), this.coordType = me(a?.type), this.nullIndex = ge(this.coordType), this.coordType === c.float32 && (t > X || r > X)) throw Error(`Float32 SharedSpatialMap supports at most ${X} entities and slots; pass type: Float64Array for more`);
|
|
70
|
+
let o = {
|
|
71
71
|
type: he(this.coordType),
|
|
72
72
|
dataLength: Y,
|
|
73
73
|
maxLength: t,
|
|
74
|
-
maxChunkSize: te(t, Y,
|
|
75
|
-
},
|
|
74
|
+
maxChunkSize: te(t, Y, u(this.coordType), i.maxAllocationLength)
|
|
75
|
+
}, s = {
|
|
76
76
|
type: he(this.coordType),
|
|
77
77
|
dataLength: B,
|
|
78
78
|
maxLength: r,
|
|
79
|
-
maxChunkSize: te(r, B,
|
|
80
|
-
},
|
|
81
|
-
this.firstBlock =
|
|
79
|
+
maxChunkSize: te(r, B, u(this.coordType), i.maxAllocationLength)
|
|
80
|
+
}, d = g.getAllocateCount(i, o), f = g.getAllocateCount(i, s), p = m.ALLOCATE_COUNT, _ = l.ALLOCATE_COUNT;
|
|
81
|
+
this.firstBlock = i.allocUI32(_ + d + f + p), this.firstBlock.data[w] = this.bucketCount, this.firstBlock.data[T] = this.coordType, this.firstBlock.data[ne] = this.maxBucketCount;
|
|
82
82
|
let v = this.getConfigView();
|
|
83
|
-
v[0] = e, this.bucketPointers = new
|
|
84
|
-
let y = this.firstBlock.getSubAllocation(_,
|
|
85
|
-
this.entityPool = new
|
|
83
|
+
v[0] = e, this.bucketPointers = new h(i), n(this.firstBlock.data, b, this.bucketPointers.pointer), this.appendBucketSegments(this.bucketCount);
|
|
84
|
+
let y = this.firstBlock.getSubAllocation(_, d);
|
|
85
|
+
this.entityPool = new g(i, {
|
|
86
86
|
firstBlock: y,
|
|
87
|
-
...
|
|
87
|
+
...o
|
|
88
88
|
}), n(this.firstBlock.data, x, y.pointer);
|
|
89
|
-
let E = this.firstBlock.getSubAllocation(_ +
|
|
90
|
-
this.slotPool = new
|
|
89
|
+
let E = this.firstBlock.getSubAllocation(_ + d, f);
|
|
90
|
+
this.slotPool = new g(i, {
|
|
91
91
|
firstBlock: E,
|
|
92
|
-
...
|
|
92
|
+
...s
|
|
93
93
|
}), n(this.firstBlock.data, S, E.pointer);
|
|
94
|
-
let D = this.firstBlock.getSubAllocation(_ +
|
|
95
|
-
this.idMap = new
|
|
94
|
+
let D = this.firstBlock.getSubAllocation(_ + d + f, p);
|
|
95
|
+
this.idMap = new m(i, {
|
|
96
96
|
firstBlock: D,
|
|
97
|
-
capacity: ee(t,
|
|
97
|
+
capacity: ee(t, i.maxAllocationLength)
|
|
98
98
|
}), n(this.firstBlock.data, C, D.pointer);
|
|
99
99
|
}
|
|
100
100
|
this.bucketMask = $(this.bucketCount), this.tableLock = new Int32Array(this.firstBlock.data.buffer, this.firstBlock.bufferByteOffset + D * Uint32Array.BYTES_PER_ELEMENT, 2);
|
|
101
|
-
let
|
|
102
|
-
this.cellSize =
|
|
101
|
+
let o = this.getConfigView();
|
|
102
|
+
this.cellSize = o[0];
|
|
103
103
|
}
|
|
104
104
|
get pointer() {
|
|
105
105
|
return this.firstBlock.pointer;
|
|
@@ -109,41 +109,41 @@ var b = 0, x = 1, S = 2, C = 3, w = 4, T = 5, E = 6, D = 8, ne = 10, O = 0, k =
|
|
|
109
109
|
for (let n of this.bucketPointers) t += new r(this.memory, e(n, this.memory.positionBits)).usedMemory;
|
|
110
110
|
return t;
|
|
111
111
|
}
|
|
112
|
-
insert(e, t, n, r = 0,
|
|
113
|
-
|
|
112
|
+
insert(e, t, n, r = 0, o = 0) {
|
|
113
|
+
i(this.tableLock);
|
|
114
114
|
try {
|
|
115
|
-
this.syncBucketCount(), this.insertLocked(e, t, n, r,
|
|
115
|
+
this.syncBucketCount(), this.insertLocked(e, t, n, r, o);
|
|
116
116
|
} finally {
|
|
117
|
-
|
|
117
|
+
a(this.tableLock);
|
|
118
118
|
}
|
|
119
119
|
this.growBucketsIfNeeded();
|
|
120
120
|
}
|
|
121
121
|
bulkInsert(e) {
|
|
122
122
|
let t = /* @__PURE__ */ new Map();
|
|
123
123
|
for (let n of e) t.set(n.id, n);
|
|
124
|
-
let n = Array.from(t.values()), r = this.idMap.getAll(n.map((e) => e.id)),
|
|
124
|
+
let n = Array.from(t.values()), r = this.idMap.getAll(n.map((e) => e.id)), o = [], s = 0, c = 0;
|
|
125
125
|
for (let e = 0; e < n.length; e++) if (r[e] === void 0) {
|
|
126
126
|
let t = n[e];
|
|
127
|
-
|
|
127
|
+
c++, s += (this.colOf(t.x + (t.width ?? 0)) - this.colOf(t.x) + 1) * (this.rowOf(t.y + (t.height ?? 0)) - this.rowOf(t.y) + 1);
|
|
128
128
|
}
|
|
129
|
-
this.ensureBucketCapacity(this.slotPool.length +
|
|
130
|
-
let
|
|
131
|
-
|
|
129
|
+
this.ensureBucketCapacity(this.slotPool.length + s);
|
|
130
|
+
let l = this.entityPool.reserveContiguous(c), u = this.slotPool.reserveContiguous(s);
|
|
131
|
+
i(this.tableLock);
|
|
132
132
|
try {
|
|
133
133
|
this.syncBucketCount();
|
|
134
134
|
for (let e = 0; e < n.length; e++) {
|
|
135
|
-
let t = n[e],
|
|
135
|
+
let t = n[e], i = t.width ?? 0, a = t.height ?? 0;
|
|
136
136
|
if (r[e] !== void 0) {
|
|
137
|
-
this.updateLocked(t.id, t.x, t.y,
|
|
137
|
+
this.updateLocked(t.id, t.x, t.y, i, a);
|
|
138
138
|
continue;
|
|
139
139
|
}
|
|
140
|
-
let
|
|
141
|
-
m[h + V] =
|
|
142
|
-
for (let e =
|
|
140
|
+
let s = this.colOf(t.x), c = this.rowOf(t.y), d = this.colOf(t.x + i), f = this.rowOf(t.y + a), p = l++, m = this.entityPool.blockFor(p), h = this.entityPool.blockOffset(p);
|
|
141
|
+
m[h + V] = s, m[h + H] = c, m[h + U] = d, m[h + W] = f, m[h + G] = t.x, m[h + K] = t.y, m[h + q] = i, m[h + J] = a, o.push([t.id, p]);
|
|
142
|
+
for (let e = c; e <= f; e++) for (let n = s; n <= d; n++) this.linkReservedBucket(n, e, u++, t.id, p, s, c);
|
|
143
143
|
}
|
|
144
|
-
this.idMap.setAll(
|
|
144
|
+
this.idMap.setAll(o);
|
|
145
145
|
} finally {
|
|
146
|
-
|
|
146
|
+
a(this.tableLock);
|
|
147
147
|
}
|
|
148
148
|
this.growBucketsForBulk();
|
|
149
149
|
}
|
|
@@ -157,20 +157,20 @@ var b = 0, x = 1, S = 2, C = 3, w = 4, T = 5, E = 6, D = 8, ne = 10, O = 0, k =
|
|
|
157
157
|
let u = this.entityPool.push(l);
|
|
158
158
|
this.idMap.set(e, u), this.linkRange(e, u, a, o, s, c);
|
|
159
159
|
}
|
|
160
|
-
update(e, t, n, r = 0,
|
|
161
|
-
let
|
|
162
|
-
if (
|
|
163
|
-
let e = this.entityPool.blockFor(
|
|
164
|
-
if (
|
|
165
|
-
e[
|
|
160
|
+
update(e, t, n, r = 0, o = 0) {
|
|
161
|
+
let s = this.idMap.get(e);
|
|
162
|
+
if (s !== void 0) {
|
|
163
|
+
let e = this.entityPool.blockFor(s), i = this.entityPool.blockOffset(s), a = this.colOf(t), c = this.rowOf(n), l = this.colOf(t + r), u = this.rowOf(n + o);
|
|
164
|
+
if (a === e[i + V] && c === e[i + H] && l === e[i + U] && u === e[i + W]) {
|
|
165
|
+
e[i + G] = t, e[i + K] = n, e[i + q] = r, e[i + J] = o;
|
|
166
166
|
return;
|
|
167
167
|
}
|
|
168
168
|
}
|
|
169
|
-
|
|
169
|
+
i(this.tableLock);
|
|
170
170
|
try {
|
|
171
|
-
this.syncBucketCount(), this.updateLocked(e, t, n, r,
|
|
171
|
+
this.syncBucketCount(), this.updateLocked(e, t, n, r, o);
|
|
172
172
|
} finally {
|
|
173
|
-
|
|
173
|
+
a(this.tableLock);
|
|
174
174
|
}
|
|
175
175
|
this.growBucketsIfNeeded();
|
|
176
176
|
}
|
|
@@ -188,11 +188,11 @@ var b = 0, x = 1, S = 2, C = 3, w = 4, T = 5, E = 6, D = 8, ne = 10, O = 0, k =
|
|
|
188
188
|
this.relinkRange(e, a, f, p, m, h, c, l, u, d), o[s + V] = f, o[s + H] = p, o[s + U] = m, o[s + W] = h, o[s + G] = t, o[s + K] = n, o[s + q] = r, o[s + J] = i;
|
|
189
189
|
}
|
|
190
190
|
remove(e) {
|
|
191
|
-
|
|
191
|
+
i(this.tableLock);
|
|
192
192
|
try {
|
|
193
193
|
return this.syncBucketCount(), this.removeLocked(e);
|
|
194
194
|
} finally {
|
|
195
|
-
|
|
195
|
+
a(this.tableLock);
|
|
196
196
|
}
|
|
197
197
|
}
|
|
198
198
|
removeLocked(e) {
|
|
@@ -208,12 +208,12 @@ var b = 0, x = 1, S = 2, C = 3, w = 4, T = 5, E = 6, D = 8, ne = 10, O = 0, k =
|
|
|
208
208
|
let a = [];
|
|
209
209
|
return this.searchInto(a, e, t, n, r, i), a;
|
|
210
210
|
}
|
|
211
|
-
searchInto(e, t, n, r,
|
|
212
|
-
|
|
211
|
+
searchInto(e, t, n, r, o, s) {
|
|
212
|
+
i(this.tableLock);
|
|
213
213
|
try {
|
|
214
|
-
return this.syncBucketCount(), this.searchIntoLocked(e, t, n, r,
|
|
214
|
+
return this.syncBucketCount(), this.searchIntoLocked(e, t, n, r, o, s);
|
|
215
215
|
} finally {
|
|
216
|
-
|
|
216
|
+
a(this.tableLock);
|
|
217
217
|
}
|
|
218
218
|
}
|
|
219
219
|
searchIntoLocked(e, t, n, r, i, a) {
|
|
@@ -232,12 +232,12 @@ var b = 0, x = 1, S = 2, C = 3, w = 4, T = 5, E = 6, D = 8, ne = 10, O = 0, k =
|
|
|
232
232
|
}
|
|
233
233
|
return e;
|
|
234
234
|
}
|
|
235
|
-
neighbors(e, t, n, r,
|
|
236
|
-
|
|
235
|
+
neighbors(e, t, n, r, o) {
|
|
236
|
+
i(this.tableLock);
|
|
237
237
|
try {
|
|
238
|
-
return this.syncBucketCount(), this.neighborsLocked(e, t, n, r,
|
|
238
|
+
return this.syncBucketCount(), this.neighborsLocked(e, t, n, r, o);
|
|
239
239
|
} finally {
|
|
240
|
-
|
|
240
|
+
a(this.tableLock);
|
|
241
241
|
}
|
|
242
242
|
}
|
|
243
243
|
neighborsLocked(e, t, n, r, i) {
|
|
@@ -282,7 +282,7 @@ var b = 0, x = 1, S = 2, C = 3, w = 4, T = 5, E = 6, D = 8, ne = 10, O = 0, k =
|
|
|
282
282
|
return n === 1 ? d.id === void 0 ? [] : [d.id] : o.map((e) => e.id);
|
|
283
283
|
}
|
|
284
284
|
clear() {
|
|
285
|
-
|
|
285
|
+
o(this.tableLock);
|
|
286
286
|
try {
|
|
287
287
|
this.syncBucketCount();
|
|
288
288
|
for (let e = 0; e < this.bucketPointers.length; e++) {
|
|
@@ -295,7 +295,7 @@ var b = 0, x = 1, S = 2, C = 3, w = 4, T = 5, E = 6, D = 8, ne = 10, O = 0, k =
|
|
|
295
295
|
for (let [t] of this.idMap) e.push(t);
|
|
296
296
|
for (let t of e) this.idMap.delete(t);
|
|
297
297
|
} finally {
|
|
298
|
-
|
|
298
|
+
s(this.tableLock);
|
|
299
299
|
}
|
|
300
300
|
}
|
|
301
301
|
free() {
|
|
@@ -306,7 +306,7 @@ var b = 0, x = 1, S = 2, C = 3, w = 4, T = 5, E = 6, D = 8, ne = 10, O = 0, k =
|
|
|
306
306
|
return { firstBlock: this.firstBlock.getSharedMemory() };
|
|
307
307
|
}
|
|
308
308
|
getConfigView() {
|
|
309
|
-
return
|
|
309
|
+
return d(this.coordType, this.firstBlock.data.buffer, this.firstBlock.bufferByteOffset + E * Uint32Array.BYTES_PER_ELEMENT, 1);
|
|
310
310
|
}
|
|
311
311
|
colOf(e) {
|
|
312
312
|
return Math.floor(e / this.cellSize);
|
|
@@ -347,25 +347,25 @@ var b = 0, x = 1, S = 2, C = 3, w = 4, T = 5, E = 6, D = 8, ne = 10, O = 0, k =
|
|
|
347
347
|
growBucketsIfNeeded() {
|
|
348
348
|
let e = Atomics.load(this.firstBlock.data, w);
|
|
349
349
|
if (!(e >= this.maxBucketCount || this.slotPool.length * Q <= e * Z)) {
|
|
350
|
-
|
|
350
|
+
o(this.tableLock);
|
|
351
351
|
try {
|
|
352
352
|
if (this.syncBucketCount(), this.slotPool.length * Q <= this.bucketCount * Z) return;
|
|
353
353
|
let e = Math.min(this.bucketCount * 2, this.maxBucketCount);
|
|
354
354
|
if (e === this.bucketCount) return;
|
|
355
355
|
this.resizeBuckets(e);
|
|
356
356
|
} finally {
|
|
357
|
-
|
|
357
|
+
s(this.tableLock);
|
|
358
358
|
}
|
|
359
359
|
}
|
|
360
360
|
}
|
|
361
361
|
ensureBucketCapacity(e) {
|
|
362
362
|
let t = Math.ceil(e * Q / Z), n = Math.min(_e(t), this.maxBucketCount);
|
|
363
363
|
if (!(n <= Atomics.load(this.firstBlock.data, w))) {
|
|
364
|
-
|
|
364
|
+
o(this.tableLock);
|
|
365
365
|
try {
|
|
366
366
|
this.syncBucketCount(), n > this.bucketCount && this.resizeBuckets(n);
|
|
367
367
|
} finally {
|
|
368
|
-
|
|
368
|
+
s(this.tableLock);
|
|
369
369
|
}
|
|
370
370
|
}
|
|
371
371
|
}
|
|
@@ -396,18 +396,18 @@ var b = 0, x = 1, S = 2, C = 3, w = 4, T = 5, E = 6, D = 8, ne = 10, O = 0, k =
|
|
|
396
396
|
}
|
|
397
397
|
linkIntoBucket(e, t, n) {
|
|
398
398
|
n[P] = e, n[F] = t;
|
|
399
|
-
let r = this.slotPool, i = r.push(n), a = this.bucketOf(e, t), o = this.getBucketSegment(a), s = this.bucketHeadSegments[a >>> j],
|
|
400
|
-
|
|
401
|
-
let
|
|
402
|
-
r.set(i, R, s[
|
|
399
|
+
let r = this.slotPool, i = r.push(n), a = this.bucketOf(e, t), o = this.getBucketSegment(a), s = this.bucketHeadSegments[a >>> j], c = this.bucketOffset(a), l = c + O;
|
|
400
|
+
f(o, l);
|
|
401
|
+
let u = c + k;
|
|
402
|
+
r.set(i, R, s[u]), s[u] = i, p(o, l);
|
|
403
403
|
}
|
|
404
404
|
linkReservedBucket(e, t, n, r, i, a, o) {
|
|
405
|
-
let s = this.slotPool,
|
|
406
|
-
|
|
407
|
-
let
|
|
408
|
-
|
|
405
|
+
let s = this.slotPool, c = s.blockFor(n), l = s.blockOffset(n);
|
|
406
|
+
c[l + N] = r, c[l + P] = e, c[l + F] = t, c[l + I] = a, c[l + L] = o, c[l + z] = i;
|
|
407
|
+
let u = this.bucketOf(e, t), d = this.getBucketSegment(u), m = this.bucketHeadSegments[u >>> j], h = this.bucketOffset(u), g = h + O;
|
|
408
|
+
f(d, g);
|
|
409
409
|
let _ = h + k;
|
|
410
|
-
|
|
410
|
+
c[l + R] = m[_], m[_] = n, p(d, g);
|
|
411
411
|
}
|
|
412
412
|
relinkRange(e, t, n, r, i, a, o, s, c, l) {
|
|
413
413
|
let u = n > o ? n : o, d = i < c ? i : c, f = r > s ? r : s, p = a < l ? a : l, m = u <= d && f <= p, h = this.slotScratch;
|
|
@@ -427,18 +427,18 @@ var b = 0, x = 1, S = 2, C = 3, w = 4, T = 5, E = 6, D = 8, ne = 10, O = 0, k =
|
|
|
427
427
|
}
|
|
428
428
|
}
|
|
429
429
|
refreshAnchorInBucket(e, t, n, r, i, a) {
|
|
430
|
-
let o = this.nullIndex, s = this.getBucketSegment(e),
|
|
431
|
-
|
|
432
|
-
let
|
|
433
|
-
for (;
|
|
434
|
-
let e =
|
|
430
|
+
let o = this.nullIndex, s = this.getBucketSegment(e), c = this.bucketOffset(e), l = c + O;
|
|
431
|
+
f(s, l);
|
|
432
|
+
let u = this.slotPool, d = this.bucketHeadSegments[e >>> j][c + k];
|
|
433
|
+
for (; d !== o;) {
|
|
434
|
+
let e = u.blockFor(d), o = u.blockOffset(d);
|
|
435
435
|
if (e[o + N] === t && e[o + P] === n && e[o + F] === r) {
|
|
436
436
|
e[o + I] = i, e[o + L] = a;
|
|
437
437
|
break;
|
|
438
438
|
}
|
|
439
|
-
|
|
439
|
+
d = e[o + R];
|
|
440
440
|
}
|
|
441
|
-
|
|
441
|
+
p(s, l);
|
|
442
442
|
}
|
|
443
443
|
unlinkRange(e, t, n, r, i) {
|
|
444
444
|
let a = this.nullIndex, o = this.slotPool;
|
|
@@ -448,65 +448,65 @@ var b = 0, x = 1, S = 2, C = 3, w = 4, T = 5, E = 6, D = 8, ne = 10, O = 0, k =
|
|
|
448
448
|
}
|
|
449
449
|
}
|
|
450
450
|
collectBucket(e, t, n, r, i, a, o, s) {
|
|
451
|
-
let
|
|
452
|
-
|
|
453
|
-
let
|
|
454
|
-
for (;
|
|
455
|
-
let t = m.blockFor(
|
|
451
|
+
let c = this.nullIndex, l = r + k, u = r + O;
|
|
452
|
+
f(t, u);
|
|
453
|
+
let d = n[l], m = this.slotPool;
|
|
454
|
+
for (; d !== c;) {
|
|
455
|
+
let t = m.blockFor(d), n = m.blockOffset(d);
|
|
456
456
|
if (t[n + P] === i && t[n + F] === a) {
|
|
457
457
|
let r = t[n + I], c = t[n + L];
|
|
458
458
|
i === (r > o ? r : o) && a === (c > s ? c : s) && e.push(t[n + N]);
|
|
459
459
|
}
|
|
460
|
-
|
|
460
|
+
d = t[n + R];
|
|
461
461
|
}
|
|
462
|
-
|
|
462
|
+
p(t, u);
|
|
463
463
|
}
|
|
464
464
|
collectNeighborBucket(e, t, n) {
|
|
465
465
|
let r = this.bucketOf(t, n), i = this.getBucketSegment(r), a = this.bucketHeadSegments[r >>> j], o = this.bucketOffset(r), s = o + k;
|
|
466
466
|
if (a[s] === this.nullIndex) return;
|
|
467
|
-
let
|
|
468
|
-
|
|
469
|
-
let
|
|
470
|
-
for (;
|
|
471
|
-
let r = this.slotPool.blockFor(
|
|
472
|
-
r[i + P] === t && r[i + F] === n && e.push(r[i + N]),
|
|
467
|
+
let c = o + O;
|
|
468
|
+
f(i, c);
|
|
469
|
+
let l = a[s];
|
|
470
|
+
for (; l !== this.nullIndex;) {
|
|
471
|
+
let r = this.slotPool.blockFor(l), i = this.slotPool.blockOffset(l);
|
|
472
|
+
r[i + P] === t && r[i + F] === n && e.push(r[i + N]), l = r[i + R];
|
|
473
473
|
}
|
|
474
|
-
|
|
474
|
+
p(i, c);
|
|
475
475
|
}
|
|
476
476
|
collectNearestBucket(e, t, n, r, i) {
|
|
477
|
-
let a = this.bucketOf(t, n), o = this.getBucketSegment(a), s = this.bucketHeadSegments[a >>> j],
|
|
478
|
-
if (s[
|
|
479
|
-
let
|
|
480
|
-
|
|
481
|
-
let
|
|
482
|
-
for (;
|
|
483
|
-
let a = this.slotPool.blockFor(
|
|
477
|
+
let a = this.bucketOf(t, n), o = this.getBucketSegment(a), s = this.bucketHeadSegments[a >>> j], c = this.bucketOffset(a), l = c + k;
|
|
478
|
+
if (s[l] === this.nullIndex) return;
|
|
479
|
+
let u = c + O;
|
|
480
|
+
f(o, u);
|
|
481
|
+
let d = s[l];
|
|
482
|
+
for (; d !== this.nullIndex;) {
|
|
483
|
+
let a = this.slotPool.blockFor(d), o = this.slotPool.blockOffset(d);
|
|
484
484
|
if (a[o + P] === t && a[o + F] === n) {
|
|
485
485
|
let t = a[o + z], n = this.entityPool.blockFor(t), s = this.entityPool.blockOffset(t), c = a[o + N], l = this.entityBlockDistanceSquared(n, s, r, i);
|
|
486
486
|
(l < e.distance || l === e.distance && (e.id === void 0 || c < e.id)) && (e.id = c, e.distance = l);
|
|
487
487
|
}
|
|
488
|
-
|
|
488
|
+
d = a[o + R];
|
|
489
489
|
}
|
|
490
|
-
|
|
490
|
+
p(o, u);
|
|
491
491
|
}
|
|
492
|
-
collectNeighborCandidates(e, t, n, r, i, a, o, s,
|
|
493
|
-
let
|
|
494
|
-
if (
|
|
492
|
+
collectNeighborCandidates(e, t, n, r, i, a, o, s, c) {
|
|
493
|
+
let l = this.bucketOf(t, n), u = this.getBucketSegment(l), d = this.bucketHeadSegments[l >>> j], m = this.bucketOffset(l), h = m + k;
|
|
494
|
+
if (d[h] === this.nullIndex) return;
|
|
495
495
|
let g = m + O;
|
|
496
|
-
|
|
497
|
-
let v =
|
|
496
|
+
f(u, g);
|
|
497
|
+
let v = d[h];
|
|
498
498
|
for (; v !== this.nullIndex;) {
|
|
499
|
-
let
|
|
500
|
-
if (
|
|
501
|
-
let d =
|
|
499
|
+
let l = this.slotPool.blockFor(v), u = this.slotPool.blockOffset(v);
|
|
500
|
+
if (l[u + P] === t && l[u + F] === n) {
|
|
501
|
+
let d = l[u + z], f = this.entityPool.blockFor(d), p = this.entityPool.blockOffset(d), m = f[p + V], h = f[p + U], g = f[p + H], v = f[p + W];
|
|
502
502
|
if (t === (r < m ? m : r > h ? h : r) && n === (i < g ? g : i > v ? v : i)) {
|
|
503
503
|
let t = this.entityBlockDistanceSquared(f, p, a, o);
|
|
504
|
-
t <=
|
|
504
|
+
t <= c && _(e, l[u + N], t, s);
|
|
505
505
|
}
|
|
506
506
|
}
|
|
507
|
-
v =
|
|
507
|
+
v = l[u + R];
|
|
508
508
|
}
|
|
509
|
-
|
|
509
|
+
p(u, g);
|
|
510
510
|
}
|
|
511
511
|
addEntityCandidate(e, t, n, r, i, a, o) {
|
|
512
512
|
let s = this.entityDistanceSquared(n, r, i);
|
|
@@ -521,27 +521,27 @@ var b = 0, x = 1, S = 2, C = 3, w = 4, T = 5, E = 6, D = 8, ne = 10, O = 0, k =
|
|
|
521
521
|
return y(n, r, i, a, i + e[t + q], a + e[t + J]);
|
|
522
522
|
}
|
|
523
523
|
unlinkFromBucket(e, t, n, r) {
|
|
524
|
-
let i = this.nullIndex, a = this.getBucketSegment(e), o = this.bucketHeadSegments[e >>> j], s = this.bucketOffset(e),
|
|
525
|
-
|
|
526
|
-
let
|
|
524
|
+
let i = this.nullIndex, a = this.getBucketSegment(e), o = this.bucketHeadSegments[e >>> j], s = this.bucketOffset(e), c = s + O;
|
|
525
|
+
f(a, c);
|
|
526
|
+
let l = s + k, u = this.slotPool, d = i, m = o[l];
|
|
527
527
|
for (; m !== i;) {
|
|
528
|
-
let e =
|
|
529
|
-
if (e[s + N] === t && e[s + P] === n && e[s + F] === r) return
|
|
530
|
-
|
|
528
|
+
let e = u.blockFor(m), s = u.blockOffset(m), f = e[s + R];
|
|
529
|
+
if (e[s + N] === t && e[s + P] === n && e[s + F] === r) return d === i ? o[l] = f : u.set(d, R, f), p(a, c), m;
|
|
530
|
+
d = m, m = f;
|
|
531
531
|
}
|
|
532
|
-
return
|
|
532
|
+
return p(a, c), i;
|
|
533
533
|
}
|
|
534
534
|
};
|
|
535
535
|
function me(e) {
|
|
536
|
-
let t =
|
|
537
|
-
if (t !==
|
|
536
|
+
let t = l(e ?? fe);
|
|
537
|
+
if (t !== c.float32 && t !== c.float64) throw Error("SharedSpatialMap must be Float32Array or Float64Array");
|
|
538
538
|
return t;
|
|
539
539
|
}
|
|
540
540
|
function he(e) {
|
|
541
|
-
return
|
|
541
|
+
return u(e) === 2 ? Float64Array : Float32Array;
|
|
542
542
|
}
|
|
543
543
|
function ge(e) {
|
|
544
|
-
return e ===
|
|
544
|
+
return e === c.float32 ? ie : ae;
|
|
545
545
|
}
|
|
546
546
|
function $(e) {
|
|
547
547
|
return e & e - 1 ? -1 : e - 1;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@daneren2005/shared-memory-objects",
|
|
3
|
-
"version": "1.5.
|
|
3
|
+
"version": "1.5.2",
|
|
4
4
|
"author": "daneren2005@gmail.com",
|
|
5
5
|
"description": "Creating objects with a SharedArrayBuffer",
|
|
6
6
|
"homepage": "https://github.com/daneren2005/shared-memory-objects#readme",
|