@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.
Files changed (48) hide show
  1. package/README.md +82 -3
  2. package/dist/shared-memory-objects.js +1356 -0
  3. package/dist/shared-memory-objects.js.map +1 -0
  4. package/dist/shared-memory-objects.umd.cjs +2 -0
  5. package/dist/shared-memory-objects.umd.cjs.map +1 -0
  6. package/dist/src/allocated-memory.d.ts +26 -0
  7. package/dist/src/cached-item-list.d.ts +37 -0
  8. package/{src/interfaces/pow2.ts → dist/src/interfaces/pow2.d.ts} +2 -3
  9. package/dist/src/interfaces/typed-array-constructor.d.ts +5 -0
  10. package/{src/interfaces/typed-array.ts → dist/src/interfaces/typed-array.d.ts} +1 -1
  11. package/dist/src/lock/read-write-lock.d.ts +5 -0
  12. package/dist/src/lock/simple-lock.d.ts +3 -0
  13. package/dist/src/main.d.ts +18 -0
  14. package/dist/src/memory-buffer.d.ts +185 -0
  15. package/dist/src/memory-heap.d.ts +34 -0
  16. package/dist/src/serialize-object.d.ts +5 -0
  17. package/dist/src/shared-list.d.ts +44 -0
  18. package/dist/src/shared-map.d.ts +25 -0
  19. package/dist/src/shared-pointer-list.d.ts +21 -0
  20. package/dist/src/shared-pool.d.ts +39 -0
  21. package/dist/src/shared-string.d.ts +23 -0
  22. package/dist/src/shared-vector.d.ts +41 -0
  23. package/dist/src/utils/16-from-32-array.d.ts +4 -0
  24. package/dist/src/utils/16-from-64-array.d.ts +2 -0
  25. package/dist/src/utils/float32-atomics.d.ts +5 -0
  26. package/dist/src/utils/pointer.d.ts +19 -0
  27. package/package.json +34 -22
  28. package/src/allocated-memory.ts +0 -89
  29. package/src/cached-item-list.ts +0 -143
  30. package/src/interfaces/typed-array-constructor.ts +0 -6
  31. package/src/lock/read-write-lock.ts +0 -41
  32. package/src/lock/simple-lock.ts +0 -21
  33. package/src/main.ts +0 -40
  34. package/src/memory-buffer.ts +0 -666
  35. package/src/memory-heap.ts +0 -206
  36. package/src/serialize-object.ts +0 -95
  37. package/src/shared-list.ts +0 -339
  38. package/src/shared-map.ts +0 -252
  39. package/src/shared-pointer-list.ts +0 -80
  40. package/src/shared-string.ts +0 -144
  41. package/src/shared-vector.ts +0 -236
  42. package/src/utils/16-from-32-array.ts +0 -23
  43. package/src/utils/16-from-64-array.ts +0 -18
  44. package/src/utils/float32-atomics.ts +0 -26
  45. package/src/utils/pointer.ts +0 -40
  46. package/src/utils/typedarray.js +0 -162
  47. package/src/vite-env.d.ts +0 -1
  48. /package/{src → dist/src}/utils/typedarray.d.ts +0 -0
@@ -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 };
@@ -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
- }
@@ -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 };