@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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@daneren2005/shared-memory-objects",
3
- "version": "0.0.8",
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
  };
@@ -338,7 +338,7 @@ export default class MemoryBuffer {
338
338
  this.state[STATE_ALIGN] = x;
339
339
  }
340
340
 
341
- protected get end() {
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
- protected get top() {
349
+ get top() {
350
350
  return Atomics.load(this.state, STATE_TOP);
351
351
  }
352
352
 
@@ -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', 2);
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
- const buffer = this.createBuffer();
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>
@@ -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
 
@@ -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) {