@daneren2005/shared-memory-objects 0.0.10 → 0.0.12
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +82 -3
- package/dist/shared-memory-objects.js +1356 -0
- package/dist/shared-memory-objects.js.map +1 -0
- package/dist/shared-memory-objects.umd.cjs +2 -0
- package/dist/shared-memory-objects.umd.cjs.map +1 -0
- package/dist/src/allocated-memory.d.ts +26 -0
- package/dist/src/cached-item-list.d.ts +37 -0
- package/{src/interfaces/pow2.ts → dist/src/interfaces/pow2.d.ts} +2 -3
- package/dist/src/interfaces/typed-array-constructor.d.ts +5 -0
- package/{src/interfaces/typed-array.ts → dist/src/interfaces/typed-array.d.ts} +1 -1
- package/dist/src/lock/read-write-lock.d.ts +5 -0
- package/dist/src/lock/simple-lock.d.ts +3 -0
- package/dist/src/main.d.ts +18 -0
- package/dist/src/memory-buffer.d.ts +185 -0
- package/dist/src/memory-heap.d.ts +34 -0
- package/dist/src/serialize-object.d.ts +5 -0
- package/dist/src/shared-list.d.ts +44 -0
- package/dist/src/shared-map.d.ts +25 -0
- package/dist/src/shared-pointer-list.d.ts +21 -0
- package/dist/src/shared-pool.d.ts +39 -0
- package/dist/src/shared-string.d.ts +23 -0
- package/dist/src/shared-vector.d.ts +41 -0
- package/dist/src/utils/16-from-32-array.d.ts +4 -0
- package/dist/src/utils/16-from-64-array.d.ts +2 -0
- package/dist/src/utils/float32-atomics.d.ts +5 -0
- package/dist/src/utils/pointer.d.ts +19 -0
- package/package.json +34 -22
- package/src/allocated-memory.ts +0 -89
- package/src/cached-item-list.ts +0 -143
- package/src/interfaces/typed-array-constructor.ts +0 -6
- package/src/lock/read-write-lock.ts +0 -41
- package/src/lock/simple-lock.ts +0 -21
- package/src/main.ts +0 -40
- package/src/memory-buffer.ts +0 -666
- package/src/memory-heap.ts +0 -206
- package/src/serialize-object.ts +0 -95
- package/src/shared-list.ts +0 -339
- package/src/shared-map.ts +0 -252
- package/src/shared-pointer-list.ts +0 -80
- package/src/shared-string.ts +0 -144
- package/src/shared-vector.ts +0 -236
- package/src/utils/16-from-32-array.ts +0 -23
- package/src/utils/16-from-64-array.ts +0 -18
- package/src/utils/float32-atomics.ts +0 -26
- package/src/utils/pointer.ts +0 -40
- package/src/utils/typedarray.js +0 -162
- package/src/vite-env.d.ts +0 -1
- /package/{src → dist/src}/utils/typedarray.d.ts +0 -0
package/src/memory-heap.ts
DELETED
|
@@ -1,206 +0,0 @@
|
|
|
1
|
-
import AllocatedMemory, { type SharedAllocatedMemory } from './allocated-memory';
|
|
2
|
-
import prettyBytes from 'pretty-bytes';
|
|
3
|
-
import { MAX_BYTE_OFFSET_LENGTH, MAX_POSITION_LENGTH } from './utils/pointer';
|
|
4
|
-
import MemoryBuffer from './memory-buffer';
|
|
5
|
-
|
|
6
|
-
const DEFAULT_BUFFER_SIZE = 8_192;
|
|
7
|
-
const BUFFER_SIZE_INDEX = 0;
|
|
8
|
-
const BUFFER_COUNT_INDEX = 1;
|
|
9
|
-
const BUFFER_AUTO_GROW_INDEX = 2;
|
|
10
|
-
export default class MemoryHeap {
|
|
11
|
-
buffers: Array<MemoryBuffer>;
|
|
12
|
-
private onGrowBufferHandlers: Array<OnGrowBuffer> = [];
|
|
13
|
-
isClone: boolean;
|
|
14
|
-
private memory: AllocatedMemory;
|
|
15
|
-
|
|
16
|
-
get bufferSize() {
|
|
17
|
-
return this.memory.data[BUFFER_SIZE_INDEX];
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
constructor(config?: MemoryHeapConfig | MemoryHeapMemory) {
|
|
21
|
-
if(config && 'buffers' in config) {
|
|
22
|
-
this.buffers = config.buffers.map(buffer => {
|
|
23
|
-
return new MemoryBuffer({
|
|
24
|
-
buf: buffer,
|
|
25
|
-
skipInitialization: true
|
|
26
|
-
});
|
|
27
|
-
});
|
|
28
|
-
|
|
29
|
-
// TODO: This should be programic instead of hoping the first allocation is always byte 40
|
|
30
|
-
this.memory = new AllocatedMemory(this, {
|
|
31
|
-
bufferPosition: 0,
|
|
32
|
-
bufferByteOffset: 40
|
|
33
|
-
});
|
|
34
|
-
this.isClone = true;
|
|
35
|
-
} else {
|
|
36
|
-
if(!('SharedArrayBuffer' in globalThis)) {
|
|
37
|
-
console.warn('SharedArrayBuffer is not working: falling back to ArrayBuffer');
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
const bufferSize = config?.bufferSize ?? DEFAULT_BUFFER_SIZE;
|
|
41
|
-
if(bufferSize > MAX_BYTE_OFFSET_LENGTH) {
|
|
42
|
-
throw new Error(`Buffer size ${bufferSize} is greater than max ${MAX_BYTE_OFFSET_LENGTH} that we can reference with pointers`);
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
let startBuffer = this.createBuffer(bufferSize);
|
|
46
|
-
this.buffers = [
|
|
47
|
-
startBuffer
|
|
48
|
-
];
|
|
49
|
-
const data = startBuffer.callocAs('u32', 3);
|
|
50
|
-
if(data) {
|
|
51
|
-
this.memory = new AllocatedMemory(this, {
|
|
52
|
-
bufferPosition: 0,
|
|
53
|
-
bufferByteOffset: data.byteOffset
|
|
54
|
-
});
|
|
55
|
-
} else {
|
|
56
|
-
throw new Error('Failed to initialize first byte from buffer');
|
|
57
|
-
}
|
|
58
|
-
this.memory.data[BUFFER_SIZE_INDEX] = bufferSize;
|
|
59
|
-
this.memory.data[BUFFER_COUNT_INDEX] = 1;
|
|
60
|
-
this.memory.data[BUFFER_AUTO_GROW_INDEX] = config?.autoGrowSize ?? 100;
|
|
61
|
-
this.isClone = false;
|
|
62
|
-
|
|
63
|
-
for(let i = 1; i < (config?.initialBuffers ?? 1); i++) {
|
|
64
|
-
this.buffers.push(this.createBuffer(bufferSize));
|
|
65
|
-
}
|
|
66
|
-
}
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
addSharedBuffer(data: GrowBufferData) {
|
|
70
|
-
this.buffers[data.bufferPosition] = new MemoryBuffer({
|
|
71
|
-
buf: data.buffer,
|
|
72
|
-
skipInitialization: true
|
|
73
|
-
});
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
private growBuffer() {
|
|
77
|
-
const buffer = this.createBuffer();
|
|
78
|
-
let nextBufferPosition = Atomics.add(this.memory.data, BUFFER_COUNT_INDEX, 1);
|
|
79
|
-
// Setting index set by internal Atomic count so we can create new buffers from multiple threads and keep position consistent
|
|
80
|
-
this.buffers[nextBufferPosition] = buffer;
|
|
81
|
-
this.onGrowBufferHandlers.forEach(handler => handler({
|
|
82
|
-
bufferPosition: nextBufferPosition,
|
|
83
|
-
buffer: buffer.buf as SharedArrayBuffer
|
|
84
|
-
}));
|
|
85
|
-
|
|
86
|
-
return buffer;
|
|
87
|
-
}
|
|
88
|
-
private createBuffer(bufferSize?: number): MemoryBuffer {
|
|
89
|
-
const usedBufferSize = bufferSize ?? this.bufferSize;
|
|
90
|
-
let buf: ArrayBuffer | SharedArrayBuffer;
|
|
91
|
-
if('SharedArrayBuffer' in globalThis) {
|
|
92
|
-
buf = new SharedArrayBuffer(usedBufferSize);
|
|
93
|
-
} else {
|
|
94
|
-
buf = new ArrayBuffer(usedBufferSize);
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
return new MemoryBuffer({
|
|
98
|
-
buf,
|
|
99
|
-
|
|
100
|
-
// We can't use this unless we can 100% guarantee that every thread will stop using memory the instant it is freed
|
|
101
|
-
// ex: Allocate 16 bytes. Thread A frees that allocation and then allocates 12 bytes and 4 bytes, but Thread B is mid-execution on the old allocation can changes the internal state of the 4-byte allocation breaking everything
|
|
102
|
-
// After the internal state is wrong MemoryBuffer will loose track of which blocks are where and how big they are
|
|
103
|
-
compact: false,
|
|
104
|
-
split: false
|
|
105
|
-
});
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
addOnGrowBufferHandlers(handler: OnGrowBuffer) {
|
|
109
|
-
this.onGrowBufferHandlers.push(handler);
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
allocUI32(count: number): AllocatedMemory {
|
|
113
|
-
count = Math.ceil(count);
|
|
114
|
-
for(let i = 0; i < this.buffers.length; i++) {
|
|
115
|
-
const buffer = this.buffers[i];
|
|
116
|
-
// Should just mean we haven't synced this buffer from another thread yet
|
|
117
|
-
if(!buffer) {
|
|
118
|
-
continue;
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
// Should be fine to initialize all values as 0s since unsigned/signed ints and floats all store 0 as all 0s
|
|
122
|
-
const data = buffer.callocAs('u32', count);
|
|
123
|
-
if(data) {
|
|
124
|
-
// Auto grow when nearly full when we need buffer to already be sync'd between threads BEFORE we try to use it
|
|
125
|
-
if(i === (this.buffers.length - 1) && Atomics.load(this.memory.data, BUFFER_COUNT_INDEX) === this.buffers.length && this.memory.data[BUFFER_AUTO_GROW_INDEX] < 100 && this.memory.data[BUFFER_AUTO_GROW_INDEX] > 0) {
|
|
126
|
-
const percentFull = buffer.top / buffer.end;
|
|
127
|
-
if(percentFull > (this.memory.data[BUFFER_AUTO_GROW_INDEX] / 100)) {
|
|
128
|
-
this.growBuffer();
|
|
129
|
-
}
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
return new AllocatedMemory(this, {
|
|
133
|
-
data,
|
|
134
|
-
buffer
|
|
135
|
-
});
|
|
136
|
-
}
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
if(this.buffers.length >= MAX_POSITION_LENGTH) {
|
|
140
|
-
throw new Error(`Can't initialize a new buffer since it would have a position greater than the max of ${MAX_POSITION_LENGTH}`);
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
// If we get here we need to grow another buffer to continue allocating new memory
|
|
144
|
-
let buffer = this.growBuffer();
|
|
145
|
-
const data = buffer.callocAs('u32', count);
|
|
146
|
-
if(data) {
|
|
147
|
-
return new AllocatedMemory(this, {
|
|
148
|
-
data,
|
|
149
|
-
buffer
|
|
150
|
-
});
|
|
151
|
-
} else {
|
|
152
|
-
throw new Error(`Unable to allocate ${count} numbers even after adding a new buffer`);
|
|
153
|
-
}
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
getSharedAlloc(shared: SharedAllocatedMemory): AllocatedMemory | undefined {
|
|
157
|
-
// Should just mean it hasn't synced to this thread yet
|
|
158
|
-
if(this.buffers[shared.bufferPosition] === undefined) {
|
|
159
|
-
return undefined;
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
return new AllocatedMemory(this, shared);
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
get currentUsed() {
|
|
166
|
-
return this.totalAllocated - this.buffers.reduce((total, memPool) => total + memPool.stats().available, 0);
|
|
167
|
-
}
|
|
168
|
-
get totalAllocated() {
|
|
169
|
-
return this.buffers[0].buf.byteLength * this.buffers.length;
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
prettyMemory() {
|
|
173
|
-
return `${myPrettyBytes(this.currentUsed)} / ${myPrettyBytes(this.totalAllocated)}`;
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
getSharedMemory(): MemoryHeapMemory {
|
|
177
|
-
return {
|
|
178
|
-
buffers: this.buffers.map(buffer => buffer.buf as SharedArrayBuffer)
|
|
179
|
-
};
|
|
180
|
-
}
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
function myPrettyBytes(bytes: number) {
|
|
184
|
-
return prettyBytes(bytes, {
|
|
185
|
-
binary: true,
|
|
186
|
-
minimumFractionDigits: 1,
|
|
187
|
-
maximumFractionDigits: 1
|
|
188
|
-
});
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
type OnGrowBuffer = (newBuffer: GrowBufferData) => void;
|
|
192
|
-
interface GrowBufferData {
|
|
193
|
-
bufferPosition: number
|
|
194
|
-
buffer: SharedArrayBuffer
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
interface MemoryHeapConfig {
|
|
198
|
-
bufferSize?: number
|
|
199
|
-
initialBuffers?: number
|
|
200
|
-
autoGrowSize?: number
|
|
201
|
-
}
|
|
202
|
-
interface MemoryHeapMemory {
|
|
203
|
-
buffers: Array<SharedArrayBuffer>
|
|
204
|
-
}
|
|
205
|
-
|
|
206
|
-
export type { MemoryHeapConfig, MemoryHeapMemory, GrowBufferData };
|
package/src/serialize-object.ts
DELETED
|
@@ -1,95 +0,0 @@
|
|
|
1
|
-
import AllocatedMemory from './allocated-memory';
|
|
2
|
-
import type MemoryHeap from './memory-heap';
|
|
3
|
-
import { getPointer } from './utils/pointer';
|
|
4
|
-
|
|
5
|
-
enum VALUE_TYPE {
|
|
6
|
-
UNDEFINED,
|
|
7
|
-
NUMBER,
|
|
8
|
-
STRING
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
// Be able to serialize a simple object into memory and re-create the object in another thread
|
|
12
|
-
// This does NOT support updating objects across threads
|
|
13
|
-
const MAGIC_NUMBER = 52361700;
|
|
14
|
-
export function serializeObjectToMemory<T extends object>(heap: MemoryHeap, object: T): AllocatedMemory {
|
|
15
|
-
const data: Array<number> = [];
|
|
16
|
-
const keys = Object.keys(object);
|
|
17
|
-
data.push(MAGIC_NUMBER);
|
|
18
|
-
data.push(keys.length);
|
|
19
|
-
|
|
20
|
-
// Create index first so we can quickly construct index
|
|
21
|
-
let keyIndexes: { [key:string]:number } = {};
|
|
22
|
-
keys.forEach(key => {
|
|
23
|
-
data.push(key.length);
|
|
24
|
-
addCharCodes(data, key);
|
|
25
|
-
|
|
26
|
-
keyIndexes[key] = data.length;
|
|
27
|
-
data.push(0);
|
|
28
|
-
});
|
|
29
|
-
|
|
30
|
-
keys.forEach(key => {
|
|
31
|
-
let keyIndex = keyIndexes[key];
|
|
32
|
-
// @ts-expect-error
|
|
33
|
-
let value = object[key];
|
|
34
|
-
if(Number.isFinite(value)) {
|
|
35
|
-
data[keyIndex] = data.length;
|
|
36
|
-
data.push(VALUE_TYPE.NUMBER);
|
|
37
|
-
data.push(value);
|
|
38
|
-
} else if(typeof value === 'string') {
|
|
39
|
-
data[keyIndex] = data.length;
|
|
40
|
-
data.push(VALUE_TYPE.STRING);
|
|
41
|
-
data.push(value.length);
|
|
42
|
-
addCharCodes(data, value);
|
|
43
|
-
}
|
|
44
|
-
});
|
|
45
|
-
|
|
46
|
-
let memory = heap.allocUI32(data.length);
|
|
47
|
-
let sharedData = memory.getArray(Float32Array, 0, data.length);
|
|
48
|
-
sharedData.set(data);
|
|
49
|
-
|
|
50
|
-
return memory;
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
export function createObjectFromPointer<T extends object>(heap: MemoryHeap, pointer: number) {
|
|
54
|
-
let memory = new AllocatedMemory(heap, getPointer(pointer));
|
|
55
|
-
return createObjectFromMemory<T>(memory);
|
|
56
|
-
}
|
|
57
|
-
export function createObjectFromMemory<T extends object>(memory: AllocatedMemory): T {
|
|
58
|
-
let sharedData = new Float32Array(memory.data.buffer, memory.bufferByteOffset);
|
|
59
|
-
if(sharedData[0] !== MAGIC_NUMBER) {
|
|
60
|
-
throw new Error('Trying to create object from invalid memory location');
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
const sharedObject: { [key:string]:any } = {};
|
|
64
|
-
const keyCount = sharedData[1];
|
|
65
|
-
let keyStartIndex = 2;
|
|
66
|
-
for(let i = 0; i < keyCount; i++) {
|
|
67
|
-
let keyLength = sharedData[keyStartIndex];
|
|
68
|
-
let keyData = new Float32Array(memory.data.buffer, memory.bufferByteOffset + (keyStartIndex + 1) * sharedData.BYTES_PER_ELEMENT, keyLength);
|
|
69
|
-
let key = String.fromCharCode.apply(null, [...keyData]);
|
|
70
|
-
let valueIndex = sharedData[keyStartIndex + keyLength + 1];
|
|
71
|
-
let valueType = sharedData[valueIndex];
|
|
72
|
-
switch(valueType) {
|
|
73
|
-
case VALUE_TYPE.NUMBER: {
|
|
74
|
-
sharedObject[key] = sharedData[valueIndex + 1];
|
|
75
|
-
break;
|
|
76
|
-
}
|
|
77
|
-
case VALUE_TYPE.STRING: {
|
|
78
|
-
let length = sharedData[valueIndex + 1];
|
|
79
|
-
let valueData = new Float32Array(memory.data.buffer, memory.bufferByteOffset + (valueIndex + 2) * sharedData.BYTES_PER_ELEMENT, length);
|
|
80
|
-
sharedObject[key] = String.fromCharCode.apply(null, [...valueData]);
|
|
81
|
-
break;
|
|
82
|
-
}
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
keyStartIndex += 2 + keyLength;
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
return sharedObject as T;
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
function addCharCodes(data: Array<number>, value: string) {
|
|
92
|
-
for(let i = 0; i < value.length; i++) {
|
|
93
|
-
data.push(value.charCodeAt(i));
|
|
94
|
-
}
|
|
95
|
-
}
|
package/src/shared-list.ts
DELETED
|
@@ -1,339 +0,0 @@
|
|
|
1
|
-
import type { SharedAllocatedMemory } from './allocated-memory';
|
|
2
|
-
import AllocatedMemory from './allocated-memory';
|
|
3
|
-
import type { TypedArrayConstructor } from './interfaces/typed-array-constructor';
|
|
4
|
-
import type MemoryHeap from './memory-heap';
|
|
5
|
-
import { getPointer, loadPointer, loadRawPointer, replaceRawPointer, storePointer, storeRawPointer } from './utils/pointer';
|
|
6
|
-
|
|
7
|
-
enum TYPE {
|
|
8
|
-
uint32,
|
|
9
|
-
int32,
|
|
10
|
-
float32
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
// TODO: We need some sort of locking on insert/deletes!
|
|
14
|
-
const FIRST_BLOCK_RECORD_KEEPING_COUNT = 4;
|
|
15
|
-
const DATA_BLOCK_RECORD_KEEPING_COUNT = 1;
|
|
16
|
-
const LENGTH_INDEX = 2;
|
|
17
|
-
export default class SharedList<T extends Uint32Array | Int32Array | Float32Array = Uint32Array> implements Iterable<SharedListIterable<T>> {
|
|
18
|
-
static readonly ALLOCATE_COUNT = FIRST_BLOCK_RECORD_KEEPING_COUNT;
|
|
19
|
-
|
|
20
|
-
private memory: MemoryHeap;
|
|
21
|
-
/* First block
|
|
22
|
-
32 index 0
|
|
23
|
-
uint16 0 - next buffer position
|
|
24
|
-
uint16 1 - next buffer index
|
|
25
|
-
32 index 1
|
|
26
|
-
uint16 2 - last buffer position
|
|
27
|
-
uint16 3 - last buffer index
|
|
28
|
-
32 index 2
|
|
29
|
-
uint32 4 - length
|
|
30
|
-
32 index 3
|
|
31
|
-
uint16 6 - type
|
|
32
|
-
uint16 7 - data length (defaults to 1 number per data)
|
|
33
|
-
*/
|
|
34
|
-
/* Other blocks
|
|
35
|
-
32 index 0
|
|
36
|
-
uint16 0 - next buffer position
|
|
37
|
-
uint16 1 - next buffer index
|
|
38
|
-
32 index 1 => data
|
|
39
|
-
*/
|
|
40
|
-
private firstBlock: AllocatedMemory;
|
|
41
|
-
private uint16Array: Uint16Array;
|
|
42
|
-
onDelete?: (data: T) => void;
|
|
43
|
-
|
|
44
|
-
get length(): number {
|
|
45
|
-
return Atomics.load(this.firstBlock.data, LENGTH_INDEX);
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
get type(): number {
|
|
49
|
-
return Atomics.load(this.uint16Array, 0);
|
|
50
|
-
}
|
|
51
|
-
private set type(value: number) {
|
|
52
|
-
Atomics.store(this.uint16Array, 0, value);
|
|
53
|
-
}
|
|
54
|
-
get dataLength(): number {
|
|
55
|
-
// Can technically be initialized by passing memory without actually every being called - need to make sure dataLength is always at least one
|
|
56
|
-
return Math.max(1, Atomics.load(this.uint16Array, 1));
|
|
57
|
-
}
|
|
58
|
-
private set dataLength(value: number) {
|
|
59
|
-
Atomics.store(this.uint16Array, 1, value);
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
constructor(memory: MemoryHeap, config?: SharedListConfig<T> | SharedListMemory) {
|
|
63
|
-
this.memory = memory;
|
|
64
|
-
|
|
65
|
-
if(config && 'firstBlock' in config) {
|
|
66
|
-
// TODO: How to handle referencing memory we don't have access to yet because buffer not synced from worker?
|
|
67
|
-
this.firstBlock = new AllocatedMemory(memory, config.firstBlock);
|
|
68
|
-
this.uint16Array = new Uint16Array(this.firstBlock.data.buffer, this.firstBlock.bufferByteOffset + (LENGTH_INDEX + 1) * Uint32Array.BYTES_PER_ELEMENT, 2);
|
|
69
|
-
} else {
|
|
70
|
-
if(config && config.initWithBlock) {
|
|
71
|
-
this.firstBlock = new AllocatedMemory(memory, config.initWithBlock);
|
|
72
|
-
} else {
|
|
73
|
-
this.firstBlock = memory.allocUI32(FIRST_BLOCK_RECORD_KEEPING_COUNT);
|
|
74
|
-
}
|
|
75
|
-
this.uint16Array = new Uint16Array(this.firstBlock.data.buffer, this.firstBlock.bufferByteOffset + (LENGTH_INDEX + 1) * Uint32Array.BYTES_PER_ELEMENT, 2);
|
|
76
|
-
|
|
77
|
-
const type = config?.type ?? Uint32Array;
|
|
78
|
-
if(type === Uint32Array) {
|
|
79
|
-
this.type = TYPE.uint32;
|
|
80
|
-
}
|
|
81
|
-
// @ts-expect-error
|
|
82
|
-
else if(type === Int32Array) {
|
|
83
|
-
this.type = TYPE.int32;
|
|
84
|
-
}
|
|
85
|
-
// @ts-expect-error
|
|
86
|
-
else if(type === Float32Array) {
|
|
87
|
-
this.type = TYPE.float32;
|
|
88
|
-
}
|
|
89
|
-
this.dataLength = config?.dataLength ?? 1;
|
|
90
|
-
}
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
insert(values: number | Array<number>) {
|
|
94
|
-
if(typeof values === 'number') {
|
|
95
|
-
values = [values];
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
let dataLength = this.dataLength;
|
|
99
|
-
if(values.length > dataLength) {
|
|
100
|
-
throw new Error(`Can't insert ${values.length} array into shared list of ${dataLength} dataLength`);
|
|
101
|
-
}
|
|
102
|
-
let newBlock = this.memory.allocUI32(DATA_BLOCK_RECORD_KEEPING_COUNT + dataLength);
|
|
103
|
-
let newData = this.getDataBlock(newBlock.data);
|
|
104
|
-
let newBlockPointer = newBlock.pointer;
|
|
105
|
-
|
|
106
|
-
for(let i = 0; i < values.length; i++) {
|
|
107
|
-
if(newData instanceof Int32Array || newData instanceof Uint32Array) {
|
|
108
|
-
Atomics.store(newData, i, values[i]);
|
|
109
|
-
} else {
|
|
110
|
-
// TODO: Should we replace with pass thru float32 conversion -> store?
|
|
111
|
-
newData[i] = values[i];
|
|
112
|
-
}
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
let lastBlockPointer;
|
|
116
|
-
let updateWorked = false;
|
|
117
|
-
while(!updateWorked) {
|
|
118
|
-
lastBlockPointer = loadRawPointer(this.firstBlock.data, 1);
|
|
119
|
-
updateWorked = replaceRawPointer(this.firstBlock.data, 1, newBlockPointer, lastBlockPointer);
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
if(lastBlockPointer) {
|
|
123
|
-
let { bufferPosition: lastBlockPosition, bufferByteOffset: lastBlockByteOffset } = getPointer(lastBlockPointer);
|
|
124
|
-
// TODO: How to handle referencing memory we don't have access to yet because buffer not synced from worker?
|
|
125
|
-
let lastBlock = new Uint32Array(this.memory.buffers[lastBlockPosition].buf, lastBlockByteOffset, 1);
|
|
126
|
-
storeRawPointer(lastBlock, 0, newBlockPointer);
|
|
127
|
-
} else {
|
|
128
|
-
// First item - store on first block
|
|
129
|
-
storeRawPointer(this.firstBlock.data, 0, newBlockPointer);
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
// Always update new last buffer position and length
|
|
133
|
-
Atomics.add(this.firstBlock.data, LENGTH_INDEX, 1);
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
deleteMatch(callback: (values: T, index: number) => boolean): boolean {
|
|
137
|
-
for(let { data, index, deleteCurrent } of this) {
|
|
138
|
-
if(callback(data, index)) {
|
|
139
|
-
deleteCurrent();
|
|
140
|
-
return true;
|
|
141
|
-
}
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
return false;
|
|
145
|
-
}
|
|
146
|
-
deleteIndex(deleteIndex: number): boolean {
|
|
147
|
-
if(deleteIndex >= this.length || deleteIndex < 0) {
|
|
148
|
-
return false;
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
return this.deleteMatch((values, index) => index === deleteIndex);
|
|
152
|
-
}
|
|
153
|
-
deleteValue(deleteValues: number | Array<number>) {
|
|
154
|
-
if(typeof deleteValues === 'number') {
|
|
155
|
-
return this.deleteMatch(values => values[0] === deleteValues);
|
|
156
|
-
} else {
|
|
157
|
-
return this.deleteMatch(values => {
|
|
158
|
-
if(values.length !== deleteValues.length) {
|
|
159
|
-
return false;
|
|
160
|
-
} else {
|
|
161
|
-
for(let i = 0; i < values.length; i++) {
|
|
162
|
-
if(values[i] !== deleteValues[i]) {
|
|
163
|
-
return false;
|
|
164
|
-
}
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
return true;
|
|
168
|
-
}
|
|
169
|
-
});
|
|
170
|
-
}
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
clear() {
|
|
174
|
-
let firstBlockPointer, lastBlockPointer;
|
|
175
|
-
let updateWorked = false;
|
|
176
|
-
while(!updateWorked) {
|
|
177
|
-
firstBlockPointer = loadRawPointer(this.firstBlock.data, 0);
|
|
178
|
-
lastBlockPointer = loadRawPointer(this.firstBlock.data, 1);
|
|
179
|
-
// Already cleared
|
|
180
|
-
if(!lastBlockPointer) {
|
|
181
|
-
return;
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
updateWorked = replaceRawPointer(this.firstBlock.data, 1, 0, lastBlockPointer);
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
// Shouldn't be possible to hit: making Typescript happy
|
|
188
|
-
if(!firstBlockPointer) {
|
|
189
|
-
return;
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
// We only want to update the last block if this is ran before something new was inserted
|
|
193
|
-
replaceRawPointer(this.firstBlock.data, 0, 0, firstBlockPointer);
|
|
194
|
-
|
|
195
|
-
// Iterate through inaccessible nodes and delete them
|
|
196
|
-
let deletedItems = 0;
|
|
197
|
-
let nextBlockPointer = firstBlockPointer;
|
|
198
|
-
while(nextBlockPointer) {
|
|
199
|
-
let { bufferPosition: nextBlockPosition, bufferByteOffset: nextBlockByteOffset } = getPointer(nextBlockPointer);
|
|
200
|
-
let memPool = this.memory.buffers[nextBlockPosition];
|
|
201
|
-
// Short circuit iterations if we can't access memory
|
|
202
|
-
if(!memPool) {
|
|
203
|
-
break;
|
|
204
|
-
}
|
|
205
|
-
|
|
206
|
-
let blockRecord = new Uint32Array(memPool.buf, nextBlockByteOffset, 2);
|
|
207
|
-
nextBlockPointer = loadRawPointer(blockRecord, 0);
|
|
208
|
-
deletedItems++;
|
|
209
|
-
|
|
210
|
-
if(this.onDelete) {
|
|
211
|
-
this.onDelete(this.getDataBlock(blockRecord));
|
|
212
|
-
}
|
|
213
|
-
|
|
214
|
-
memPool.free(blockRecord.byteOffset);
|
|
215
|
-
}
|
|
216
|
-
|
|
217
|
-
// Subtract by however many we deleted so that a insert during this operation is accurate
|
|
218
|
-
Atomics.sub(this.firstBlock.data, LENGTH_INDEX, deletedItems);
|
|
219
|
-
}
|
|
220
|
-
|
|
221
|
-
*[Symbol.iterator]() {
|
|
222
|
-
let currentIndex = 0;
|
|
223
|
-
let { bufferPosition: nextBlockPosition, bufferByteOffset: nextBlockByteOffset } = loadPointer(this.firstBlock.data, 0);
|
|
224
|
-
let lastBlockData = this.firstBlock.data;
|
|
225
|
-
let lastBlockPosition = 0;
|
|
226
|
-
let lastBlockByteOffset = 0;
|
|
227
|
-
while(nextBlockByteOffset) {
|
|
228
|
-
let memPool = this.memory.buffers[nextBlockPosition];
|
|
229
|
-
// Short circuit iterations if we can't access memory
|
|
230
|
-
if(!memPool) {
|
|
231
|
-
return;
|
|
232
|
-
}
|
|
233
|
-
|
|
234
|
-
let blockRecord = new Uint32Array(memPool.buf, nextBlockByteOffset, 2);
|
|
235
|
-
let blockData = this.getDataBlock(blockRecord);
|
|
236
|
-
|
|
237
|
-
let currentBlockPosition = nextBlockPosition;
|
|
238
|
-
let currentBlockByteOffset = nextBlockByteOffset;
|
|
239
|
-
({ bufferPosition: nextBlockPosition, bufferByteOffset: nextBlockByteOffset } = loadPointer(blockRecord, 0));
|
|
240
|
-
|
|
241
|
-
let updateLastBlock = true;
|
|
242
|
-
yield {
|
|
243
|
-
data: blockData,
|
|
244
|
-
index: currentIndex,
|
|
245
|
-
deleteCurrent: () => {
|
|
246
|
-
// Move previous index to point to one after
|
|
247
|
-
storePointer(lastBlockData, 0, nextBlockPosition, nextBlockByteOffset);
|
|
248
|
-
|
|
249
|
-
// If this is the last item, update last block to be previous location
|
|
250
|
-
if(!nextBlockByteOffset) {
|
|
251
|
-
storePointer(this.firstBlock.data, 1, lastBlockPosition, lastBlockByteOffset);
|
|
252
|
-
}
|
|
253
|
-
|
|
254
|
-
if(this.onDelete) {
|
|
255
|
-
this.onDelete(this.getDataBlock(blockRecord));
|
|
256
|
-
}
|
|
257
|
-
|
|
258
|
-
memPool.free(blockRecord.byteOffset);
|
|
259
|
-
Atomics.sub(this.firstBlock.data, LENGTH_INDEX, 1);
|
|
260
|
-
updateLastBlock = false;
|
|
261
|
-
}
|
|
262
|
-
};
|
|
263
|
-
|
|
264
|
-
if(updateLastBlock) {
|
|
265
|
-
lastBlockData = blockRecord;
|
|
266
|
-
lastBlockPosition = currentBlockPosition;
|
|
267
|
-
lastBlockByteOffset = currentBlockByteOffset;
|
|
268
|
-
currentIndex++;
|
|
269
|
-
}
|
|
270
|
-
}
|
|
271
|
-
}
|
|
272
|
-
|
|
273
|
-
forEach(callback: (data: T) => void) {
|
|
274
|
-
for(let value of this) {
|
|
275
|
-
callback(value.data);
|
|
276
|
-
}
|
|
277
|
-
}
|
|
278
|
-
|
|
279
|
-
getSharedMemory(): SharedListMemory {
|
|
280
|
-
return {
|
|
281
|
-
firstBlock: this.firstBlock.getSharedMemory()
|
|
282
|
-
};
|
|
283
|
-
}
|
|
284
|
-
|
|
285
|
-
private getDataBlock(memory: Uint32Array): T {
|
|
286
|
-
const startIndex = memory.byteOffset + DATA_BLOCK_RECORD_KEEPING_COUNT * memory.BYTES_PER_ELEMENT;
|
|
287
|
-
|
|
288
|
-
switch(this.type) {
|
|
289
|
-
case TYPE.int32:
|
|
290
|
-
// @ts-expect-error
|
|
291
|
-
return new Int32Array(memory.buffer, startIndex, this.dataLength);
|
|
292
|
-
case TYPE.uint32:
|
|
293
|
-
// @ts-expect-error
|
|
294
|
-
return new Uint32Array(memory.buffer, startIndex, this.dataLength);
|
|
295
|
-
case TYPE.float32:
|
|
296
|
-
// @ts-expect-error
|
|
297
|
-
return new Float32Array(memory.buffer, startIndex, this.dataLength);
|
|
298
|
-
default:
|
|
299
|
-
throw new Error(`Unknown data block type ${this.type}`);
|
|
300
|
-
}
|
|
301
|
-
}
|
|
302
|
-
|
|
303
|
-
free() {
|
|
304
|
-
let { bufferPosition: nextBlockPosition, bufferByteOffset: nextBlockByteOffset } = loadPointer(this.firstBlock.data, 0);
|
|
305
|
-
while(nextBlockByteOffset) {
|
|
306
|
-
let allocatedMemory = new AllocatedMemory(this.memory, {
|
|
307
|
-
bufferPosition: nextBlockPosition,
|
|
308
|
-
bufferByteOffset: nextBlockByteOffset
|
|
309
|
-
});
|
|
310
|
-
|
|
311
|
-
({ bufferPosition: nextBlockPosition, bufferByteOffset: nextBlockByteOffset } = loadPointer(allocatedMemory.data, 0));
|
|
312
|
-
|
|
313
|
-
if(this.onDelete) {
|
|
314
|
-
this.onDelete(this.getDataBlock(allocatedMemory.data));
|
|
315
|
-
}
|
|
316
|
-
|
|
317
|
-
allocatedMemory.free();
|
|
318
|
-
}
|
|
319
|
-
|
|
320
|
-
this.firstBlock.free();
|
|
321
|
-
}
|
|
322
|
-
}
|
|
323
|
-
|
|
324
|
-
interface SharedListConfig<T extends Uint32Array | Int32Array | Float32Array> {
|
|
325
|
-
initWithBlock?: SharedAllocatedMemory
|
|
326
|
-
type?: TypedArrayConstructor<T>
|
|
327
|
-
dataLength?: number
|
|
328
|
-
}
|
|
329
|
-
interface SharedListMemory {
|
|
330
|
-
firstBlock: SharedAllocatedMemory
|
|
331
|
-
}
|
|
332
|
-
|
|
333
|
-
interface SharedListIterable<T extends Uint32Array | Int32Array | Float32Array> {
|
|
334
|
-
data: T
|
|
335
|
-
index: number
|
|
336
|
-
deleteCurrent: () => void
|
|
337
|
-
}
|
|
338
|
-
|
|
339
|
-
export { type SharedListMemory };
|