@daneren2005/shared-memory-objects 0.0.0 → 0.0.1

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.
@@ -0,0 +1,75 @@
1
+ import MemoryHeap from '../memory-heap';
2
+ import SharedString from '../shared-string';
3
+
4
+ describe('SharedString', () => {
5
+ it('can create', () => {
6
+ let memory = new MemoryHeap();
7
+ let string = new SharedString(memory, 'Test');
8
+
9
+ expect(string.value).toEqual('Test');
10
+ });
11
+
12
+ it('utf16 characters work', () => {
13
+ let memory = new MemoryHeap();
14
+ let string = new SharedString(memory, 'Teǭst');
15
+
16
+ expect(string.value).toEqual('Teǭst');
17
+ });
18
+
19
+ it('can initialize from memory without the string allocated yet', () => {
20
+ let memory = new MemoryHeap();
21
+ let allocatedMemory = memory.allocUI32(SharedString.ALLOCATE_COUNT);
22
+ let string = new SharedString(memory, {
23
+ ...allocatedMemory.getSharedMemory(),
24
+ value: 'Test'
25
+ });
26
+
27
+ expect(string.value).toEqual('Test');
28
+ });
29
+ it('can work from uninitialized memory', () => {
30
+ let memory = new MemoryHeap();
31
+ let allocatedMemory = memory.allocUI32(SharedString.ALLOCATE_COUNT);
32
+ let string = new SharedString(memory, allocatedMemory.getSharedMemory());
33
+
34
+ expect(string.value).toEqual('');
35
+ });
36
+
37
+ it('can be set to new memory strings', () => {
38
+ let memory = new MemoryHeap();
39
+ let string = new SharedString(memory, 'Tests again22222222222222222222');
40
+ let startMemory = memory.currentUsed;
41
+ let copy = new SharedString(memory, string.getSharedMemory());
42
+
43
+ expect(copy.value).toEqual('Tests again22222222222222222222');
44
+
45
+ copy.value = 'Blows again22222222222222222222';
46
+ expect(string.value).toEqual('Blows again22222222222222222222');
47
+ expect(memory.currentUsed).toEqual(startMemory);
48
+
49
+ string.value = 'Yo';
50
+ expect(string.value).toEqual('Yo');
51
+ expect(copy.value).toEqual('Yo');
52
+ expect(memory.currentUsed).toBeLessThan(startMemory);
53
+
54
+ string.value = 'Yo Mama Is So Fat2222222222222222222222222222222222222222';
55
+ expect(memory.currentUsed).toBeGreaterThan(startMemory);
56
+ });
57
+
58
+ it('can set to empty string', () => {
59
+ let memory = new MemoryHeap();
60
+ let string = new SharedString(memory, '');
61
+
62
+ expect(string.value).toEqual('');
63
+ });
64
+
65
+ it('free', () => {
66
+ let memory = new MemoryHeap();
67
+ let startMemory = memory.currentUsed;
68
+ let string = new SharedString(memory, 'Tests');
69
+ // No memory leak from changing values
70
+ string.value = 'Blob';
71
+ string.free();
72
+
73
+ expect(memory.currentUsed).toEqual(startMemory);
74
+ });
75
+ });
@@ -0,0 +1,163 @@
1
+ import MemoryHeap from '../memory-heap';
2
+ import SharedVector from '../shared-vector';
3
+
4
+ describe('SharedVector', () => {
5
+ let memory: MemoryHeap;
6
+ beforeEach(() => {
7
+ memory = new MemoryHeap({
8
+ bufferSize: 1024 * 16
9
+ });
10
+ });
11
+
12
+ it('insert into vector', () => {
13
+ let vector = new SharedVector(memory, {
14
+ type: Uint32Array
15
+ });
16
+
17
+ vector.push(10);
18
+ vector.push(52);
19
+ vector.push(4);
20
+
21
+ expect(vector.length).toEqual(3);
22
+ expect(flat(vector)).toEqual([10, 52, 4]);
23
+ });
24
+
25
+ it('continually grows memory as needed', () => {
26
+ let vector = new SharedVector(memory, {
27
+ type: Uint32Array
28
+ });
29
+
30
+ const expectedValues = [];
31
+ for(let i = 0; i < 1_000; i++) {
32
+ vector.push(i);
33
+ expectedValues.push(i);
34
+ }
35
+
36
+ expect(vector.length).toEqual(1_000);
37
+ expect(flat(vector)).toEqual(expectedValues);
38
+ });
39
+
40
+ it('pop', () => {
41
+ let vector = new SharedVector(memory);
42
+
43
+ vector.push(10);
44
+ vector.push(52);
45
+ vector.push(4);
46
+ vector.push(8);
47
+
48
+ expect([...vector.pop()]).toEqual([8]);
49
+ expect(vector.length).toEqual(3);
50
+ expect([...vector.pop()]).toEqual([4]);
51
+ expect(vector.length).toEqual(2);
52
+ expect(flat(vector)).toEqual([10, 52]);
53
+ });
54
+
55
+ it('deleteIndex', () => {
56
+ let vector = new SharedVector(memory);
57
+
58
+ vector.push(10);
59
+ vector.push(52);
60
+ vector.push(4);
61
+ vector.push(8);
62
+
63
+ vector.deleteIndex(3);
64
+ expect(vector.length).toEqual(3);
65
+ expect(flat(vector)).toEqual([10, 52, 4]);
66
+
67
+ vector.deleteIndex(1);
68
+ expect(vector.length).toEqual(2);
69
+ expect(flat(vector)).toEqual([10, 4]);
70
+
71
+ vector.deleteIndex(0);
72
+ expect(vector.length).toEqual(1);
73
+ expect(flat(vector)).toEqual([4]);
74
+ });
75
+
76
+ it('with dataLength: 3', () => {
77
+ let vector = new SharedVector(memory, {
78
+ type: Uint32Array,
79
+ dataLength: 3
80
+ });
81
+
82
+ vector.push(10);
83
+ vector.push([52, 32, 6]);
84
+ vector.push([40, 41, 42]);
85
+
86
+ expect(vector.length).toEqual(3);
87
+ expect(flat(vector)).toEqual([
88
+ 10, 0, 0,
89
+ 52, 32, 6,
90
+ 40, 41, 42
91
+ ]);
92
+
93
+ expect([...vector.pop()]).toEqual([40, 41, 42]);
94
+ expect(vector.length).toEqual(2);
95
+ expect(flat(vector)).toEqual([
96
+ 10, 0, 0,
97
+ 52, 32, 6
98
+ ]);
99
+
100
+ vector.deleteIndex(0);
101
+ expect(vector.length).toEqual(1);
102
+ expect(flat(vector)).toEqual([
103
+ 52, 32, 6
104
+ ]);
105
+ });
106
+
107
+ it('can work from memory', () => {
108
+ let mainVector = new SharedVector(memory, {
109
+ type: Uint32Array,
110
+ dataLength: 3
111
+ });
112
+ let cloneVector = new SharedVector(memory, mainVector.getSharedMemory());
113
+
114
+ mainVector.push(10);
115
+ mainVector.push([52, 32, 6]);
116
+ mainVector.push([40, 41, 42]);
117
+
118
+ expect(mainVector.length).toEqual(3);
119
+ expect(flat(mainVector)).toEqual([
120
+ 10, 0, 0,
121
+ 52, 32, 6,
122
+ 40, 41, 42
123
+ ]);
124
+ expect(cloneVector.length).toEqual(3);
125
+ expect(flat(cloneVector)).toEqual([
126
+ 10, 0, 0,
127
+ 52, 32, 6,
128
+ 40, 41, 42
129
+ ]);
130
+
131
+ // Make sure growing works
132
+ for(let i = 0; i < 10; i++) {
133
+ cloneVector.push(i);
134
+ }
135
+ for(let i = 0; i < 10; i++) {
136
+ mainVector.push(i);
137
+ }
138
+ expect(mainVector.length).toEqual(23);
139
+ expect(cloneVector.length).toEqual(23);
140
+ });
141
+
142
+ it('free', () => {
143
+ let startMemory = memory.currentUsed;
144
+ let vector = new SharedVector(memory, {
145
+ type: Uint32Array
146
+ });
147
+
148
+ for(let i = 0; i < 1_000; i++) {
149
+ vector.push(i);
150
+ }
151
+
152
+ vector.free();
153
+ expect(memory.currentUsed).toEqual(startMemory);
154
+ });
155
+ });
156
+
157
+ function flat(list: SharedVector<any>) {
158
+ return [...list].reduce((array, value) => {
159
+ array.push(...value);
160
+
161
+ return array;
162
+ }, []);
163
+ }
@@ -0,0 +1,84 @@
1
+ import type { TypedArray } from './interfaces/typed-array';
2
+ import type { TypedArrayConstructor } from './interfaces/typed-array-constructor';
3
+ import type MemoryBuffer from './memory-buffer';
4
+ import type MemoryHeap from './memory-heap';
5
+ import { createPointer } from './utils/pointer';
6
+
7
+ export default class AllocatedMemory {
8
+ private readonly memory: MemoryHeap;
9
+
10
+ readonly bufferPosition: number;
11
+ get bufferByteOffset(): number {
12
+ return this.data.byteOffset;
13
+ }
14
+ get pointer(): number {
15
+ return createPointer(this.bufferPosition, this.bufferByteOffset);
16
+ }
17
+ private buffer: MemoryBuffer;
18
+ data: Uint32Array;
19
+
20
+ constructor(memory: MemoryHeap, config: AllocatedMemoryConfig | SharedAllocatedMemory) {
21
+ this.memory = memory;
22
+
23
+ if('buffer' in config) {
24
+ this.data = config.data;
25
+ this.buffer = config.buffer;
26
+ this.bufferPosition = this.memory.buffers.indexOf(config.buffer);
27
+ } else {
28
+ this.bufferPosition = config.bufferPosition;
29
+ this.buffer = memory.buffers[config.bufferPosition];
30
+
31
+ // Making sure these are the correct size is slow but in dev we want to make sure we aren't allowing to go out of bounds
32
+ if(import.meta.env.MODE === 'production') {
33
+ this.data = new Uint32Array(this.buffer.buf, config.bufferByteOffset);
34
+ } else {
35
+ this.data = new Uint32Array(this.buffer.buf, config.bufferByteOffset, this.buffer.lengthOf(config.bufferByteOffset));
36
+ }
37
+ }
38
+ }
39
+
40
+ getArray<T extends TypedArray>(type: TypedArrayConstructor<T>, offset: number, length: number): T {
41
+ if(import.meta.env.MODE === 'development') {
42
+ if(offset + length > this.data.length) {
43
+ console.warn(`Trying to grab more memory from AllocatedMemory.getArray then we have: ${offset} + ${length} > ${this.data.length}`);
44
+ }
45
+ }
46
+
47
+ return new type(this.data.buffer, this.data.byteOffset + offset * type.BYTES_PER_ELEMENT, length);
48
+ }
49
+ getArrayMemory(offset: number, length: number): SharedAllocatedMemory {
50
+ if(import.meta.env.MODE === 'development') {
51
+ if(offset + length > this.data.length) {
52
+ console.warn(`Trying to grab more memory from AllocatedMemory.getArrayMemory then we have: ${offset} + ${length} > ${this.data.length}`);
53
+ }
54
+ }
55
+
56
+ return {
57
+ bufferPosition: this.bufferPosition,
58
+ bufferByteOffset: this.bufferByteOffset + offset * this.data.BYTES_PER_ELEMENT
59
+ };
60
+ }
61
+
62
+ free() {
63
+ // NOTE: From worker thread you can't pass the array, you have to pass an explicit address to free
64
+ this.buffer.free(this.data.byteOffset);
65
+ }
66
+
67
+ getSharedMemory(): SharedAllocatedMemory {
68
+ return {
69
+ bufferPosition: this.bufferPosition,
70
+ bufferByteOffset: this.bufferByteOffset
71
+ };
72
+ }
73
+ }
74
+
75
+ interface AllocatedMemoryConfig {
76
+ data: Uint32Array
77
+ buffer: MemoryBuffer
78
+ }
79
+
80
+ interface SharedAllocatedMemory {
81
+ bufferPosition: number
82
+ bufferByteOffset: number
83
+ }
84
+ export type { SharedAllocatedMemory };
@@ -0,0 +1,3 @@
1
+ type Pow2 = 0x1 | 0x2 | 0x4 | 0x8 | 0x10 | 0x20 | 0x40 | 0x80 | 0x100 | 0x200 | 0x400 | 0x800 | 0x1000 | 0x2000 | 0x4000 | 0x8000 | 0x10000 | 0x20000 | 0x40000 | 0x80000 | 0x100000 | 0x200000 | 0x400000 | 0x800000 | 0x1000000 | 0x2000000 | 0x4000000 | 0x8000000 | 0x10000000 | 0x20000000 | 0x40000000 | 0x80000000;
2
+
3
+ export type { Pow2 };
@@ -0,0 +1,6 @@
1
+ interface TypedArrayConstructor<T> {
2
+ new(buffer: ArrayBufferLike, byteOffset: number, length: number): T
3
+ BYTES_PER_ELEMENT: number
4
+ }
5
+
6
+ export type { TypedArrayConstructor };
@@ -0,0 +1 @@
1
+ export type TypedArray = Float32Array | Float64Array | Int8Array | Int16Array | Int32Array | Uint8Array | Uint8ClampedArray | Uint16Array | Uint32Array;
@@ -0,0 +1,41 @@
1
+ const UNLOCKED = 0;
2
+ const READ_LOCKED = 1;
3
+ const WRITE_LOCKED = 2;
4
+
5
+ export function readLock(data: Int32Array, index: number = 0) {
6
+ // Wait over and over again until we get that it was unlocked or read locked
7
+ while(Atomics.compareExchange(data, index, UNLOCKED, READ_LOCKED) === WRITE_LOCKED) {
8
+ Atomics.wait(data, index, WRITE_LOCKED);
9
+ }
10
+
11
+ Atomics.add(data, index + 1, 1);
12
+ }
13
+ export function writeLock(data: Int32Array, index: number = 0) {
14
+ // Write lock needs to be exclusive - wait until we were in UNLOCKED to proceed
15
+ let oldValue = Atomics.compareExchange(data, index, UNLOCKED, WRITE_LOCKED);
16
+ while(oldValue !== UNLOCKED) {
17
+ Atomics.wait(data, index, oldValue);
18
+ oldValue = Atomics.compareExchange(data, index, UNLOCKED, WRITE_LOCKED);
19
+ }
20
+ }
21
+
22
+ export function readUnlock(data: Int32Array, index: number = 0) {
23
+ let readCount = Atomics.sub(data, index + 1, 1) - 1;
24
+
25
+ if(readCount <= 0) {
26
+ if(Atomics.compareExchange(data, index, READ_LOCKED, UNLOCKED) !== READ_LOCKED) {
27
+ console.warn('We are unlocking when it was not read locked!');
28
+ }
29
+
30
+ Atomics.notify(data, index);
31
+ }
32
+ }
33
+ export function writeUnlock(data: Int32Array, index: number = 0) {
34
+ if(Atomics.compareExchange(data, index, WRITE_LOCKED, UNLOCKED) !== WRITE_LOCKED) {
35
+ console.warn('We are unlocking when it was not write locked!');
36
+ }
37
+
38
+ Atomics.notify(data, index);
39
+ }
40
+
41
+ export const READ_WRITE_LOCK_ALLOCATE_COUNT = 2;
@@ -0,0 +1,21 @@
1
+ const UNLOCKED = 0;
2
+ const LOCKED = 1;
3
+ export function lock(data: Int32Array, index: number = 0) {
4
+ // Wait over and over again until we are one who set this from UNLOCKED to LOCKED
5
+ while(Atomics.compareExchange(data, index, UNLOCKED, LOCKED) !== UNLOCKED) {
6
+ if('WorkerGlobalScope' in self) {
7
+ Atomics.wait(data, index, LOCKED);
8
+ } else {
9
+ // TODO: Spin-locks suck....
10
+ }
11
+ }
12
+ }
13
+ export function unlock(data: Int32Array, index: number = 0) {
14
+ if(Atomics.compareExchange(data, index, LOCKED, UNLOCKED) !== LOCKED) {
15
+ console.warn('We are unlocking when it was not locked!');
16
+ }
17
+
18
+ Atomics.notify(data, index);
19
+ }
20
+
21
+ export const SIMPLE_LOCK_ALLOCATE_COUNT = 1;
package/src/main.ts ADDED
@@ -0,0 +1,35 @@
1
+ import AllocatedMemory, { type SharedAllocatedMemory } from './allocated-memory';
2
+ import MemoryBuffer from './memory-buffer';
3
+ import MemoryHeap, { type MemoryHeapMemory } from './memory-heap';
4
+
5
+ import SharedList, { type SharedListMemory } from './shared-list';
6
+ import SharedMap from './shared-map';
7
+ import SharedPointerList from './shared-pointer-list';
8
+ import SharedString from './shared-string';
9
+ import SharedVector from './shared-vector';
10
+
11
+ import type { TypedArrayConstructor } from './interfaces/typed-array-constructor';
12
+
13
+ export * from './utils/16-from-32-array';
14
+ export * from './utils/16-from-64-array';
15
+ export * from './utils/float32-atomics';
16
+ export * from './utils/pointer';
17
+ export * from './lock/simple-lock';
18
+ export * from './lock/read-write-lock';
19
+
20
+ export {
21
+ AllocatedMemory,
22
+ type SharedAllocatedMemory,
23
+ MemoryBuffer,
24
+ MemoryHeap,
25
+ type MemoryHeapMemory,
26
+
27
+ SharedList,
28
+ type SharedListMemory,
29
+ SharedMap,
30
+ SharedPointerList,
31
+ SharedString,
32
+ SharedVector,
33
+
34
+ type TypedArrayConstructor
35
+ };