@daneren2005/shared-memory-objects 0.0.8 → 0.0.10
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/package.json +1 -1
- package/src/cached-item-list.ts +143 -0
- package/src/main.ts +4 -1
- package/src/memory-buffer.ts +2 -2
- package/src/memory-heap.ts +25 -10
- package/src/shared-list.ts +58 -0
- package/src/shared-string.ts +4 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@daneren2005/shared-memory-objects",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.10",
|
|
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",
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
import type { SharedAllocatedMemory } from './allocated-memory';
|
|
2
|
+
import type MemoryHeap from './memory-heap';
|
|
3
|
+
import SharedList, { type SharedListMemory } from './shared-list';
|
|
4
|
+
|
|
5
|
+
export default abstract class CachedItemList<T extends Item> implements Iterable<{ item: T, deleteCurrent: () => void }> {
|
|
6
|
+
static readonly ALLOCATE_COUNT = SharedList.ALLOCATE_COUNT;
|
|
7
|
+
|
|
8
|
+
protected heap: MemoryHeap;
|
|
9
|
+
protected list: SharedList;
|
|
10
|
+
protected cache: Map<number, T> = new Map();
|
|
11
|
+
|
|
12
|
+
constructor(heap: MemoryHeap, config?: CachedListConfig | SharedListMemory) {
|
|
13
|
+
if(config) {
|
|
14
|
+
this.list = new SharedList(heap, config);
|
|
15
|
+
} else {
|
|
16
|
+
this.list = new SharedList(heap);
|
|
17
|
+
}
|
|
18
|
+
this.heap = heap;
|
|
19
|
+
|
|
20
|
+
this.list.onDelete = (pointerData: Uint32Array) => {
|
|
21
|
+
let pointer = Atomics.load(pointerData, 0);
|
|
22
|
+
if(pointer) {
|
|
23
|
+
let item = this.cache.get(pointer);
|
|
24
|
+
if(!item) {
|
|
25
|
+
item = this.initItem(pointer);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
if(item) {
|
|
29
|
+
item.free();
|
|
30
|
+
this.cache.delete(pointer);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
get length() {
|
|
37
|
+
return this.list.length;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
clear() {
|
|
41
|
+
this.list.clear();
|
|
42
|
+
this.cache.clear();
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
insert(item: T) {
|
|
46
|
+
this.list.insert(item.pointer);
|
|
47
|
+
this.cache.set(item.pointer, item);
|
|
48
|
+
}
|
|
49
|
+
delete(item: T) {
|
|
50
|
+
this.cache.delete(item.pointer);
|
|
51
|
+
return this.list.deleteValue(item.pointer);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
getByPointer(pointer: number): T | undefined {
|
|
55
|
+
let item = this.cache.get(pointer);
|
|
56
|
+
if(!item) {
|
|
57
|
+
item = this.initItem(pointer);
|
|
58
|
+
if(item) {
|
|
59
|
+
this.cache.set(pointer, item);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
return item;
|
|
64
|
+
}
|
|
65
|
+
protected abstract initItem(pointer: number): T | undefined
|
|
66
|
+
|
|
67
|
+
*[Symbol.iterator]() {
|
|
68
|
+
let iterator = this.list[Symbol.iterator]();
|
|
69
|
+
|
|
70
|
+
for(let { data: pointerData, deleteCurrent } of iterator) {
|
|
71
|
+
let pointer = Atomics.load(pointerData, 0);
|
|
72
|
+
if(!pointer) {
|
|
73
|
+
continue;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
let item = this.cache.get(pointer);
|
|
77
|
+
if(!item) {
|
|
78
|
+
item = this.initItem(pointer);
|
|
79
|
+
if(item) {
|
|
80
|
+
this.cache.set(pointer, item);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
if(item) {
|
|
85
|
+
yield {
|
|
86
|
+
item,
|
|
87
|
+
deleteCurrent
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
forEach(callback: (item: T) => void, filter?: (item: T) => boolean) {
|
|
93
|
+
for(let { item } of this) {
|
|
94
|
+
if(!filter || filter(item)) {
|
|
95
|
+
callback(item);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
find(callback: (item: T) => boolean): T | undefined {
|
|
101
|
+
for(let { item } of this) {
|
|
102
|
+
if(callback(item)) {
|
|
103
|
+
return item;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
filter(callback: (entity: T) => boolean): Array<T> {
|
|
108
|
+
let items = [];
|
|
109
|
+
for(let { item } of this) {
|
|
110
|
+
if(callback(item)) {
|
|
111
|
+
items.push(item);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
return items;
|
|
116
|
+
}
|
|
117
|
+
map<X>(callback: (item: T) => X): Array<X> {
|
|
118
|
+
const array: Array<X> = [];
|
|
119
|
+
for(let { item } of this) {
|
|
120
|
+
array.push(callback(item));
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
return array;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
getSharedMemory() {
|
|
127
|
+
return this.list.getSharedMemory();
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
free() {
|
|
131
|
+
this.list.free();
|
|
132
|
+
this.cache.clear();
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
export interface CachedListConfig {
|
|
137
|
+
initWithBlock: SharedAllocatedMemory
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
interface Item {
|
|
141
|
+
pointer: number
|
|
142
|
+
free: () => void
|
|
143
|
+
}
|
package/src/main.ts
CHANGED
|
@@ -7,6 +7,7 @@ import SharedMap, { type SharedMapMemory } from './shared-map';
|
|
|
7
7
|
import SharedPointerList from './shared-pointer-list';
|
|
8
8
|
import SharedString from './shared-string';
|
|
9
9
|
import SharedVector, { type SharedVectorMemory } from './shared-vector';
|
|
10
|
+
import CachedItemList, { type CachedListConfig } from './cached-item-list';
|
|
10
11
|
|
|
11
12
|
export * from './interfaces/typed-array';
|
|
12
13
|
export * from './interfaces/typed-array-constructor';
|
|
@@ -33,5 +34,7 @@ export {
|
|
|
33
34
|
SharedPointerList,
|
|
34
35
|
SharedString,
|
|
35
36
|
SharedVector,
|
|
36
|
-
type SharedVectorMemory
|
|
37
|
+
type SharedVectorMemory,
|
|
38
|
+
CachedItemList,
|
|
39
|
+
type CachedListConfig
|
|
37
40
|
};
|
package/src/memory-buffer.ts
CHANGED
|
@@ -338,7 +338,7 @@ export default class MemoryBuffer {
|
|
|
338
338
|
this.state[STATE_ALIGN] = x;
|
|
339
339
|
}
|
|
340
340
|
|
|
341
|
-
|
|
341
|
+
get end() {
|
|
342
342
|
return this.state[STATE_END];
|
|
343
343
|
}
|
|
344
344
|
|
|
@@ -346,7 +346,7 @@ export default class MemoryBuffer {
|
|
|
346
346
|
this.state[STATE_END] = x;
|
|
347
347
|
}
|
|
348
348
|
|
|
349
|
-
|
|
349
|
+
get top() {
|
|
350
350
|
return Atomics.load(this.state, STATE_TOP);
|
|
351
351
|
}
|
|
352
352
|
|
package/src/memory-heap.ts
CHANGED
|
@@ -6,6 +6,7 @@ import MemoryBuffer from './memory-buffer';
|
|
|
6
6
|
const DEFAULT_BUFFER_SIZE = 8_192;
|
|
7
7
|
const BUFFER_SIZE_INDEX = 0;
|
|
8
8
|
const BUFFER_COUNT_INDEX = 1;
|
|
9
|
+
const BUFFER_AUTO_GROW_INDEX = 2;
|
|
9
10
|
export default class MemoryHeap {
|
|
10
11
|
buffers: Array<MemoryBuffer>;
|
|
11
12
|
private onGrowBufferHandlers: Array<OnGrowBuffer> = [];
|
|
@@ -45,7 +46,7 @@ export default class MemoryHeap {
|
|
|
45
46
|
this.buffers = [
|
|
46
47
|
startBuffer
|
|
47
48
|
];
|
|
48
|
-
const data = startBuffer.callocAs('u32',
|
|
49
|
+
const data = startBuffer.callocAs('u32', 3);
|
|
49
50
|
if(data) {
|
|
50
51
|
this.memory = new AllocatedMemory(this, {
|
|
51
52
|
bufferPosition: 0,
|
|
@@ -56,6 +57,7 @@ export default class MemoryHeap {
|
|
|
56
57
|
}
|
|
57
58
|
this.memory.data[BUFFER_SIZE_INDEX] = bufferSize;
|
|
58
59
|
this.memory.data[BUFFER_COUNT_INDEX] = 1;
|
|
60
|
+
this.memory.data[BUFFER_AUTO_GROW_INDEX] = config?.autoGrowSize ?? 100;
|
|
59
61
|
this.isClone = false;
|
|
60
62
|
|
|
61
63
|
for(let i = 1; i < (config?.initialBuffers ?? 1); i++) {
|
|
@@ -71,6 +73,18 @@ export default class MemoryHeap {
|
|
|
71
73
|
});
|
|
72
74
|
}
|
|
73
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
|
+
}
|
|
74
88
|
private createBuffer(bufferSize?: number): MemoryBuffer {
|
|
75
89
|
const usedBufferSize = bufferSize ?? this.bufferSize;
|
|
76
90
|
let buf: ArrayBuffer | SharedArrayBuffer;
|
|
@@ -107,6 +121,14 @@ export default class MemoryHeap {
|
|
|
107
121
|
// Should be fine to initialize all values as 0s since unsigned/signed ints and floats all store 0 as all 0s
|
|
108
122
|
const data = buffer.callocAs('u32', count);
|
|
109
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
|
+
|
|
110
132
|
return new AllocatedMemory(this, {
|
|
111
133
|
data,
|
|
112
134
|
buffer
|
|
@@ -119,15 +141,7 @@ export default class MemoryHeap {
|
|
|
119
141
|
}
|
|
120
142
|
|
|
121
143
|
// If we get here we need to grow another buffer to continue allocating new memory
|
|
122
|
-
|
|
123
|
-
let nextBufferPosition = Atomics.add(this.memory.data, BUFFER_COUNT_INDEX, 1);
|
|
124
|
-
// Setting index set by internal Atomic count so we can create new buffers from multiple threads and keep position consistent
|
|
125
|
-
this.buffers[nextBufferPosition] = buffer;
|
|
126
|
-
this.onGrowBufferHandlers.forEach(handler => handler({
|
|
127
|
-
bufferPosition: nextBufferPosition,
|
|
128
|
-
buffer: buffer.buf as SharedArrayBuffer
|
|
129
|
-
}));
|
|
130
|
-
|
|
144
|
+
let buffer = this.growBuffer();
|
|
131
145
|
const data = buffer.callocAs('u32', count);
|
|
132
146
|
if(data) {
|
|
133
147
|
return new AllocatedMemory(this, {
|
|
@@ -183,6 +197,7 @@ interface GrowBufferData {
|
|
|
183
197
|
interface MemoryHeapConfig {
|
|
184
198
|
bufferSize?: number
|
|
185
199
|
initialBuffers?: number
|
|
200
|
+
autoGrowSize?: number
|
|
186
201
|
}
|
|
187
202
|
interface MemoryHeapMemory {
|
|
188
203
|
buffers: Array<SharedArrayBuffer>
|
package/src/shared-list.ts
CHANGED
|
@@ -39,6 +39,7 @@ export default class SharedList<T extends Uint32Array | Int32Array | Float32Arra
|
|
|
39
39
|
*/
|
|
40
40
|
private firstBlock: AllocatedMemory;
|
|
41
41
|
private uint16Array: Uint16Array;
|
|
42
|
+
onDelete?: (data: T) => void;
|
|
42
43
|
|
|
43
44
|
get length(): number {
|
|
44
45
|
return Atomics.load(this.firstBlock.data, LENGTH_INDEX);
|
|
@@ -169,6 +170,54 @@ export default class SharedList<T extends Uint32Array | Int32Array | Float32Arra
|
|
|
169
170
|
}
|
|
170
171
|
}
|
|
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
|
+
|
|
172
221
|
*[Symbol.iterator]() {
|
|
173
222
|
let currentIndex = 0;
|
|
174
223
|
let { bufferPosition: nextBlockPosition, bufferByteOffset: nextBlockByteOffset } = loadPointer(this.firstBlock.data, 0);
|
|
@@ -202,6 +251,10 @@ export default class SharedList<T extends Uint32Array | Int32Array | Float32Arra
|
|
|
202
251
|
storePointer(this.firstBlock.data, 1, lastBlockPosition, lastBlockByteOffset);
|
|
203
252
|
}
|
|
204
253
|
|
|
254
|
+
if(this.onDelete) {
|
|
255
|
+
this.onDelete(this.getDataBlock(blockRecord));
|
|
256
|
+
}
|
|
257
|
+
|
|
205
258
|
memPool.free(blockRecord.byteOffset);
|
|
206
259
|
Atomics.sub(this.firstBlock.data, LENGTH_INDEX, 1);
|
|
207
260
|
updateLastBlock = false;
|
|
@@ -256,6 +309,11 @@ export default class SharedList<T extends Uint32Array | Int32Array | Float32Arra
|
|
|
256
309
|
});
|
|
257
310
|
|
|
258
311
|
({ bufferPosition: nextBlockPosition, bufferByteOffset: nextBlockByteOffset } = loadPointer(allocatedMemory.data, 0));
|
|
312
|
+
|
|
313
|
+
if(this.onDelete) {
|
|
314
|
+
this.onDelete(this.getDataBlock(allocatedMemory.data));
|
|
315
|
+
}
|
|
316
|
+
|
|
259
317
|
allocatedMemory.free();
|
|
260
318
|
}
|
|
261
319
|
|
package/src/shared-string.ts
CHANGED
|
@@ -125,6 +125,10 @@ export default class SharedString {
|
|
|
125
125
|
return this.allocatedMemory.getSharedMemory();
|
|
126
126
|
}
|
|
127
127
|
|
|
128
|
+
get pointer() {
|
|
129
|
+
return this.allocatedMemory.pointer;
|
|
130
|
+
}
|
|
131
|
+
|
|
128
132
|
free() {
|
|
129
133
|
let { bufferPosition, bufferByteOffset } = loadPointer(this.allocatedMemory.data, POINTER_INDEX);
|
|
130
134
|
if(bufferByteOffset) {
|