@daneren2005/shared-memory-objects 1.2.0 → 1.3.0

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.
@@ -1 +1 @@
1
- {"version":3,"file":"shared-pool.js","names":[],"sources":["../src/shared-pool.ts"],"sourcesContent":["import type { SharedAllocatedMemory } from './allocated-memory';\nimport AllocatedMemory from './allocated-memory';\nimport type { TypedArrayConstructor } from './interfaces/typed-array-constructor';\nimport { lock, unlock } from './lock/simple-lock';\nimport type MemoryHeap from './memory-heap';\nimport SharedStack from './shared-stack';\nimport { getPointer, loadRawPointer, storeRawPointer } from './utils/pointer';\n\nenum TYPE {\n\tuint32,\n\tint32,\n\tfloat32,\n\tfloat64,\n}\n\nconst LENGTH_INDEX = 0;\nconst TYPE_INDEX = 1;\nconst MAX_CHUNK_SIZE_INDEX = 2;\n// Guards appending a chunk pointer so only one thread grows the pointer stack at a time\nconst GROW_LOCK_INDEX = 3;\n// Pointers to the two backing SharedStacks (their own firstBlocks): the chunk pointers and the recycled indexes\nconst POINTER_STACK_INDEX = 4;\nconst RECYCLE_STACK_INDEX = 5;\n\n// Array with stable indexes and maximum contiguous memory sizes (necessary to fit large data sets into max 1MB buffers).\n// The chunk pointers and the recycled indexes both live in SharedStacks, whose segments are never moved or freed once\n// published, so concurrent readers never touch memory that is being reclaimed.\n// https://plflib.org/colony.htm for future enhancements - it seems to be an optimized version of what we were aiming for with this\nexport default class SharedPool<T extends Uint32Array | Int32Array | Float32Array | Float64Array = Uint32Array> implements Iterable<T> {\n\tstatic readonly ALLOCATE_COUNT = 6;\n\tprivate memory: MemoryHeap;\n\n\t// Current Length, Type/DataLength, MaxChunkLength, Grow lock, Pointer stack, Recycle stack\n\tprivate firstBlock: AllocatedMemory;\n\tprivate uint16Array: Uint16Array;\n\tprivate growLock: Int32Array;\n\tprivate pointerStack: SharedStack;\n\tprivate recycleStack: SharedStack;\n\n\tprivate cachedFullDataBlock: { [key: number]: T } = {};\n\n\t// Config is written once at construction and never mutated, so it is cached in plain fields\n\tprivate readonly cachedMaxChunkSize: number;\n\tprivate readonly cachedDataLength: number;\n\tprivate readonly cachedType: number;\n\tprivate readonly cachedByteMultipler: number;\n\n\tget length(): number {\n\t\treturn Atomics.load(this.firstBlock.data, LENGTH_INDEX) - this.recycleStack.length;\n\t}\n\tget maxChunkSize(): number {\n\t\treturn this.cachedMaxChunkSize;\n\t}\n\tprivate set maxChunkSize(value: number) {\n\t\tAtomics.store(this.firstBlock.data, MAX_CHUNK_SIZE_INDEX, value);\n\t}\n\n\tget type(): number {\n\t\treturn this.cachedType;\n\t}\n\tprivate set type(value: number) {\n\t\tAtomics.store(this.uint16Array, 0, value);\n\t}\n\tget dataLength(): number {\n\t\treturn this.cachedDataLength;\n\t}\n\tprivate set dataLength(value: number) {\n\t\tAtomics.store(this.uint16Array, 1, value);\n\t}\n\n\tget bufferLength(): number {\n\t\treturn this.cachedMaxChunkSize * this.pointerStack.length;\n\t}\n\tget byteMultipler(): number {\n\t\treturn this.cachedByteMultipler;\n\t}\n\n\tconstructor(memory: MemoryHeap, config?: SharedPoolConfig<T> | SharedPoolMemory) {\n\t\tthis.memory = memory;\n\n\t\tif(config && 'firstBlock' in config) {\n\t\t\tthis.firstBlock = new AllocatedMemory(memory, config.firstBlock);\n\t\t\tthis.uint16Array = new Uint16Array(this.firstBlock.data.buffer, this.firstBlock.bufferByteOffset + TYPE_INDEX * Uint32Array.BYTES_PER_ELEMENT, 2);\n\t\t\tthis.pointerStack = new SharedStack(memory, {\n\t\t\t\tfirstBlock: getPointer(loadRawPointer(this.firstBlock.data, POINTER_STACK_INDEX)),\n\t\t\t});\n\t\t\tthis.recycleStack = new SharedStack(memory, {\n\t\t\t\tfirstBlock: getPointer(loadRawPointer(this.firstBlock.data, RECYCLE_STACK_INDEX)),\n\t\t\t});\n\t\t} else {\n\t\t\t// Inline both stacks' firstBlocks right after the pool header so the whole spine is one contiguous allocation\n\t\t\tthis.firstBlock = memory.allocUI32(SharedPool.ALLOCATE_COUNT + SharedStack.DEFAULT_ALLOCATE_COUNT * 2);\n\t\t\tthis.uint16Array = new Uint16Array(this.firstBlock.data.buffer, this.firstBlock.bufferByteOffset + TYPE_INDEX * Uint32Array.BYTES_PER_ELEMENT, 2);\n\n\t\t\tlet dataLength = config?.dataLength ?? 1;\n\t\t\tlet maxLength = config?.maxChunkSize ?? 100;\n\n\t\t\tthis.pointerStack = new SharedStack(memory, {\n\t\t\t\tfirstBlock: this.firstBlock.getSubAllocation(SharedPool.ALLOCATE_COUNT, SharedStack.DEFAULT_ALLOCATE_COUNT),\n\t\t\t\ttype: Uint32Array,\n\t\t\t});\n\t\t\tstoreRawPointer(this.firstBlock.data, POINTER_STACK_INDEX, this.pointerStack.pointer);\n\t\t\tthis.recycleStack = new SharedStack(memory, {\n\t\t\t\tfirstBlock: this.firstBlock.getSubAllocation(SharedPool.ALLOCATE_COUNT + SharedStack.DEFAULT_ALLOCATE_COUNT, SharedStack.DEFAULT_ALLOCATE_COUNT),\n\t\t\t\ttype: Uint32Array,\n\t\t\t});\n\t\t\tstoreRawPointer(this.firstBlock.data, RECYCLE_STACK_INDEX, this.recycleStack.pointer);\n\n\t\t\tconst type = config?.type ?? Uint32Array;\n\t\t\tif(type === Uint32Array) {\n\t\t\t\tthis.type = TYPE.uint32;\n\t\t\t// @ts-expect-error\n\t\t\t} else if(type === Int32Array) {\n\t\t\t\tthis.type = TYPE.int32;\n\t\t\t// @ts-expect-error\n\t\t\t} else if(type === Float32Array) {\n\t\t\t\tthis.type = TYPE.float32;\n\t\t\t// @ts-expect-error\n\t\t\t} else if(type === Float64Array) {\n\t\t\t\tthis.type = TYPE.float64;\n\t\t\t}\n\t\t\tthis.dataLength = dataLength;\n\t\t\tthis.maxChunkSize = maxLength;\n\n\t\t\t// @ts-expect-error\n\t\t\tlet byteMultipler = type === Float64Array ? 2 : 1;\n\t\t\tlet firstArray = memory.allocUI32(maxLength * dataLength * byteMultipler);\n\t\t\tthis.pointerStack.push(firstArray.pointer);\n\t\t}\n\n\t\tthis.cachedType = this.uint16Array[0];\n\t\tthis.cachedDataLength = Math.max(1, this.uint16Array[1]);\n\t\tthis.cachedMaxChunkSize = this.firstBlock.data[MAX_CHUNK_SIZE_INDEX];\n\t\tthis.cachedByteMultipler = this.cachedType === TYPE.float64 ? 2 : 1;\n\n\t\tthis.growLock = new Int32Array(this.firstBlock.data.buffer, this.firstBlock.bufferByteOffset + GROW_LOCK_INDEX * Uint32Array.BYTES_PER_ELEMENT, 1);\n\t}\n\n\tat(index: number): T {\n\t\tlet dataBlock = this.getFullDataBlock(index);\n\t\treturn this.getDataBlock(dataBlock, index % this.cachedMaxChunkSize);\n\t}\n\tget(index: number, dataIndex = 0): number {\n\t\tconst dataLength = this.cachedDataLength;\n\t\tif(dataIndex >= dataLength) {\n\t\t\tthrow new Error(`${dataIndex} is out of dataLength bounds ${dataLength}`);\n\t\t}\n\n\t\tlet dataBlock = this.getFullDataBlock(index);\n\t\treturn dataBlock[(index % this.cachedMaxChunkSize) * dataLength + dataIndex];\n\t}\n\n\tpush(values: number | Array<number>): number {\n\t\tlet dataLength = this.cachedDataLength;\n\t\tconst isSingleNumber = typeof values === 'number';\n\t\tif(!isSingleNumber && values.length > dataLength) {\n\t\t\tthrow new Error(`Can't insert ${values.length} array into shared list of ${dataLength} dataLength`);\n\t\t}\n\n\t\t// Reuse a recycled index if one is available. pop() can come back undefined when another thread drained the last\n\t\t// slot between our two calls, so fall through to a fresh index rather than trusting a length() read\n\t\tlet newIndex = this.recycleStack.pop();\n\t\tif(newIndex === undefined) {\n\t\t\tnewIndex = Atomics.add(this.firstBlock.data, LENGTH_INDEX, 1);\n\t\t}\n\n\t\tlet dataBlock = this.getFullDataBlock(newIndex);\n\t\tlet startIndex = dataLength * (newIndex % this.cachedMaxChunkSize);\n\t\tif(isSingleNumber) {\n\t\t\tdataBlock[startIndex] = values;\n\t\t} else {\n\t\t\tdataBlock.set(values, startIndex);\n\t\t}\n\n\t\treturn newIndex;\n\t}\n\n\tdeleteIndex(index: number) {\n\t\tthis.recycleStack.push(index);\n\t}\n\n\tclear() {\n\t\tthis.firstBlock.data[LENGTH_INDEX] = 0;\n\t\tthis.recycleStack.clear();\n\t}\n\n\t*[Symbol.iterator]() {\n\t\tconst recycledValues: { [key: number]: true } = {};\n\t\tfor(let value of this.recycleStack) {\n\t\t\trecycledValues[value] = true;\n\t\t}\n\n\t\tconst maxChunkSize = this.cachedMaxChunkSize;\n\t\t// Snapshot the length once - iteration over a concurrently mutated pool is a snapshot anyway, and re-loading the\n\t\t// atomic every step is pure overhead\n\t\tconst length = Atomics.load(this.firstBlock.data, LENGTH_INDEX);\n\t\tlet dataBlock = this.getFullDataBlock(0);\n\t\tlet dataBlockIndex = 0;\n\t\tfor(let i = 0; i < length; i++) {\n\t\t\tif(!recycledValues[i]) {\n\t\t\t\tlet newDataBlockIndex = Math.floor(i / maxChunkSize);\n\t\t\t\tif(newDataBlockIndex !== dataBlockIndex) {\n\t\t\t\t\tdataBlock = this.getFullDataBlock(i);\n\t\t\t\t\tdataBlockIndex = newDataBlockIndex;\n\t\t\t\t}\n\t\t\t\tyield this.getDataBlock(dataBlock, i % maxChunkSize);\n\t\t\t}\n\t\t}\n\t}\n\n\tprivate getFullDataBlock(index: number) {\n\t\tlet pointerIndex = Math.floor(index / this.cachedMaxChunkSize);\n\t\tlet cachedDataBlock = this.cachedFullDataBlock[pointerIndex];\n\t\tif(cachedDataBlock) {\n\t\t\treturn cachedDataBlock;\n\t\t}\n\t\tif(pointerIndex >= this.pointerStack.length) {\n\t\t\t// Only let a single thread append a new chunk at a time - otherwise two concurrent inserts landing in a new\n\t\t\t// chunk would each allocate + push a buffer, orphaning one and leaking memory\n\t\t\tlock(this.growLock);\n\t\t\ttry {\n\t\t\t\t// Re-check under the lock: another thread may have already appended the chunk while we were waiting\n\t\t\t\twhile(pointerIndex >= this.pointerStack.length) {\n\t\t\t\t\tlet newArray = this.memory.allocUI32(this.cachedMaxChunkSize * this.cachedDataLength * this.cachedByteMultipler);\n\t\t\t\t\tthis.pointerStack.push(newArray.pointer);\n\t\t\t\t}\n\t\t\t} finally {\n\t\t\t\tunlock(this.growLock);\n\t\t\t}\n\t\t}\n\n\t\tlet array = new AllocatedMemory(this.memory, getPointer(this.pointerStack.at(pointerIndex)));\n\n\t\tlet blockLength = this.cachedDataLength * this.cachedMaxChunkSize;\n\t\tlet data: T;\n\t\tswitch(this.cachedType) {\n\t\t\tcase TYPE.int32:\n\t\t\t\tdata = new Int32Array(array.data.buffer, array.bufferByteOffset, blockLength) as T;\n\t\t\t\tbreak;\n\t\t\tcase TYPE.uint32:\n\t\t\t\tdata = new Uint32Array(array.data.buffer, array.bufferByteOffset, blockLength) as T;\n\t\t\t\tbreak;\n\t\t\tcase TYPE.float32:\n\t\t\t\tdata = new Float32Array(array.data.buffer, array.bufferByteOffset, blockLength) as T;\n\t\t\t\tbreak;\n\t\t\tcase TYPE.float64:\n\t\t\t\tdata = new Float64Array(array.data.buffer, array.bufferByteOffset, blockLength) as T;\n\t\t\t\tbreak;\n\t\t\tdefault:\n\t\t\t\tthrow new Error(`Unknown data block type ${this.cachedType}`);\n\t\t}\n\n\t\tthis.cachedFullDataBlock[pointerIndex] = data;\n\t\treturn data;\n\t}\n\n\tprivate getDataBlock(rawData: T, index: number): T {\n\t\tconst dataLength = this.cachedDataLength;\n\t\tconst start = index * dataLength;\n\t\treturn rawData.subarray(start, start + dataLength) as T;\n\t}\n\n\tfree() {\n\t\tthis.recycleStack.free();\n\n\t\tfor(let pointer of this.pointerStack) {\n\t\t\tlet memory = new AllocatedMemory(this.memory, getPointer(pointer));\n\t\t\tmemory.free();\n\t\t}\n\t\tthis.pointerStack.free();\n\t\tthis.firstBlock.free();\n\t}\n\n\tgetSharedMemory(): SharedPoolMemory {\n\t\treturn {\n\t\t\tfirstBlock: this.firstBlock.getSharedMemory(),\n\t\t};\n\t}\n}\n\ninterface SharedPoolConfig<T extends Uint32Array | Int32Array | Float32Array | Float64Array> {\n\tmaxChunkSize?: number\n\ttype?: TypedArrayConstructor<T>\n\tdataLength?: number\n}\ninterface SharedPoolMemory {\n\tfirstBlock: SharedAllocatedMemory\n}\n\nexport type { SharedPoolConfig, SharedPoolMemory };\n"],"mappings":"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"}
1
+ {"version":3,"file":"shared-pool.js","names":[],"sources":["../src/shared-pool.ts"],"sourcesContent":["import type { SharedAllocatedMemory } from './allocated-memory';\nimport AllocatedMemory from './allocated-memory';\nimport type { TypedArrayConstructor } from './interfaces/typed-array-constructor';\nimport { lock, unlock } from './lock/simple-lock';\nimport type MemoryHeap from './memory-heap';\nimport SharedStack from './shared-stack';\nimport { getPointer, loadRawPointer, storeRawPointer } from './utils/pointer';\n\nenum TYPE {\n\tuint32,\n\tint32,\n\tfloat32,\n\tfloat64,\n}\n\nconst LENGTH_INDEX = 0;\nconst TYPE_INDEX = 1;\nconst MAX_CHUNK_SIZE_INDEX = 2;\n// Guards appending a chunk pointer so only one thread grows the pointer stack at a time\nconst GROW_LOCK_INDEX = 3;\n// Pointers to the two backing SharedStacks (their own firstBlocks): the chunk pointers and the recycled indexes\nconst POINTER_STACK_INDEX = 4;\nconst RECYCLE_STACK_INDEX = 5;\n\nconst DEFAULT_MAX_CHUNK_SIZE = 100;\nconst DEFAULT_MAX_LENGTH = 1_000_000;\nconst DEFAULT_MAX_RECYCLED_LENGTH = 100_000;\n\n// Array with stable indexes and maximum contiguous memory sizes (necessary to fit large data sets into max 1MB buffers).\n// The chunk pointers and the recycled indexes both live in SharedStacks, whose segments are never moved or freed once\n// published, so concurrent readers never touch memory that is being reclaimed.\n// https://plflib.org/colony.htm for future enhancements - it seems to be an optimized version of what we were aiming for with this\nexport default class SharedPool<T extends Uint32Array | Int32Array | Float32Array | Float64Array = Uint32Array> implements Iterable<T> {\n\tstatic readonly ALLOCATE_COUNT = 6;\n\tprivate memory: MemoryHeap;\n\n\t// Current Length, Type/DataLength, MaxChunkLength, Grow lock, Pointer stack, Recycle stack\n\tprivate firstBlock: AllocatedMemory;\n\tprivate uint16Array: Uint16Array;\n\tprivate growLock: Int32Array;\n\tprivate pointerStack: SharedStack;\n\tprivate recycleStack: SharedStack;\n\treadonly maxLength: number;\n\n\tprivate cachedFullDataBlock: { [key: number]: T } = {};\n\n\t// Config is written once at construction and never mutated, so it is cached in plain fields\n\tprivate readonly cachedMaxChunkSize: number;\n\tprivate readonly cachedDataLength: number;\n\tprivate readonly cachedType: number;\n\tprivate readonly cachedByteMultipler: number;\n\n\tget length(): number {\n\t\treturn Atomics.load(this.firstBlock.data, LENGTH_INDEX) - this.recycleStack.length;\n\t}\n\tget maxChunkSize(): number {\n\t\treturn this.cachedMaxChunkSize;\n\t}\n\tprivate set maxChunkSize(value: number) {\n\t\tAtomics.store(this.firstBlock.data, MAX_CHUNK_SIZE_INDEX, value);\n\t}\n\n\tget type(): number {\n\t\treturn this.cachedType;\n\t}\n\tprivate set type(value: number) {\n\t\tAtomics.store(this.uint16Array, 0, value);\n\t}\n\tget dataLength(): number {\n\t\treturn this.cachedDataLength;\n\t}\n\tprivate set dataLength(value: number) {\n\t\tAtomics.store(this.uint16Array, 1, value);\n\t}\n\n\tget bufferLength(): number {\n\t\treturn this.cachedMaxChunkSize * this.pointerStack.length;\n\t}\n\tget byteMultipler(): number {\n\t\treturn this.cachedByteMultipler;\n\t}\n\n\t// Own internal memory (which already includes both stacks' inlined firstBlocks) plus every chunk buffer and the\n\t// segments each stack has grown. The stacks' usedMemory excludes their inlined firstBlocks, so they aren't double-counted.\n\tget usedMemory(): number {\n\t\tlet total = this.firstBlock.usedMemory;\n\t\tfor(let pointer of this.pointerStack) {\n\t\t\ttotal += new AllocatedMemory(this.memory, getPointer(pointer)).usedMemory;\n\t\t}\n\t\treturn total + this.pointerStack.usedMemory + this.recycleStack.usedMemory;\n\t}\n\n\tconstructor(memory: MemoryHeap, config?: SharedPoolConfig<T> | SharedPoolMemory) {\n\t\tthis.memory = memory;\n\n\t\tif(config && 'firstBlock' in config) {\n\t\t\tthis.firstBlock = new AllocatedMemory(memory, config.firstBlock);\n\t\t\tthis.uint16Array = new Uint16Array(this.firstBlock.data.buffer, this.firstBlock.bufferByteOffset + TYPE_INDEX * Uint32Array.BYTES_PER_ELEMENT, 2);\n\t\t\tthis.pointerStack = new SharedStack(memory, {\n\t\t\t\tfirstBlock: getPointer(loadRawPointer(this.firstBlock.data, POINTER_STACK_INDEX)),\n\t\t\t});\n\t\t\tthis.recycleStack = new SharedStack(memory, {\n\t\t\t\tfirstBlock: getPointer(loadRawPointer(this.firstBlock.data, RECYCLE_STACK_INDEX)),\n\t\t\t});\n\t\t} else {\n\t\t\tlet dataLength = config?.dataLength ?? 1;\n\t\t\tlet maxChunkSize = config?.maxChunkSize ?? DEFAULT_MAX_CHUNK_SIZE;\n\t\t\tlet maxLength = config?.maxLength ?? DEFAULT_MAX_LENGTH;\n\t\t\tlet maxRecycledLength = config?.maxRecycledLength ?? DEFAULT_MAX_RECYCLED_LENGTH;\n\n\t\t\t// Each stack sizes its inlined firstBlock from its own maxLength, so compute both up front to lay them out. The\n\t\t\t// stacks cap their segments at what a buffer can hold, so size the inlined blocks against that same cap.\n\t\t\tlet maxSegmentLength = SharedStack.getMaxSegmentLength(memory.maxAllocationLength);\n\t\t\tlet pointerStackMaxLength = maxLength / maxChunkSize;\n\t\t\tlet pointerStackAllocateCount = SharedStack.getAllocateCount(pointerStackMaxLength, undefined, maxSegmentLength);\n\t\t\tlet recycleStackAllocateCount = SharedStack.getAllocateCount(maxRecycledLength, undefined, maxSegmentLength);\n\n\t\t\t// Inline both stacks' firstBlocks right after the pool header so the whole spine is one contiguous allocation\n\t\t\tthis.firstBlock = memory.allocUI32(SharedPool.ALLOCATE_COUNT + pointerStackAllocateCount + recycleStackAllocateCount);\n\t\t\tthis.uint16Array = new Uint16Array(this.firstBlock.data.buffer, this.firstBlock.bufferByteOffset + TYPE_INDEX * Uint32Array.BYTES_PER_ELEMENT, 2);\n\n\t\t\tthis.pointerStack = new SharedStack(memory, {\n\t\t\t\tfirstBlock: this.firstBlock.getSubAllocation(SharedPool.ALLOCATE_COUNT, pointerStackAllocateCount),\n\t\t\t\ttype: Uint32Array,\n\t\t\t\tmaxLength: pointerStackMaxLength,\n\t\t\t});\n\t\t\tstoreRawPointer(this.firstBlock.data, POINTER_STACK_INDEX, this.pointerStack.pointer);\n\t\t\tthis.recycleStack = new SharedStack(memory, {\n\t\t\t\tfirstBlock: this.firstBlock.getSubAllocation(SharedPool.ALLOCATE_COUNT + pointerStackAllocateCount, recycleStackAllocateCount),\n\t\t\t\ttype: Uint32Array,\n\t\t\t\tmaxLength: maxRecycledLength,\n\t\t\t});\n\t\t\tstoreRawPointer(this.firstBlock.data, RECYCLE_STACK_INDEX, this.recycleStack.pointer);\n\n\t\t\tconst type = config?.type ?? Uint32Array;\n\t\t\tif(type === Uint32Array) {\n\t\t\t\tthis.type = TYPE.uint32;\n\t\t\t// @ts-expect-error\n\t\t\t} else if(type === Int32Array) {\n\t\t\t\tthis.type = TYPE.int32;\n\t\t\t// @ts-expect-error\n\t\t\t} else if(type === Float32Array) {\n\t\t\t\tthis.type = TYPE.float32;\n\t\t\t// @ts-expect-error\n\t\t\t} else if(type === Float64Array) {\n\t\t\t\tthis.type = TYPE.float64;\n\t\t\t}\n\t\t\tthis.dataLength = dataLength;\n\t\t\tthis.maxChunkSize = maxChunkSize;\n\n\t\t\t// @ts-expect-error\n\t\t\tlet byteMultipler = type === Float64Array ? 2 : 1;\n\t\t\tlet firstArray = memory.allocUI32(maxChunkSize * dataLength * byteMultipler);\n\t\t\tthis.pointerStack.push(firstArray.pointer);\n\t\t}\n\n\t\tthis.cachedType = this.uint16Array[0];\n\t\tthis.cachedDataLength = Math.max(1, this.uint16Array[1]);\n\t\tthis.cachedMaxChunkSize = this.firstBlock.data[MAX_CHUNK_SIZE_INDEX];\n\t\tthis.cachedByteMultipler = this.cachedType === TYPE.float64 ? 2 : 1;\n\t\tthis.maxLength = this.cachedMaxChunkSize * this.pointerStack.maxLength;\n\n\t\tthis.growLock = new Int32Array(this.firstBlock.data.buffer, this.firstBlock.bufferByteOffset + GROW_LOCK_INDEX * Uint32Array.BYTES_PER_ELEMENT, 1);\n\t}\n\n\tat(index: number): T {\n\t\tlet dataBlock = this.getFullDataBlock(index);\n\t\treturn this.getDataBlock(dataBlock, index % this.cachedMaxChunkSize);\n\t}\n\tget(index: number, dataIndex = 0): number {\n\t\tconst dataLength = this.cachedDataLength;\n\t\tif(dataIndex >= dataLength) {\n\t\t\tthrow new Error(`${dataIndex} is out of dataLength bounds ${dataLength}`);\n\t\t}\n\n\t\tlet dataBlock = this.getFullDataBlock(index);\n\t\treturn dataBlock[(index % this.cachedMaxChunkSize) * dataLength + dataIndex];\n\t}\n\n\tpush(values: number | Array<number>): number {\n\t\tlet dataLength = this.cachedDataLength;\n\t\tconst isSingleNumber = typeof values === 'number';\n\t\tif(!isSingleNumber && values.length > dataLength) {\n\t\t\tthrow new Error(`Can't insert ${values.length} array into shared list of ${dataLength} dataLength`);\n\t\t}\n\n\t\t// Reuse a recycled index if one is available. pop() can come back undefined when another thread drained the last\n\t\t// slot between our two calls, so fall through to a fresh index rather than trusting a length() read\n\t\tlet newIndex = this.recycleStack.pop();\n\t\tif(newIndex === undefined) {\n\t\t\tnewIndex = Atomics.add(this.firstBlock.data, LENGTH_INDEX, 1);\n\t\t}\n\n\t\tlet dataBlock = this.getFullDataBlock(newIndex);\n\t\tlet startIndex = dataLength * (newIndex % this.cachedMaxChunkSize);\n\t\tif(isSingleNumber) {\n\t\t\tdataBlock[startIndex] = values;\n\t\t} else {\n\t\t\tdataBlock.set(values, startIndex);\n\t\t}\n\n\t\treturn newIndex;\n\t}\n\n\tdeleteIndex(index: number) {\n\t\tthis.recycleStack.push(index);\n\t}\n\n\tclear() {\n\t\tthis.firstBlock.data[LENGTH_INDEX] = 0;\n\t\tthis.recycleStack.clear();\n\t}\n\n\t*[Symbol.iterator]() {\n\t\tconst recycledValues: { [key: number]: true } = {};\n\t\tfor(let value of this.recycleStack) {\n\t\t\trecycledValues[value] = true;\n\t\t}\n\n\t\tconst maxChunkSize = this.cachedMaxChunkSize;\n\t\t// Snapshot the length once - iteration over a concurrently mutated pool is a snapshot anyway, and re-loading the\n\t\t// atomic every step is pure overhead\n\t\tconst length = Atomics.load(this.firstBlock.data, LENGTH_INDEX);\n\t\tlet dataBlock = this.getFullDataBlock(0);\n\t\tlet dataBlockIndex = 0;\n\t\tfor(let i = 0; i < length; i++) {\n\t\t\tif(!recycledValues[i]) {\n\t\t\t\tlet newDataBlockIndex = Math.floor(i / maxChunkSize);\n\t\t\t\tif(newDataBlockIndex !== dataBlockIndex) {\n\t\t\t\t\tdataBlock = this.getFullDataBlock(i);\n\t\t\t\t\tdataBlockIndex = newDataBlockIndex;\n\t\t\t\t}\n\t\t\t\tyield this.getDataBlock(dataBlock, i % maxChunkSize);\n\t\t\t}\n\t\t}\n\t}\n\n\tprivate getFullDataBlock(index: number) {\n\t\tlet pointerIndex = Math.floor(index / this.cachedMaxChunkSize);\n\t\tlet cachedDataBlock = this.cachedFullDataBlock[pointerIndex];\n\t\tif(cachedDataBlock) {\n\t\t\treturn cachedDataBlock;\n\t\t}\n\t\tif(pointerIndex >= this.pointerStack.length) {\n\t\t\t// Only let a single thread append a new chunk at a time - otherwise two concurrent inserts landing in a new\n\t\t\t// chunk would each allocate + push a buffer, orphaning one and leaking memory\n\t\t\tlock(this.growLock);\n\t\t\ttry {\n\t\t\t\t// Re-check under the lock: another thread may have already appended the chunk while we were waiting\n\t\t\t\twhile(pointerIndex >= this.pointerStack.length) {\n\t\t\t\t\tlet newArray = this.memory.allocUI32(this.cachedMaxChunkSize * this.cachedDataLength * this.cachedByteMultipler);\n\t\t\t\t\tthis.pointerStack.push(newArray.pointer);\n\t\t\t\t}\n\t\t\t} finally {\n\t\t\t\tunlock(this.growLock);\n\t\t\t}\n\t\t}\n\n\t\tlet array = new AllocatedMemory(this.memory, getPointer(this.pointerStack.at(pointerIndex)));\n\n\t\tlet blockLength = this.cachedDataLength * this.cachedMaxChunkSize;\n\t\tlet data: T;\n\t\tswitch(this.cachedType) {\n\t\t\tcase TYPE.int32:\n\t\t\t\tdata = new Int32Array(array.data.buffer, array.bufferByteOffset, blockLength) as T;\n\t\t\t\tbreak;\n\t\t\tcase TYPE.uint32:\n\t\t\t\tdata = new Uint32Array(array.data.buffer, array.bufferByteOffset, blockLength) as T;\n\t\t\t\tbreak;\n\t\t\tcase TYPE.float32:\n\t\t\t\tdata = new Float32Array(array.data.buffer, array.bufferByteOffset, blockLength) as T;\n\t\t\t\tbreak;\n\t\t\tcase TYPE.float64:\n\t\t\t\tdata = new Float64Array(array.data.buffer, array.bufferByteOffset, blockLength) as T;\n\t\t\t\tbreak;\n\t\t\tdefault:\n\t\t\t\tthrow new Error(`Unknown data block type ${this.cachedType}`);\n\t\t}\n\n\t\tthis.cachedFullDataBlock[pointerIndex] = data;\n\t\treturn data;\n\t}\n\n\tprivate getDataBlock(rawData: T, index: number): T {\n\t\tconst dataLength = this.cachedDataLength;\n\t\tconst start = index * dataLength;\n\t\treturn rawData.subarray(start, start + dataLength) as T;\n\t}\n\n\tfree() {\n\t\tthis.recycleStack.free();\n\n\t\tfor(let pointer of this.pointerStack) {\n\t\t\tlet memory = new AllocatedMemory(this.memory, getPointer(pointer));\n\t\t\tmemory.free();\n\t\t}\n\t\tthis.pointerStack.free();\n\t\tthis.firstBlock.free();\n\t}\n\n\tgetSharedMemory(): SharedPoolMemory {\n\t\treturn {\n\t\t\tfirstBlock: this.firstBlock.getSharedMemory(),\n\t\t};\n\t}\n}\n\ninterface SharedPoolConfig<T extends Uint32Array | Int32Array | Float32Array | Float64Array> {\n\tmaxChunkSize?: number\n\tmaxLength?: number\n\tmaxRecycledLength?: number\n\ttype?: TypedArrayConstructor<T>\n\tdataLength?: number\n}\ninterface SharedPoolMemory {\n\tfirstBlock: SharedAllocatedMemory\n}\n\nexport type { SharedPoolConfig, SharedPoolMemory };\n"],"mappings":"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"}
@@ -3,8 +3,12 @@ import AllocatedMemory from './allocated-memory';
3
3
  import type { TypedArrayConstructor } from './interfaces/typed-array-constructor';
4
4
  import type MemoryHeap from './memory-heap';
5
5
  export default class SharedStack<T extends Uint32Array | Int32Array | Float32Array = Uint32Array> implements Iterable<number> {
6
- static readonly BASE_ALLOCATE_COUNT = 7;
6
+ static readonly BASE_ALLOCATE_COUNT = 8;
7
7
  static readonly DEFAULT_ALLOCATE_COUNT: number;
8
+ static getMaxSegmentLength(maxAllocationLength: number, baseSegmentLength?: number): number;
9
+ static getMaxSegments(maxLength: number, baseSegmentLength?: number, maxSegmentLength?: number): number;
10
+ static getCapacity(segments: number, baseSegmentLength?: number, maxSegmentLength?: number): number;
11
+ static getAllocateCount(maxLength: number, baseSegmentLength?: number, maxSegmentLength?: number): number;
8
12
  private memory;
9
13
  private firstBlock;
10
14
  private uint16Array;
@@ -14,6 +18,9 @@ export default class SharedStack<T extends Uint32Array | Int32Array | Float32Arr
14
18
  private cachedSegments;
15
19
  private cachedSequences;
16
20
  private cachedBaseSegmentLength;
21
+ private cachedMaxSegmentLength;
22
+ private cachedCapSegmentCount;
23
+ private cachedGeometricCapacity;
17
24
  get length(): number;
18
25
  get segmentCount(): number;
19
26
  private set segmentCount(value);
@@ -23,8 +30,13 @@ export default class SharedStack<T extends Uint32Array | Int32Array | Float32Arr
23
30
  private set baseSegmentLength(value);
24
31
  get maxSegments(): number;
25
32
  private set maxSegments(value);
33
+ get maxSegmentLength(): number;
34
+ private set maxSegmentLength(value);
26
35
  get pointer(): number;
36
+ get usedMemory(): number;
27
37
  constructor(memory: MemoryHeap, config?: SharedStackConfig<T> | SharedStackMemory | (SharedStackMemory & SharedStackConfig<T>));
38
+ private resolveMaxSegmentLength;
39
+ private resolveMaxSegments;
28
40
  private initialize;
29
41
  at(index: number): number;
30
42
  push(value: number): number;
@@ -38,7 +50,8 @@ export default class SharedStack<T extends Uint32Array | Int32Array | Float32Arr
38
50
  interface SharedStackConfig<T extends Uint32Array | Int32Array | Float32Array> {
39
51
  type?: TypedArrayConstructor<T>;
40
52
  baseSegmentLength?: number;
41
- segmentCount?: number;
53
+ maxLength?: number;
54
+ maxSegmentLength?: number;
42
55
  }
43
56
  interface SharedStackMemory {
44
57
  firstBlock: SharedAllocatedMemory | AllocatedMemory;
@@ -2,9 +2,27 @@ import { getPointer as e, storePointer as t } from "./utils/pointer.js";
2
2
  import n from "./allocated-memory.js";
3
3
  import { lock as r, unlock as i } from "./lock/simple-lock.js";
4
4
  //#region src/shared-stack.ts
5
- var a = 0, o = 1, s = 2, c = 3, l = 4, u = 5, d = 6, f = 4, p = 20, m = 22, h = (1 << m) - 1, g = 4194302, _ = 1 << m, v = class m {
6
- static BASE_ALLOCATE_COUNT = 7;
7
- static DEFAULT_ALLOCATE_COUNT = this.BASE_ALLOCATE_COUNT + p;
5
+ var a = 0, o = 1, s = 2, c = 3, l = 4, u = 5, d = 6, f = 7, p = 4, m = 20, h = 22, g = (1 << h) - 1, _ = 4194302, v = 1 << h, y = class h {
6
+ static BASE_ALLOCATE_COUNT = 8;
7
+ static DEFAULT_ALLOCATE_COUNT = this.BASE_ALLOCATE_COUNT + m;
8
+ static getMaxSegmentLength(e, t = p) {
9
+ let n = t;
10
+ for (; n * 4 <= e;) n *= 2;
11
+ return n;
12
+ }
13
+ static getMaxSegments(e, t = p, n = Infinity) {
14
+ let r = 0, i = 0, a = t;
15
+ for (; i < e;) i += a, r++, a = Math.min(a * 2, n);
16
+ return r;
17
+ }
18
+ static getCapacity(e, t = p, n = Infinity) {
19
+ let r = 0, i = t;
20
+ for (let t = 0; t < e; t++) r += i, i = Math.min(i * 2, n);
21
+ return r;
22
+ }
23
+ static getAllocateCount(e, t = p, n = Infinity) {
24
+ return this.BASE_ALLOCATE_COUNT + this.getMaxSegments(e, t, n);
25
+ }
8
26
  memory;
9
27
  firstBlock;
10
28
  uint16Array;
@@ -14,14 +32,17 @@ var a = 0, o = 1, s = 2, c = 3, l = 4, u = 5, d = 6, f = 4, p = 20, m = 22, h =
14
32
  cachedSegments = [];
15
33
  cachedSequences = [];
16
34
  cachedBaseSegmentLength = 0;
35
+ cachedMaxSegmentLength = 0;
36
+ cachedCapSegmentCount = 0;
37
+ cachedGeometricCapacity = 0;
17
38
  get length() {
18
- return Atomics.load(this.firstBlock.data, a) & h;
39
+ return Atomics.load(this.firstBlock.data, a) & g;
19
40
  }
20
41
  get segmentCount() {
21
- return Atomics.load(this.firstBlock.data, l);
42
+ return Atomics.load(this.firstBlock.data, u);
22
43
  }
23
44
  set segmentCount(e) {
24
- this.firstBlock.data[l] = e;
45
+ this.firstBlock.data[u] = e;
25
46
  }
26
47
  get type() {
27
48
  return this.uint16Array[0];
@@ -41,74 +62,130 @@ var a = 0, o = 1, s = 2, c = 3, l = 4, u = 5, d = 6, f = 4, p = 20, m = 22, h =
41
62
  set maxSegments(e) {
42
63
  this.firstBlock.data[c] = e;
43
64
  }
65
+ get maxSegmentLength() {
66
+ return this.firstBlock.data[l];
67
+ }
68
+ set maxSegmentLength(e) {
69
+ this.firstBlock.data[l] = e;
70
+ }
44
71
  get pointer() {
45
72
  return this.firstBlock.pointer;
46
73
  }
74
+ get usedMemory() {
75
+ let t = this.ownsFirstBlock ? this.firstBlock.usedMemory : 0;
76
+ for (let r = 0; r < this.segmentCount; r++) {
77
+ let i = e(Atomics.load(this.firstBlock.data, f + r));
78
+ t += new n(this.memory, i).usedMemory;
79
+ }
80
+ return t;
81
+ }
47
82
  constructor(e, t) {
48
- if (this.memory = e, t && "firstBlock" in t) this.firstBlock = t.firstBlock instanceof n ? t.firstBlock : new n(e, t.firstBlock), this.uint16Array = new Uint16Array(this.firstBlock.data.buffer, this.firstBlock.bufferByteOffset + o * Uint32Array.BYTES_PER_ELEMENT, 2), "type" in t || "baseSegmentLength" in t || "segmentCount" in t ? (this.initialize(t), this.ownsFirstBlock = !1) : this.ownsFirstBlock = !0;
83
+ if (this.memory = e, t && "firstBlock" in t) this.firstBlock = t.firstBlock instanceof n ? t.firstBlock : new n(e, t.firstBlock), this.uint16Array = new Uint16Array(this.firstBlock.data.buffer, this.firstBlock.bufferByteOffset + o * Uint32Array.BYTES_PER_ELEMENT, 2), "type" in t || "baseSegmentLength" in t || "maxLength" in t ? (this.initialize(t), this.ownsFirstBlock = !1) : this.ownsFirstBlock = !0;
49
84
  else {
50
- let n = t?.segmentCount ?? p;
51
- this.firstBlock = e.allocUI32(m.BASE_ALLOCATE_COUNT + n), this.uint16Array = new Uint16Array(this.firstBlock.data.buffer, this.firstBlock.bufferByteOffset + o * Uint32Array.BYTES_PER_ELEMENT, 2), this.ownsFirstBlock = !0, this.initialize(t);
85
+ let n = t?.baseSegmentLength ?? p, r = this.resolveMaxSegmentLength(n, t?.maxSegmentLength), i = this.resolveMaxSegments(t?.maxLength, n, r);
86
+ this.firstBlock = e.allocUI32(h.BASE_ALLOCATE_COUNT + i), this.uint16Array = new Uint16Array(this.firstBlock.data.buffer, this.firstBlock.bufferByteOffset + o * Uint32Array.BYTES_PER_ELEMENT, 2), this.ownsFirstBlock = !0, this.initialize(t);
52
87
  }
53
- if (this.growLock = new Int32Array(this.firstBlock.data.buffer, this.firstBlock.bufferByteOffset + u * Uint32Array.BYTES_PER_ELEMENT, 1), this.cachedBaseSegmentLength = this.baseSegmentLength, this.maxLength = this.cachedBaseSegmentLength * (2 ** this.maxSegments - 1), this.maxLength > g) throw Error(`SharedStack maxLength ${this.maxLength} exceeds the ${g} the versioned length counter can address`);
88
+ if (this.growLock = new Int32Array(this.firstBlock.data.buffer, this.firstBlock.bufferByteOffset + d * Uint32Array.BYTES_PER_ELEMENT, 1), this.cachedBaseSegmentLength = this.baseSegmentLength, this.cachedMaxSegmentLength = this.maxSegmentLength, this.cachedCapSegmentCount = 32 - Math.clz32(this.cachedMaxSegmentLength / this.cachedBaseSegmentLength | 0), this.cachedGeometricCapacity = this.cachedBaseSegmentLength * ((1 << this.cachedCapSegmentCount) - 1), this.maxLength = h.getCapacity(this.maxSegments, this.cachedBaseSegmentLength, this.cachedMaxSegmentLength), this.maxLength > _) throw Error(`SharedStack maxLength ${this.maxLength} exceeds the ${_} the versioned length counter can address`);
89
+ }
90
+ resolveMaxSegmentLength(e, t) {
91
+ let n = h.getMaxSegmentLength(this.memory.maxAllocationLength, e);
92
+ if (t) {
93
+ let r = e;
94
+ for (; r * 2 <= t;) r *= 2;
95
+ n = Math.min(n, r);
96
+ }
97
+ return n;
98
+ }
99
+ resolveMaxSegments(e, t, n) {
100
+ let r = e === void 0 ? m : h.getMaxSegments(e, t, n), i = this.memory.maxAllocationLength - h.BASE_ALLOCATE_COUNT;
101
+ return Math.min(r, i);
54
102
  }
55
103
  initialize(e) {
56
- let n = e?.baseSegmentLength ?? f, r = this.memory.allocUI32(n * 2);
57
- t(this.firstBlock.data, d, r.bufferPosition, r.bufferByteOffset), this.baseSegmentLength = n, this.maxSegments = e?.segmentCount ?? p, this.segmentCount = 1;
58
- let i = e?.type ?? Uint32Array;
59
- i === Uint32Array ? this.type = 0 : i === Int32Array ? this.type = 1 : i === Float32Array && (this.type = 2);
104
+ let n = e?.baseSegmentLength ?? p, r = this.resolveMaxSegmentLength(n, e?.maxSegmentLength), i = this.memory.allocUI32(n * 2);
105
+ t(this.firstBlock.data, f, i.bufferPosition, i.bufferByteOffset), this.baseSegmentLength = n, this.maxSegmentLength = r, this.maxSegments = this.resolveMaxSegments(e?.maxLength, n, r), this.segmentCount = 1;
106
+ let a = e?.type ?? Uint32Array;
107
+ a === Uint32Array ? this.type = 0 : a === Int32Array ? this.type = 1 : a === Float32Array && (this.type = 2);
60
108
  }
61
109
  at(e) {
62
110
  let t = this.length;
63
111
  if (e >= t || e < 0) throw Error(`${e} is out of bounds ${t}`);
64
- let n = this.cachedBaseSegmentLength, r = 31 - Math.clz32((e / n | 0) + 1);
65
- return (this.cachedSegments[r] ?? this.getSegment(r))[e - n * ((1 << r) - 1)];
112
+ let n = this.cachedGeometricCapacity, r, i;
113
+ if (e < n) {
114
+ let t = this.cachedBaseSegmentLength;
115
+ r = 31 - Math.clz32((e / t | 0) + 1), i = e - t * ((1 << r) - 1);
116
+ } else {
117
+ let t = this.cachedMaxSegmentLength, a = e - n, o = a / t | 0;
118
+ r = this.cachedCapSegmentCount + o, i = a - o * t;
119
+ }
120
+ return (this.cachedSegments[r] ?? this.getSegment(r))[i];
66
121
  }
67
122
  push(e) {
68
- let t = this.firstBlock.data, n = this.cachedBaseSegmentLength;
123
+ let t = this.firstBlock.data, n = this.cachedGeometricCapacity;
69
124
  for (;;) {
70
- let r = t[a], i = r & h;
125
+ let r = t[a], i = r & g;
71
126
  if (i >= this.maxLength) throw Error(`${i + 1} is out of bounds ${this.maxLength}`);
72
- let o = 31 - Math.clz32((i / n | 0) + 1), s = i - n * ((1 << o) - 1), c = this.cachedSegments[o] ?? this.getSegment(o), l = this.cachedSequences[o], u = Atomics.load(l, s);
73
- if (!(u & 1) && Atomics.compareExchange(t, a, r, r + _ + 1) === r) return c[s] = e, Atomics.store(l, s, u + 1), i;
127
+ let o, s;
128
+ if (i < n) {
129
+ let e = this.cachedBaseSegmentLength;
130
+ o = 31 - Math.clz32((i / e | 0) + 1), s = i - e * ((1 << o) - 1);
131
+ } else {
132
+ let e = this.cachedMaxSegmentLength, t = i - n, r = t / e | 0;
133
+ o = this.cachedCapSegmentCount + r, s = t - r * e;
134
+ }
135
+ let c = this.cachedSegments[o] ?? this.getSegment(o), l = this.cachedSequences[o], u = Atomics.load(l, s);
136
+ if (!(u & 1) && Atomics.compareExchange(t, a, r, r + v + 1) === r) return c[s] = e, Atomics.store(l, s, u + 1), i;
74
137
  }
75
138
  }
76
139
  pop() {
77
- let e = this.firstBlock.data, t = this.cachedBaseSegmentLength;
140
+ let e = this.firstBlock.data, t = this.cachedGeometricCapacity;
78
141
  for (;;) {
79
- let n = e[a], r = n & h;
142
+ let n = e[a], r = n & g;
80
143
  if (r <= 0) return;
81
- let i = r - 1, o = 31 - Math.clz32((i / t | 0) + 1), s = i - t * ((1 << o) - 1), c = this.cachedSegments[o] ?? this.getSegment(o), l = this.cachedSequences[o], u = Atomics.load(l, s);
82
- if (!(u & 1) || Atomics.compareExchange(e, a, n, n + _ - 1) !== n) continue;
144
+ let i = r - 1, o, s;
145
+ if (i < t) {
146
+ let e = this.cachedBaseSegmentLength;
147
+ o = 31 - Math.clz32((i / e | 0) + 1), s = i - e * ((1 << o) - 1);
148
+ } else {
149
+ let e = this.cachedMaxSegmentLength, n = i - t, r = n / e | 0;
150
+ o = this.cachedCapSegmentCount + r, s = n - r * e;
151
+ }
152
+ let c = this.cachedSegments[o] ?? this.getSegment(o), l = this.cachedSequences[o], u = Atomics.load(l, s);
153
+ if (!(u & 1) || Atomics.compareExchange(e, a, n, n + v - 1) !== n) continue;
83
154
  let d = c[s];
84
155
  return Atomics.store(l, s, u + 1), d;
85
156
  }
86
157
  }
87
158
  clear() {
88
- let e = this.cachedBaseSegmentLength, t = Atomics.load(this.firstBlock.data, a) & h;
89
- for (let n = 0; n < t; n++) {
90
- let t = 31 - Math.clz32((n / e | 0) + 1), r = this.cachedSequences[t] ?? (this.getSegment(t), this.cachedSequences[t]), i = n - e * ((1 << t) - 1), a = Atomics.load(r, i);
91
- a & 1 && Atomics.store(r, i, a + 1);
159
+ let e = this.cachedBaseSegmentLength, t = this.cachedGeometricCapacity, n = this.cachedMaxSegmentLength, r = Atomics.load(this.firstBlock.data, a) & g;
160
+ for (let i = 0; i < r; i++) {
161
+ let r, a;
162
+ if (i < t) r = 31 - Math.clz32((i / e | 0) + 1), a = i - e * ((1 << r) - 1);
163
+ else {
164
+ let e = i - t, o = e / n | 0;
165
+ r = this.cachedCapSegmentCount + o, a = e - o * n;
166
+ }
167
+ let o = this.cachedSequences[r] ?? (this.getSegment(r), this.cachedSequences[r]), s = Atomics.load(o, a);
168
+ s & 1 && Atomics.store(o, a, s + 1);
92
169
  }
93
170
  Atomics.store(this.firstBlock.data, a, 0);
94
171
  }
95
172
  *[Symbol.iterator]() {
96
- let e = this.length, t = this.baseSegmentLength, n = 0, r = this.getSegment(n), i = 0;
97
- for (let a = 0; a < e; a++) yield r[i], i++, i >= t && (n++, r = this.getSegment(n), i = 0, t *= 2);
173
+ let e = this.length, t = this.cachedMaxSegmentLength, n = this.baseSegmentLength, r = 0, i = this.getSegment(r), a = 0;
174
+ for (let o = 0; o < e; o++) yield i[a], a++, a >= n && (r++, i = this.getSegment(r), a = 0, n = Math.min(n * 2, t));
98
175
  }
99
176
  getSegment(a) {
100
177
  if (this.cachedSegments[a]) return this.cachedSegments[a];
101
- let o = this.cachedBaseSegmentLength * 2 ** a, s;
178
+ let o = a < this.cachedCapSegmentCount ? this.cachedBaseSegmentLength << a : this.cachedMaxSegmentLength, s;
102
179
  if (a >= this.segmentCount) {
103
180
  r(this.growLock);
104
181
  try {
105
- a >= this.segmentCount && (s = this.memory.allocUI32(o * 2), t(this.firstBlock.data, d + a, s.bufferPosition, s.bufferByteOffset), Atomics.add(this.firstBlock.data, l, 1));
182
+ a >= this.segmentCount && (s = this.memory.allocUI32(o * 2), t(this.firstBlock.data, f + a, s.bufferPosition, s.bufferByteOffset), Atomics.add(this.firstBlock.data, u, 1));
106
183
  } finally {
107
184
  i(this.growLock);
108
185
  }
109
186
  }
110
187
  if (!s) {
111
- let t = Atomics.load(this.firstBlock.data, d + a), r = e(t);
188
+ let t = Atomics.load(this.firstBlock.data, f + a), r = e(t);
112
189
  s = new n(this.memory, r);
113
190
  }
114
191
  let c;
@@ -124,12 +201,12 @@ var a = 0, o = 1, s = 2, c = 3, l = 4, u = 5, d = 6, f = 4, p = 20, m = 22, h =
124
201
  break;
125
202
  default: throw Error(`Unknown data block type ${this.type}`);
126
203
  }
127
- let u = s.bufferByteOffset + o * Uint32Array.BYTES_PER_ELEMENT;
128
- return this.cachedSequences[a] = new Int32Array(s.data.buffer, u, o), this.cachedSegments[a] = c, c;
204
+ let l = s.bufferByteOffset + o * Uint32Array.BYTES_PER_ELEMENT;
205
+ return this.cachedSequences[a] = new Int32Array(s.data.buffer, l, o), this.cachedSegments[a] = c, c;
129
206
  }
130
207
  free() {
131
208
  for (let t = 0; t < this.segmentCount; t++) {
132
- let r = Atomics.load(this.firstBlock.data, d + t), i = e(r);
209
+ let r = Atomics.load(this.firstBlock.data, f + t), i = e(r);
133
210
  new n(this.memory, i).free();
134
211
  }
135
212
  this.ownsFirstBlock && this.firstBlock.free();
@@ -139,6 +216,6 @@ var a = 0, o = 1, s = 2, c = 3, l = 4, u = 5, d = 6, f = 4, p = 20, m = 22, h =
139
216
  }
140
217
  };
141
218
  //#endregion
142
- export { v as default };
219
+ export { y as default };
143
220
 
144
221
  //# sourceMappingURL=shared-stack.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"shared-stack.js","names":[],"sources":["../src/shared-stack.ts"],"sourcesContent":["import type { SharedAllocatedMemory } from './allocated-memory';\nimport AllocatedMemory from './allocated-memory';\nimport type { TypedArrayConstructor } from './interfaces/typed-array-constructor';\nimport { lock, unlock } from './lock/simple-lock';\nimport type MemoryHeap from './memory-heap';\nimport { getPointer, storePointer } from './utils/pointer';\n\nenum TYPE {\n\tuint32,\n\tint32,\n\tfloat32,\n}\n\nconst LENGTH_INDEX = 0;\nconst TYPE_INDEX = 1;\nconst BASE_SEGMENT_LENGTH_INDEX = 2;\nconst MAX_SEGMENTS_INDEX = 3;\nconst SEGMENT_COUNT_INDEX = 4;\nconst GROW_LOCK_INDEX = 5;\nconst SEGMENT_START_INDEX = 6;\n\nconst DEFAULT_BASE_SEGMENT_LENGTH = 4;\nconst DEFAULT_MAX_SEGMENTS = 20;\n\n// The LENGTH word packs an ABA version counter (high bits) with the live count (low LEN_BITS). Both push and pop bump\n// the version on every successful CAS, so the CAS fails on ANY intervening op - the count can never ABA back to the\n// same value with a slot at a different generation. 22 count bits cover the default max length (4,194,300); the\n// remaining 10 version bits (1024) only need to outlast a single thread's load->CAS window.\nconst LEN_BITS = 22;\nconst LEN_MASK = (1 << LEN_BITS) - 1;\nconst MAX_COUNT = LEN_MASK - 1;\n// Adding this to the packed word bumps the version (high bits) by one. Combined with +1/-1 for the count it repacks the\n// whole word in a single add: the count is guarded from overflowing into the version bits, and a version overflow just\n// wraps harmlessly when the value is coerced back to Uint32 on the atomic store.\nconst VERSION_STEP = 1 << LEN_BITS;\n\n// Per-slot publish handshake. Each data slot has a companion sequence number: even means the slot is empty (free to\n// fill) at some lap, odd means the value is published (safe to read). It only ever increases. push/pop read the slot's\n// sequence and bail out (retry) unless it has the parity they need, then CAS the length; because the versioned CAS\n// rejects any intervening op, the sequence read stays valid all the way through the publish store below.\n\nexport default class SharedStack<T extends Uint32Array | Int32Array | Float32Array = Uint32Array> implements Iterable<number> {\n\tstatic readonly BASE_ALLOCATE_COUNT = 7;\n\tstatic readonly DEFAULT_ALLOCATE_COUNT = this.BASE_ALLOCATE_COUNT + DEFAULT_MAX_SEGMENTS;\n\tprivate memory: MemoryHeap;\n\n\t// Length, Type, Base Segment Length, Max Segments, Segment Count, Grow Lock, Segments\n\tprivate firstBlock: AllocatedMemory;\n\tprivate uint16Array: Uint16Array;\n\tprivate growLock: Int32Array;\n\treadonly maxLength: number;\n\n\t// False when firstBlock is a slice of another structure's memory (e.g. inlined in SharedPool); that owner frees it\n\tprivate ownsFirstBlock: boolean;\n\n\tprivate cachedSegments: T[] = [];\n\t// Parallel to cachedSegments: the per-slot publish sequences for each slot of that segment. Populated together with\n\t// the data view in getSegment, so whenever cachedSegments[i] is set cachedSequences[i] is too.\n\tprivate cachedSequences: Int32Array[] = [];\n\tprivate cachedBaseSegmentLength = 0;\n\n\tget length(): number {\n\t\treturn Atomics.load(this.firstBlock.data, LENGTH_INDEX) & LEN_MASK;\n\t}\n\tget segmentCount(): number {\n\t\treturn Atomics.load(this.firstBlock.data, SEGMENT_COUNT_INDEX);\n\t}\n\tprivate set segmentCount(value: number) {\n\t\tthis.firstBlock.data[SEGMENT_COUNT_INDEX] = value;\n\t}\n\t\n\tget type(): number {\n\t\treturn this.uint16Array[0];\n\t}\n\tprivate set type(value: number) {\n\t\tAtomics.store(this.uint16Array, 0, value);\n\t}\n\n\tget baseSegmentLength() {\n\t\treturn this.firstBlock.data[BASE_SEGMENT_LENGTH_INDEX];\n\t}\n\tprivate set baseSegmentLength(value: number) {\n\t\tthis.firstBlock.data[BASE_SEGMENT_LENGTH_INDEX] = value;\n\t}\n\tget maxSegments() {\n\t\treturn this.firstBlock.data[MAX_SEGMENTS_INDEX];\n\t}\n\tprivate set maxSegments(value: number) {\n\t\tthis.firstBlock.data[MAX_SEGMENTS_INDEX] = value;\n\t}\n\n\tget pointer() {\n\t\treturn this.firstBlock.pointer;\n\t}\n\n\tconstructor(memory: MemoryHeap, config?: SharedStackConfig<T> | SharedStackMemory | SharedStackMemory & SharedStackConfig<T>) {\n\t\tthis.memory = memory;\n\n\t\tif(config && 'firstBlock' in config) {\n\t\t\t// An AllocatedMemory instance is a caller-reserved slice (e.g. inlined in SharedPool); a plain pointer is the\n\t\t\t// serialized clone path. Taking the instance directly keeps us off the pointer-resolving construction branch.\n\t\t\tthis.firstBlock = config.firstBlock instanceof AllocatedMemory ? config.firstBlock : new AllocatedMemory(memory, config.firstBlock);\n\t\t\tthis.uint16Array = new Uint16Array(this.firstBlock.data.buffer, this.firstBlock.bufferByteOffset + TYPE_INDEX * Uint32Array.BYTES_PER_ELEMENT, 2);\n\n\t\t\t// Memory passed together with config means the caller reserved the firstBlock slot for us to initialize in place\n\t\t\tif('type' in config || 'baseSegmentLength' in config || 'segmentCount' in config) {\n\t\t\t\tthis.initialize(config);\n\t\t\t\tthis.ownsFirstBlock = false;\n\t\t\t} else {\n\t\t\t\tthis.ownsFirstBlock = true;\n\t\t\t}\n\t\t} else {\n\t\t\tlet maxSegments = config?.segmentCount ?? DEFAULT_MAX_SEGMENTS;\n\n\t\t\tthis.firstBlock = memory.allocUI32(SharedStack.BASE_ALLOCATE_COUNT + maxSegments);\n\t\t\tthis.uint16Array = new Uint16Array(this.firstBlock.data.buffer, this.firstBlock.bufferByteOffset + TYPE_INDEX * Uint32Array.BYTES_PER_ELEMENT, 2);\n\t\t\tthis.ownsFirstBlock = true;\n\n\t\t\tthis.initialize(config);\n\t\t}\n\n\t\tthis.growLock = new Int32Array(this.firstBlock.data.buffer, this.firstBlock.bufferByteOffset + GROW_LOCK_INDEX * Uint32Array.BYTES_PER_ELEMENT, 1);\n\t\tthis.cachedBaseSegmentLength = this.baseSegmentLength;\n\t\tthis.maxLength = this.cachedBaseSegmentLength * (2 ** this.maxSegments - 1);\n\t\tif(this.maxLength > MAX_COUNT) {\n\t\t\t// The length word reserves LEN_BITS for the count and the rest for the ABA version tag\n\t\t\tthrow new Error(`SharedStack maxLength ${this.maxLength} exceeds the ${MAX_COUNT} the versioned length counter can address`);\n\t\t}\n\t}\n\n\tprivate initialize(config?: SharedStackConfig<T>) {\n\t\tlet baseSegmentLength = config?.baseSegmentLength ?? DEFAULT_BASE_SEGMENT_LENGTH;\n\t\t// Each segment block holds the data slots followed by an equal-length region of publish sequences\n\t\tlet firstSegmentBlock = this.memory.allocUI32(baseSegmentLength * 2);\n\t\tstorePointer(this.firstBlock.data, SEGMENT_START_INDEX, firstSegmentBlock.bufferPosition, firstSegmentBlock.bufferByteOffset);\n\t\tthis.baseSegmentLength = baseSegmentLength;\n\t\tthis.maxSegments = config?.segmentCount ?? DEFAULT_MAX_SEGMENTS;\n\t\tthis.segmentCount = 1;\n\n\t\tconst type = config?.type ?? Uint32Array;\n\t\tif(type === Uint32Array) {\n\t\t\tthis.type = TYPE.uint32;\n\t\t// @ts-expect-error\n\t\t} else if(type === Int32Array) {\n\t\t\tthis.type = TYPE.int32;\n\t\t// @ts-expect-error\n\t\t} else if(type === Float32Array) {\n\t\t\tthis.type = TYPE.float32;\n\t\t}\n\t}\n\n\tat(index: number): number {\n\t\tlet length = this.length;\n\t\tif(index >= length || index < 0) {\n\t\t\tthrow new Error(`${index} is out of bounds ${length}`);\n\t\t}\n\n\t\tlet baseSegmentLength = this.cachedBaseSegmentLength;\n\t\tlet segmentIndex = 31 - Math.clz32(((index / baseSegmentLength) | 0) + 1);\n\t\tlet segment = this.cachedSegments[segmentIndex] ?? this.getSegment(segmentIndex);\n\t\treturn segment[index - baseSegmentLength * ((1 << segmentIndex) - 1)];\n\t}\n\n\tpush(value: number): number {\n\t\tlet data = this.firstBlock.data;\n\t\tlet baseSegmentLength = this.cachedBaseSegmentLength;\n\t\t// eslint-disable-next-line no-constant-condition\n\t\twhile(true) {\n\t\t\t// Plain (relaxed) read is enough: it only seeds the versioned CAS below, which rejects any stale value\n\t\t\tlet packed = data[LENGTH_INDEX];\n\t\t\tlet newIndex = packed & LEN_MASK;\n\t\t\tif(newIndex >= this.maxLength) {\n\t\t\t\tthrow new Error(`${newIndex + 1} is out of bounds ${this.maxLength}`);\n\t\t\t}\n\n\t\t\tlet segmentIndex = 31 - Math.clz32(((newIndex / baseSegmentLength) | 0) + 1);\n\t\t\tlet localIndex = newIndex - baseSegmentLength * ((1 << segmentIndex) - 1);\n\t\t\tlet segment = this.cachedSegments[segmentIndex] ?? this.getSegment(segmentIndex);\n\t\t\tlet sequences = this.cachedSequences[segmentIndex];\n\n\t\t\t// The slot must be empty (even sequence) to fill. Odd means a popper hasn't finished emptying it yet - retry\n\t\t\tlet sequence = Atomics.load(sequences, localIndex);\n\t\t\tif((sequence & 1) !== 0) {\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\t// Take the slot only if the whole versioned length word is unchanged. The version bump makes this fail on any\n\t\t\t// intervening op, so a win guarantees nothing touched the top - and our sequence read above is still valid\n\t\t\tif(Atomics.compareExchange(data, LENGTH_INDEX, packed, packed + VERSION_STEP + 1) !== packed) {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tsegment[localIndex] = value;\n\t\t\t// Publish: bump to odd (full). Safe as a plain store - no other op can claim this slot until the length comes\n\t\t\t// back down to it, which can't happen (its popper is gated on an odd sequence) until this store lands\n\t\t\tAtomics.store(sequences, localIndex, sequence + 1);\n\t\t\treturn newIndex;\n\t\t}\n\t}\n\n\tpop(): number | undefined {\n\t\tlet data = this.firstBlock.data;\n\t\tlet baseSegmentLength = this.cachedBaseSegmentLength;\n\t\t// eslint-disable-next-line no-constant-condition\n\t\twhile(true) {\n\t\t\t// Plain (relaxed) read is enough: it only seeds the versioned CAS below, which rejects any stale value\n\t\t\tlet packed = data[LENGTH_INDEX];\n\t\t\tlet length = packed & LEN_MASK;\n\t\t\tif(length <= 0) {\n\t\t\t\treturn undefined;\n\t\t\t}\n\t\t\tlet oldIndex = length - 1;\n\n\t\t\tlet segmentIndex = 31 - Math.clz32(((oldIndex / baseSegmentLength) | 0) + 1);\n\t\t\tlet localIndex = oldIndex - baseSegmentLength * ((1 << segmentIndex) - 1);\n\t\t\tlet segment = this.cachedSegments[segmentIndex] ?? this.getSegment(segmentIndex);\n\t\t\tlet sequences = this.cachedSequences[segmentIndex];\n\n\t\t\t// The slot must be full (odd sequence) to read. Even means a pusher hasn't finished publishing it yet - retry\n\t\t\tlet sequence = Atomics.load(sequences, localIndex);\n\t\t\tif((sequence & 1) === 0) {\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tif(Atomics.compareExchange(data, LENGTH_INDEX, packed, packed + VERSION_STEP - 1) !== packed) {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tlet value = segment[localIndex];\n\t\t\t// Release the slot: bump to even (empty) so a later push may reuse it\n\t\t\tAtomics.store(sequences, localIndex, sequence + 1);\n\t\t\treturn value;\n\t\t}\n\t}\n\n\tclear() {\n\t\t// Mark every occupied slot empty (bump its odd sequence to the next even) before dropping the length, otherwise a\n\t\t// later push reusing the slot would spin forever on a stale full sequence. Not safe to call concurrently with push/pop.\n\t\tlet baseSegmentLength = this.cachedBaseSegmentLength;\n\t\tlet oldLength = Atomics.load(this.firstBlock.data, LENGTH_INDEX) & LEN_MASK;\n\t\tfor(let i = 0; i < oldLength; i++) {\n\t\t\tlet segmentIndex = 31 - Math.clz32(((i / baseSegmentLength) | 0) + 1);\n\t\t\tlet sequences = this.cachedSequences[segmentIndex] ?? (this.getSegment(segmentIndex), this.cachedSequences[segmentIndex]);\n\t\t\tlet localIndex = i - baseSegmentLength * ((1 << segmentIndex) - 1);\n\t\t\tlet sequence = Atomics.load(sequences, localIndex);\n\t\t\tif((sequence & 1) !== 0) {\n\t\t\t\tAtomics.store(sequences, localIndex, sequence + 1);\n\t\t\t}\n\t\t}\n\t\tAtomics.store(this.firstBlock.data, LENGTH_INDEX, 0);\n\t}\n\n\t*[Symbol.iterator]() {\n\t\tlet totalDataEntries = this.length;\n\n\t\tlet segmentLength = this.baseSegmentLength;\n\t\tlet segmentIndex = 0;\n\t\tlet segment = this.getSegment(segmentIndex);\n\t\tlet segmentDataIndex = 0;\n\t\tfor(let i = 0; i < totalDataEntries; i++) {\n\t\t\tyield segment[segmentDataIndex];\n\t\t\tsegmentDataIndex++;\n\t\t\tif(segmentDataIndex >= segmentLength) {\n\t\t\t\tsegmentIndex++;\n\t\t\t\tsegment = this.getSegment(segmentIndex);\n\t\t\t\tsegmentDataIndex = 0;\n\t\t\t\tsegmentLength = segmentLength * 2;\n\t\t\t}\n\t\t}\n\t}\n\n\tprivate getSegment(segmentIndex: number): T {\n\t\tif(this.cachedSegments[segmentIndex]) {\n\t\t\treturn this.cachedSegments[segmentIndex];\n\t\t}\n\n\t\tlet segmentLength = this.cachedBaseSegmentLength * 2 ** segmentIndex;\n\t\tlet segmentDataBlock: AllocatedMemory | undefined;\n\t\tif(segmentIndex >= this.segmentCount) {\n\t\t\t// Lock when growing so another thread doesn't also setup the same segment\n\t\t\tlock(this.growLock);\n\t\t\ttry {\n\t\t\t\t// Re-check under the lock: another thread may have already appended the segment while we were waiting\n\t\t\t\tif(segmentIndex >= this.segmentCount) {\n\t\t\t\t\t// Double length: the data slots followed by their publish sequences (calloc-zeroed = 0 = empty)\n\t\t\t\t\tsegmentDataBlock = this.memory.allocUI32(segmentLength * 2);\n\t\t\t\t\tstorePointer(this.firstBlock.data, SEGMENT_START_INDEX + segmentIndex, segmentDataBlock.bufferPosition, segmentDataBlock.bufferByteOffset);\n\t\t\t\t\tAtomics.add(this.firstBlock.data, SEGMENT_COUNT_INDEX, 1);\n\t\t\t\t}\n\t\t\t} finally {\n\t\t\t\tunlock(this.growLock);\n\t\t\t}\n\t\t}\n\n\t\tif(!segmentDataBlock) {\n\t\t\tlet pointerNumber = Atomics.load(this.firstBlock.data, SEGMENT_START_INDEX + segmentIndex);\n\t\t\tlet pointer = getPointer(pointerNumber);\n\t\t\tsegmentDataBlock = new AllocatedMemory(this.memory, pointer);\n\t\t}\n\n\t\tlet data: T;\n\t\tswitch(this.type) {\n\t\t\tcase TYPE.int32:\n\t\t\t\tdata = new Int32Array(segmentDataBlock.data.buffer, segmentDataBlock.bufferByteOffset, segmentLength) as T;\n\t\t\t\tbreak;\n\t\t\tcase TYPE.uint32:\n\t\t\t\tdata = new Uint32Array(segmentDataBlock.data.buffer, segmentDataBlock.bufferByteOffset, segmentLength) as T;\n\t\t\t\tbreak;\n\t\t\tcase TYPE.float32:\n\t\t\t\tdata = new Float32Array(segmentDataBlock.data.buffer, segmentDataBlock.bufferByteOffset, segmentLength) as T;\n\t\t\t\tbreak;\n\t\t\tdefault:\n\t\t\t\tthrow new Error(`Unknown data block type ${this.type}`);\n\t\t}\n\t\t// Publish sequences live in the second half of the block, always as Int32 regardless of the data type\n\t\tlet sequenceByteOffset = segmentDataBlock.bufferByteOffset + segmentLength * Uint32Array.BYTES_PER_ELEMENT;\n\t\tthis.cachedSequences[segmentIndex] = new Int32Array(segmentDataBlock.data.buffer, sequenceByteOffset, segmentLength);\n\t\tthis.cachedSegments[segmentIndex] = data;\n\n\t\treturn data;\n\t}\n\n\tfree() {\n\t\tfor(let i = 0; i < this.segmentCount; i++) {\n\t\t\tlet pointerNumber = Atomics.load(this.firstBlock.data, SEGMENT_START_INDEX + i);\n\t\t\tlet pointer = getPointer(pointerNumber);\n\t\t\tlet rawData = new AllocatedMemory(this.memory, pointer);\n\t\t\trawData.free();\n\t\t}\n\t\tif(this.ownsFirstBlock) {\n\t\t\tthis.firstBlock.free();\n\t\t}\n\t}\n\n\tgetSharedMemory(): SharedStackMemory {\n\t\treturn {\n\t\t\tfirstBlock: this.firstBlock.getSharedMemory(),\n\t\t};\n\t}\n}\n\ninterface SharedStackConfig<T extends Uint32Array | Int32Array | Float32Array> {\n\ttype?: TypedArrayConstructor<T>\n\n\tbaseSegmentLength?: number\n\tsegmentCount?: number\n}\ninterface SharedStackMemory {\n\tfirstBlock: SharedAllocatedMemory | AllocatedMemory\n}\n\nexport type { SharedStackConfig, SharedStackMemory };"],"mappings":"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"}
1
+ {"version":3,"file":"shared-stack.js","names":[],"sources":["../src/shared-stack.ts"],"sourcesContent":["import type { SharedAllocatedMemory } from './allocated-memory';\nimport AllocatedMemory from './allocated-memory';\nimport type { TypedArrayConstructor } from './interfaces/typed-array-constructor';\nimport { lock, unlock } from './lock/simple-lock';\nimport type MemoryHeap from './memory-heap';\nimport { getPointer, storePointer } from './utils/pointer';\n\nenum TYPE {\n\tuint32,\n\tint32,\n\tfloat32,\n}\n\nconst LENGTH_INDEX = 0;\nconst TYPE_INDEX = 1;\nconst BASE_SEGMENT_LENGTH_INDEX = 2;\nconst MAX_SEGMENTS_INDEX = 3;\nconst MAX_SEGMENT_LENGTH_INDEX = 4;\nconst SEGMENT_COUNT_INDEX = 5;\nconst GROW_LOCK_INDEX = 6;\nconst SEGMENT_START_INDEX = 7;\n\nconst DEFAULT_BASE_SEGMENT_LENGTH = 4;\n// Default segment budget. With uncapped power-of-two growth these 20 segments spanned 4,194,300 slots, but that ceiling\n// was never reachable: past segment 14 a segment's block outgrows a 1MB buffer and allocation throws. Capping segments at\n// what a buffer holds, the honest capacity of 20 segments is getCapacity(20, base, cap) instead.\nconst DEFAULT_MAX_SEGMENTS = 20;\n\n// The LENGTH word packs an ABA version counter (high bits) with the live count (low LEN_BITS). Both push and pop bump\n// the version on every successful CAS, so the CAS fails on ANY intervening op - the count can never ABA back to the\n// same value with a slot at a different generation. 22 count bits cover the default max length (4,194,300); the\n// remaining 10 version bits (1024) only need to outlast a single thread's load->CAS window.\nconst LEN_BITS = 22;\nconst LEN_MASK = (1 << LEN_BITS) - 1;\nconst MAX_COUNT = LEN_MASK - 1;\n// Adding this to the packed word bumps the version (high bits) by one. Combined with +1/-1 for the count it repacks the\n// whole word in a single add: the count is guarded from overflowing into the version bits, and a version overflow just\n// wraps harmlessly when the value is coerced back to Uint32 on the atomic store.\nconst VERSION_STEP = 1 << LEN_BITS;\n\n// Per-slot publish handshake. Each data slot has a companion sequence number: even means the slot is empty (free to\n// fill) at some lap, odd means the value is published (safe to read). It only ever increases. push/pop read the slot's\n// sequence and bail out (retry) unless it has the parity they need, then CAS the length; because the versioned CAS\n// rejects any intervening op, the sequence read stays valid all the way through the publish store below.\n\nexport default class SharedStack<T extends Uint32Array | Int32Array | Float32Array = Uint32Array> implements Iterable<number> {\n\tstatic readonly BASE_ALLOCATE_COUNT = 8;\n\tstatic readonly DEFAULT_ALLOCATE_COUNT = this.BASE_ALLOCATE_COUNT + DEFAULT_MAX_SEGMENTS;\n\n\t// Largest power-of-two segment length whose block (segmentLength data slots plus an equal run of publish sequences)\n\t// still fits in a single buffer. Segments double until they reach this, then stay flat so a segment can always be\n\t// allocated in one contiguous block. maxAllocationLength is MemoryHeap.maxAllocationLength.\n\tstatic getMaxSegmentLength(maxAllocationLength: number, baseSegmentLength = DEFAULT_BASE_SEGMENT_LENGTH): number {\n\t\tlet segmentLength = baseSegmentLength;\n\t\t// A segment of length L needs a block of 2L u32s, so the next double only fits while 2 * (2L) <= maxAllocationLength\n\t\twhile(segmentLength * 4 <= maxAllocationLength) {\n\t\t\tsegmentLength *= 2;\n\t\t}\n\t\treturn segmentLength;\n\t}\n\n\t// Smallest segment count whose summed capacity covers maxLength. Segments double from baseSegmentLength but never grow\n\t// past maxSegmentLength, so once capped they each add a flat maxSegmentLength. Looped rather than solved in closed form\n\t// to avoid float rounding landing a segment short or over.\n\tstatic getMaxSegments(maxLength: number, baseSegmentLength = DEFAULT_BASE_SEGMENT_LENGTH, maxSegmentLength = Infinity): number {\n\t\tlet segments = 0;\n\t\tlet capacity = 0;\n\t\tlet segmentLength = baseSegmentLength;\n\t\twhile(capacity < maxLength) {\n\t\t\tcapacity += segmentLength;\n\t\t\tsegments++;\n\t\t\tsegmentLength = Math.min(segmentLength * 2, maxSegmentLength);\n\t\t}\n\t\treturn segments;\n\t}\n\t// Total capacity of the first `segments` segments under the same doubling-then-capped growth as getMaxSegments\n\tstatic getCapacity(segments: number, baseSegmentLength = DEFAULT_BASE_SEGMENT_LENGTH, maxSegmentLength = Infinity): number {\n\t\tlet capacity = 0;\n\t\tlet segmentLength = baseSegmentLength;\n\t\tfor(let i = 0; i < segments; i++) {\n\t\t\tcapacity += segmentLength;\n\t\t\tsegmentLength = Math.min(segmentLength * 2, maxSegmentLength);\n\t\t}\n\t\treturn capacity;\n\t}\n\tstatic getAllocateCount(maxLength: number, baseSegmentLength = DEFAULT_BASE_SEGMENT_LENGTH, maxSegmentLength = Infinity): number {\n\t\treturn this.BASE_ALLOCATE_COUNT + this.getMaxSegments(maxLength, baseSegmentLength, maxSegmentLength);\n\t}\n\tprivate memory: MemoryHeap;\n\n\t// Length, Type, Base Segment Length, Max Segments, Max Segment Length, Segment Count, Grow Lock, Segments\n\tprivate firstBlock: AllocatedMemory;\n\tprivate uint16Array: Uint16Array;\n\tprivate growLock: Int32Array;\n\treadonly maxLength: number;\n\n\t// False when firstBlock is a slice of another structure's memory (e.g. inlined in SharedPool); that owner frees it\n\tprivate ownsFirstBlock: boolean;\n\n\tprivate cachedSegments: T[] = [];\n\t// Parallel to cachedSegments: the per-slot publish sequences for each slot of that segment. Populated together with\n\t// the data view in getSegment, so whenever cachedSegments[i] is set cachedSequences[i] is too.\n\tprivate cachedSequences: Int32Array[] = [];\n\tprivate cachedBaseSegmentLength = 0;\n\t// Segment growth caps at cachedMaxSegmentLength. Segments 0..(cachedCapSegmentCount - 1) double geometrically and cover\n\t// the first cachedGeometricCapacity slots; every segment beyond that is a flat cachedMaxSegmentLength. Indexing splits\n\t// on cachedGeometricCapacity: below it the fast log2 mapping, above it a plain linear division into capped segments.\n\tprivate cachedMaxSegmentLength = 0;\n\tprivate cachedCapSegmentCount = 0;\n\tprivate cachedGeometricCapacity = 0;\n\n\tget length(): number {\n\t\treturn Atomics.load(this.firstBlock.data, LENGTH_INDEX) & LEN_MASK;\n\t}\n\tget segmentCount(): number {\n\t\treturn Atomics.load(this.firstBlock.data, SEGMENT_COUNT_INDEX);\n\t}\n\tprivate set segmentCount(value: number) {\n\t\tthis.firstBlock.data[SEGMENT_COUNT_INDEX] = value;\n\t}\n\t\n\tget type(): number {\n\t\treturn this.uint16Array[0];\n\t}\n\tprivate set type(value: number) {\n\t\tAtomics.store(this.uint16Array, 0, value);\n\t}\n\n\tget baseSegmentLength() {\n\t\treturn this.firstBlock.data[BASE_SEGMENT_LENGTH_INDEX];\n\t}\n\tprivate set baseSegmentLength(value: number) {\n\t\tthis.firstBlock.data[BASE_SEGMENT_LENGTH_INDEX] = value;\n\t}\n\tget maxSegments() {\n\t\treturn this.firstBlock.data[MAX_SEGMENTS_INDEX];\n\t}\n\tprivate set maxSegments(value: number) {\n\t\tthis.firstBlock.data[MAX_SEGMENTS_INDEX] = value;\n\t}\n\tget maxSegmentLength() {\n\t\treturn this.firstBlock.data[MAX_SEGMENT_LENGTH_INDEX];\n\t}\n\tprivate set maxSegmentLength(value: number) {\n\t\tthis.firstBlock.data[MAX_SEGMENT_LENGTH_INDEX] = value;\n\t}\n\n\tget pointer() {\n\t\treturn this.firstBlock.pointer;\n\t}\n\n\t// Own internal memory plus every allocated segment. The firstBlock is excluded when inlined in another structure\n\t// (e.g. SharedPool) since that owner already counts it as part of its own internal memory.\n\tget usedMemory(): number {\n\t\tlet total = this.ownsFirstBlock ? this.firstBlock.usedMemory : 0;\n\t\tfor(let i = 0; i < this.segmentCount; i++) {\n\t\t\tlet pointer = getPointer(Atomics.load(this.firstBlock.data, SEGMENT_START_INDEX + i));\n\t\t\ttotal += new AllocatedMemory(this.memory, pointer).usedMemory;\n\t\t}\n\t\treturn total;\n\t}\n\n\tconstructor(memory: MemoryHeap, config?: SharedStackConfig<T> | SharedStackMemory | SharedStackMemory & SharedStackConfig<T>) {\n\t\tthis.memory = memory;\n\n\t\tif(config && 'firstBlock' in config) {\n\t\t\t// An AllocatedMemory instance is a caller-reserved slice (e.g. inlined in SharedPool); a plain pointer is the\n\t\t\t// serialized clone path. Taking the instance directly keeps us off the pointer-resolving construction branch.\n\t\t\tthis.firstBlock = config.firstBlock instanceof AllocatedMemory ? config.firstBlock : new AllocatedMemory(memory, config.firstBlock);\n\t\t\tthis.uint16Array = new Uint16Array(this.firstBlock.data.buffer, this.firstBlock.bufferByteOffset + TYPE_INDEX * Uint32Array.BYTES_PER_ELEMENT, 2);\n\n\t\t\t// Memory passed together with config means the caller reserved the firstBlock slot for us to initialize in place\n\t\t\tif('type' in config || 'baseSegmentLength' in config || 'maxLength' in config) {\n\t\t\t\tthis.initialize(config);\n\t\t\t\tthis.ownsFirstBlock = false;\n\t\t\t} else {\n\t\t\t\tthis.ownsFirstBlock = true;\n\t\t\t}\n\t\t} else {\n\t\t\tlet baseSegmentLength = config?.baseSegmentLength ?? DEFAULT_BASE_SEGMENT_LENGTH;\n\t\t\tlet maxSegmentLength = this.resolveMaxSegmentLength(baseSegmentLength, config?.maxSegmentLength);\n\t\t\tlet maxSegments = this.resolveMaxSegments(config?.maxLength, baseSegmentLength, maxSegmentLength);\n\n\t\t\tthis.firstBlock = memory.allocUI32(SharedStack.BASE_ALLOCATE_COUNT + maxSegments);\n\t\t\tthis.uint16Array = new Uint16Array(this.firstBlock.data.buffer, this.firstBlock.bufferByteOffset + TYPE_INDEX * Uint32Array.BYTES_PER_ELEMENT, 2);\n\t\t\tthis.ownsFirstBlock = true;\n\n\t\t\tthis.initialize(config);\n\t\t}\n\n\t\tthis.growLock = new Int32Array(this.firstBlock.data.buffer, this.firstBlock.bufferByteOffset + GROW_LOCK_INDEX * Uint32Array.BYTES_PER_ELEMENT, 1);\n\t\tthis.cachedBaseSegmentLength = this.baseSegmentLength;\n\t\tthis.cachedMaxSegmentLength = this.maxSegmentLength;\n\t\t// Segments 0..(K-1) double geometrically up to the cap, where K = log2(cap / base) + 1\n\t\tthis.cachedCapSegmentCount = 32 - Math.clz32(((this.cachedMaxSegmentLength / this.cachedBaseSegmentLength) | 0));\n\t\tthis.cachedGeometricCapacity = this.cachedBaseSegmentLength * ((1 << this.cachedCapSegmentCount) - 1);\n\t\tthis.maxLength = SharedStack.getCapacity(this.maxSegments, this.cachedBaseSegmentLength, this.cachedMaxSegmentLength);\n\t\tif(this.maxLength > MAX_COUNT) {\n\t\t\t// The length word reserves LEN_BITS for the count and the rest for the ABA version tag\n\t\t\tthrow new Error(`SharedStack maxLength ${this.maxLength} exceeds the ${MAX_COUNT} the versioned length counter can address`);\n\t\t}\n\t}\n\n\t// Cap on segment length: the largest a single buffer can hold, optionally lowered to a caller-requested value (floored\n\t// to a power-of-two multiple of the base). Beyond this segments stop doubling and stay flat so each still fits a buffer.\n\tprivate resolveMaxSegmentLength(baseSegmentLength: number, requested?: number): number {\n\t\tlet cap = SharedStack.getMaxSegmentLength(this.memory.maxAllocationLength, baseSegmentLength);\n\t\tif(requested) {\n\t\t\tlet floored = baseSegmentLength;\n\t\t\twhile(floored * 2 <= requested) {\n\t\t\t\tfloored *= 2;\n\t\t\t}\n\t\t\tcap = Math.min(cap, floored);\n\t\t}\n\t\treturn cap;\n\t}\n\n\t// Segments needed to cover maxLength (or the default budget when none is given), but never more than the segment-pointer\n\t// spine can hold in a single buffer. Once capped, segments no longer double, so a huge maxLength on a small buffer is\n\t// honestly bounded here instead of blowing up later when the spine (or a segment) can't be allocated.\n\tprivate resolveMaxSegments(maxLength: number | undefined, baseSegmentLength: number, maxSegmentLength: number): number {\n\t\tlet needed = maxLength === undefined\n\t\t\t? DEFAULT_MAX_SEGMENTS\n\t\t\t: SharedStack.getMaxSegments(maxLength, baseSegmentLength, maxSegmentLength);\n\t\tlet spineLimit = this.memory.maxAllocationLength - SharedStack.BASE_ALLOCATE_COUNT;\n\t\treturn Math.min(needed, spineLimit);\n\t}\n\n\tprivate initialize(config?: SharedStackConfig<T>) {\n\t\tlet baseSegmentLength = config?.baseSegmentLength ?? DEFAULT_BASE_SEGMENT_LENGTH;\n\t\tlet maxSegmentLength = this.resolveMaxSegmentLength(baseSegmentLength, config?.maxSegmentLength);\n\t\t// Each segment block holds the data slots followed by an equal-length region of publish sequences\n\t\tlet firstSegmentBlock = this.memory.allocUI32(baseSegmentLength * 2);\n\t\tstorePointer(this.firstBlock.data, SEGMENT_START_INDEX, firstSegmentBlock.bufferPosition, firstSegmentBlock.bufferByteOffset);\n\t\tthis.baseSegmentLength = baseSegmentLength;\n\t\tthis.maxSegmentLength = maxSegmentLength;\n\t\tthis.maxSegments = this.resolveMaxSegments(config?.maxLength, baseSegmentLength, maxSegmentLength);\n\t\tthis.segmentCount = 1;\n\n\t\tconst type = config?.type ?? Uint32Array;\n\t\tif(type === Uint32Array) {\n\t\t\tthis.type = TYPE.uint32;\n\t\t// @ts-expect-error\n\t\t} else if(type === Int32Array) {\n\t\t\tthis.type = TYPE.int32;\n\t\t// @ts-expect-error\n\t\t} else if(type === Float32Array) {\n\t\t\tthis.type = TYPE.float32;\n\t\t}\n\t}\n\n\tat(index: number): number {\n\t\tlet length = this.length;\n\t\tif(index >= length || index < 0) {\n\t\t\tthrow new Error(`${index} is out of bounds ${length}`);\n\t\t}\n\n\t\tlet geometricCapacity = this.cachedGeometricCapacity;\n\t\tlet segmentIndex: number;\n\t\tlet localIndex: number;\n\t\tif(index < geometricCapacity) {\n\t\t\tlet baseSegmentLength = this.cachedBaseSegmentLength;\n\t\t\tsegmentIndex = 31 - Math.clz32(((index / baseSegmentLength) | 0) + 1);\n\t\t\tlocalIndex = index - baseSegmentLength * ((1 << segmentIndex) - 1);\n\t\t} else {\n\t\t\tlet maxSegmentLength = this.cachedMaxSegmentLength;\n\t\t\tlet offset = index - geometricCapacity;\n\t\t\tlet extra = (offset / maxSegmentLength) | 0;\n\t\t\tsegmentIndex = this.cachedCapSegmentCount + extra;\n\t\t\tlocalIndex = offset - extra * maxSegmentLength;\n\t\t}\n\t\tlet segment = this.cachedSegments[segmentIndex] ?? this.getSegment(segmentIndex);\n\t\treturn segment[localIndex];\n\t}\n\n\tpush(value: number): number {\n\t\tlet data = this.firstBlock.data;\n\t\tlet geometricCapacity = this.cachedGeometricCapacity;\n\t\t// eslint-disable-next-line no-constant-condition\n\t\twhile(true) {\n\t\t\t// Plain (relaxed) read is enough: it only seeds the versioned CAS below, which rejects any stale value\n\t\t\tlet packed = data[LENGTH_INDEX];\n\t\t\tlet newIndex = packed & LEN_MASK;\n\t\t\tif(newIndex >= this.maxLength) {\n\t\t\t\tthrow new Error(`${newIndex + 1} is out of bounds ${this.maxLength}`);\n\t\t\t}\n\n\t\t\tlet segmentIndex: number;\n\t\t\tlet localIndex: number;\n\t\t\tif(newIndex < geometricCapacity) {\n\t\t\t\tlet baseSegmentLength = this.cachedBaseSegmentLength;\n\t\t\t\tsegmentIndex = 31 - Math.clz32(((newIndex / baseSegmentLength) | 0) + 1);\n\t\t\t\tlocalIndex = newIndex - baseSegmentLength * ((1 << segmentIndex) - 1);\n\t\t\t} else {\n\t\t\t\tlet maxSegmentLength = this.cachedMaxSegmentLength;\n\t\t\t\tlet offset = newIndex - geometricCapacity;\n\t\t\t\tlet extra = (offset / maxSegmentLength) | 0;\n\t\t\t\tsegmentIndex = this.cachedCapSegmentCount + extra;\n\t\t\t\tlocalIndex = offset - extra * maxSegmentLength;\n\t\t\t}\n\t\t\tlet segment = this.cachedSegments[segmentIndex] ?? this.getSegment(segmentIndex);\n\t\t\tlet sequences = this.cachedSequences[segmentIndex];\n\n\t\t\t// The slot must be empty (even sequence) to fill. Odd means a popper hasn't finished emptying it yet - retry\n\t\t\tlet sequence = Atomics.load(sequences, localIndex);\n\t\t\tif((sequence & 1) !== 0) {\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\t// Take the slot only if the whole versioned length word is unchanged. The version bump makes this fail on any\n\t\t\t// intervening op, so a win guarantees nothing touched the top - and our sequence read above is still valid\n\t\t\tif(Atomics.compareExchange(data, LENGTH_INDEX, packed, packed + VERSION_STEP + 1) !== packed) {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tsegment[localIndex] = value;\n\t\t\t// Publish: bump to odd (full). Safe as a plain store - no other op can claim this slot until the length comes\n\t\t\t// back down to it, which can't happen (its popper is gated on an odd sequence) until this store lands\n\t\t\tAtomics.store(sequences, localIndex, sequence + 1);\n\t\t\treturn newIndex;\n\t\t}\n\t}\n\n\tpop(): number | undefined {\n\t\tlet data = this.firstBlock.data;\n\t\tlet geometricCapacity = this.cachedGeometricCapacity;\n\t\t// eslint-disable-next-line no-constant-condition\n\t\twhile(true) {\n\t\t\t// Plain (relaxed) read is enough: it only seeds the versioned CAS below, which rejects any stale value\n\t\t\tlet packed = data[LENGTH_INDEX];\n\t\t\tlet length = packed & LEN_MASK;\n\t\t\tif(length <= 0) {\n\t\t\t\treturn undefined;\n\t\t\t}\n\t\t\tlet oldIndex = length - 1;\n\n\t\t\tlet segmentIndex: number;\n\t\t\tlet localIndex: number;\n\t\t\tif(oldIndex < geometricCapacity) {\n\t\t\t\tlet baseSegmentLength = this.cachedBaseSegmentLength;\n\t\t\t\tsegmentIndex = 31 - Math.clz32(((oldIndex / baseSegmentLength) | 0) + 1);\n\t\t\t\tlocalIndex = oldIndex - baseSegmentLength * ((1 << segmentIndex) - 1);\n\t\t\t} else {\n\t\t\t\tlet maxSegmentLength = this.cachedMaxSegmentLength;\n\t\t\t\tlet offset = oldIndex - geometricCapacity;\n\t\t\t\tlet extra = (offset / maxSegmentLength) | 0;\n\t\t\t\tsegmentIndex = this.cachedCapSegmentCount + extra;\n\t\t\t\tlocalIndex = offset - extra * maxSegmentLength;\n\t\t\t}\n\t\t\tlet segment = this.cachedSegments[segmentIndex] ?? this.getSegment(segmentIndex);\n\t\t\tlet sequences = this.cachedSequences[segmentIndex];\n\n\t\t\t// The slot must be full (odd sequence) to read. Even means a pusher hasn't finished publishing it yet - retry\n\t\t\tlet sequence = Atomics.load(sequences, localIndex);\n\t\t\tif((sequence & 1) === 0) {\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tif(Atomics.compareExchange(data, LENGTH_INDEX, packed, packed + VERSION_STEP - 1) !== packed) {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tlet value = segment[localIndex];\n\t\t\t// Release the slot: bump to even (empty) so a later push may reuse it\n\t\t\tAtomics.store(sequences, localIndex, sequence + 1);\n\t\t\treturn value;\n\t\t}\n\t}\n\n\tclear() {\n\t\t// Mark every occupied slot empty (bump its odd sequence to the next even) before dropping the length, otherwise a\n\t\t// later push reusing the slot would spin forever on a stale full sequence. Not safe to call concurrently with push/pop.\n\t\tlet baseSegmentLength = this.cachedBaseSegmentLength;\n\t\tlet geometricCapacity = this.cachedGeometricCapacity;\n\t\tlet maxSegmentLength = this.cachedMaxSegmentLength;\n\t\tlet oldLength = Atomics.load(this.firstBlock.data, LENGTH_INDEX) & LEN_MASK;\n\t\tfor(let i = 0; i < oldLength; i++) {\n\t\t\tlet segmentIndex: number;\n\t\t\tlet localIndex: number;\n\t\t\tif(i < geometricCapacity) {\n\t\t\t\tsegmentIndex = 31 - Math.clz32(((i / baseSegmentLength) | 0) + 1);\n\t\t\t\tlocalIndex = i - baseSegmentLength * ((1 << segmentIndex) - 1);\n\t\t\t} else {\n\t\t\t\tlet offset = i - geometricCapacity;\n\t\t\t\tlet extra = (offset / maxSegmentLength) | 0;\n\t\t\t\tsegmentIndex = this.cachedCapSegmentCount + extra;\n\t\t\t\tlocalIndex = offset - extra * maxSegmentLength;\n\t\t\t}\n\t\t\tlet sequences = this.cachedSequences[segmentIndex] ?? (this.getSegment(segmentIndex), this.cachedSequences[segmentIndex]);\n\t\t\tlet sequence = Atomics.load(sequences, localIndex);\n\t\t\tif((sequence & 1) !== 0) {\n\t\t\t\tAtomics.store(sequences, localIndex, sequence + 1);\n\t\t\t}\n\t\t}\n\t\tAtomics.store(this.firstBlock.data, LENGTH_INDEX, 0);\n\t}\n\n\t*[Symbol.iterator]() {\n\t\tlet totalDataEntries = this.length;\n\n\t\tlet maxSegmentLength = this.cachedMaxSegmentLength;\n\t\tlet segmentLength = this.baseSegmentLength;\n\t\tlet segmentIndex = 0;\n\t\tlet segment = this.getSegment(segmentIndex);\n\t\tlet segmentDataIndex = 0;\n\t\tfor(let i = 0; i < totalDataEntries; i++) {\n\t\t\tyield segment[segmentDataIndex];\n\t\t\tsegmentDataIndex++;\n\t\t\tif(segmentDataIndex >= segmentLength) {\n\t\t\t\tsegmentIndex++;\n\t\t\t\tsegment = this.getSegment(segmentIndex);\n\t\t\t\tsegmentDataIndex = 0;\n\t\t\t\tsegmentLength = Math.min(segmentLength * 2, maxSegmentLength);\n\t\t\t}\n\t\t}\n\t}\n\n\tprivate getSegment(segmentIndex: number): T {\n\t\tif(this.cachedSegments[segmentIndex]) {\n\t\t\treturn this.cachedSegments[segmentIndex];\n\t\t}\n\n\t\t// Geometric while below the cap, then flat so the block always fits a single buffer\n\t\tlet segmentLength = segmentIndex < this.cachedCapSegmentCount\n\t\t\t? this.cachedBaseSegmentLength << segmentIndex\n\t\t\t: this.cachedMaxSegmentLength;\n\t\tlet segmentDataBlock: AllocatedMemory | undefined;\n\t\tif(segmentIndex >= this.segmentCount) {\n\t\t\t// Lock when growing so another thread doesn't also setup the same segment\n\t\t\tlock(this.growLock);\n\t\t\ttry {\n\t\t\t\t// Re-check under the lock: another thread may have already appended the segment while we were waiting\n\t\t\t\tif(segmentIndex >= this.segmentCount) {\n\t\t\t\t\t// Double length: the data slots followed by their publish sequences (calloc-zeroed = 0 = empty)\n\t\t\t\t\tsegmentDataBlock = this.memory.allocUI32(segmentLength * 2);\n\t\t\t\t\tstorePointer(this.firstBlock.data, SEGMENT_START_INDEX + segmentIndex, segmentDataBlock.bufferPosition, segmentDataBlock.bufferByteOffset);\n\t\t\t\t\tAtomics.add(this.firstBlock.data, SEGMENT_COUNT_INDEX, 1);\n\t\t\t\t}\n\t\t\t} finally {\n\t\t\t\tunlock(this.growLock);\n\t\t\t}\n\t\t}\n\n\t\tif(!segmentDataBlock) {\n\t\t\tlet pointerNumber = Atomics.load(this.firstBlock.data, SEGMENT_START_INDEX + segmentIndex);\n\t\t\tlet pointer = getPointer(pointerNumber);\n\t\t\tsegmentDataBlock = new AllocatedMemory(this.memory, pointer);\n\t\t}\n\n\t\tlet data: T;\n\t\tswitch(this.type) {\n\t\t\tcase TYPE.int32:\n\t\t\t\tdata = new Int32Array(segmentDataBlock.data.buffer, segmentDataBlock.bufferByteOffset, segmentLength) as T;\n\t\t\t\tbreak;\n\t\t\tcase TYPE.uint32:\n\t\t\t\tdata = new Uint32Array(segmentDataBlock.data.buffer, segmentDataBlock.bufferByteOffset, segmentLength) as T;\n\t\t\t\tbreak;\n\t\t\tcase TYPE.float32:\n\t\t\t\tdata = new Float32Array(segmentDataBlock.data.buffer, segmentDataBlock.bufferByteOffset, segmentLength) as T;\n\t\t\t\tbreak;\n\t\t\tdefault:\n\t\t\t\tthrow new Error(`Unknown data block type ${this.type}`);\n\t\t}\n\t\t// Publish sequences live in the second half of the block, always as Int32 regardless of the data type\n\t\tlet sequenceByteOffset = segmentDataBlock.bufferByteOffset + segmentLength * Uint32Array.BYTES_PER_ELEMENT;\n\t\tthis.cachedSequences[segmentIndex] = new Int32Array(segmentDataBlock.data.buffer, sequenceByteOffset, segmentLength);\n\t\tthis.cachedSegments[segmentIndex] = data;\n\n\t\treturn data;\n\t}\n\n\tfree() {\n\t\tfor(let i = 0; i < this.segmentCount; i++) {\n\t\t\tlet pointerNumber = Atomics.load(this.firstBlock.data, SEGMENT_START_INDEX + i);\n\t\t\tlet pointer = getPointer(pointerNumber);\n\t\t\tlet rawData = new AllocatedMemory(this.memory, pointer);\n\t\t\trawData.free();\n\t\t}\n\t\tif(this.ownsFirstBlock) {\n\t\t\tthis.firstBlock.free();\n\t\t}\n\t}\n\n\tgetSharedMemory(): SharedStackMemory {\n\t\treturn {\n\t\t\tfirstBlock: this.firstBlock.getSharedMemory(),\n\t\t};\n\t}\n}\n\ninterface SharedStackConfig<T extends Uint32Array | Int32Array | Float32Array> {\n\ttype?: TypedArrayConstructor<T>\n\n\tbaseSegmentLength?: number\n\tmaxLength?: number\n\t// Cap on how large a single segment may grow. Defaults to the largest a buffer can hold; a smaller value is floored to\n\t// a power-of-two multiple of baseSegmentLength. Beyond the cap segments stay flat instead of doubling.\n\tmaxSegmentLength?: number\n}\ninterface SharedStackMemory {\n\tfirstBlock: SharedAllocatedMemory | AllocatedMemory\n}\n\nexport type { SharedStackConfig, SharedStackMemory };"],"mappings":"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"}
@@ -14,6 +14,7 @@ export default class SharedVector<T extends Uint32Array | Int32Array | Float32Ar
14
14
  get bufferLength(): number;
15
15
  private set bufferLength(value);
16
16
  get pointer(): number;
17
+ get usedMemory(): number;
17
18
  private cachedFullDataBlock?;
18
19
  private cachedPointer;
19
20
  constructor(memory: MemoryHeap, config?: SharedVectorConfig<T> | SharedVectorMemory | (SharedVectorMemory & SharedVectorConfig<T>));
@@ -30,6 +30,10 @@ var a = 0, o = 1, s = 2, c = 3, l = 4, u = class u {
30
30
  get pointer() {
31
31
  return this.firstBlock.pointer;
32
32
  }
33
+ get usedMemory() {
34
+ let e = new i(this.memory, t(this.firstBlock.data, a));
35
+ return this.firstBlock.usedMemory + e.usedMemory;
36
+ }
33
37
  cachedFullDataBlock;
34
38
  cachedPointer;
35
39
  constructor(e, t) {
@@ -1 +1 @@
1
- {"version":3,"file":"shared-vector.js","names":[],"sources":["../src/shared-vector.ts"],"sourcesContent":["import type { SharedAllocatedMemory } from './allocated-memory';\nimport AllocatedMemory from './allocated-memory';\nimport type { TypedArrayConstructor } from './interfaces/typed-array-constructor';\nimport type MemoryHeap from './memory-heap';\nimport { getPointer, loadPointer, storePointer, storeRawPointer } from './utils/pointer';\n\nenum TYPE {\n\tuint32,\n\tint32,\n\tfloat32,\n}\n\nconst VECTOR_INDEX = 0;\nconst LENGTH_INDEX = 1;\nconst BUFFER_LENGTH_INDEX = 2;\nconst TYPE_INDEX = 3;\nconst DEFAULT_SIZE = 4;\nexport default class SharedVector<T extends Uint32Array | Int32Array | Float32Array = Uint32Array> implements Iterable<T> {\n\tstatic readonly ALLOCATE_COUNT = 4;\n\tprivate memory: MemoryHeap;\n\n\t// Pointer, List Length, Buffer Length, Type/DataLength\n\tprivate firstBlock: AllocatedMemory;\n\tprivate uint16Array: Uint16Array;\n\n\tget length(): number {\n\t\treturn Atomics.load(this.firstBlock.data, LENGTH_INDEX);\n\t}\n\t\n\tget type(): number {\n\t\treturn this.uint16Array[0];\n\t}\n\tprivate set type(value: number) {\n\t\tAtomics.store(this.uint16Array, 0, value);\n\t}\n\tget dataLength(): number {\n\t\t// Can technically be initialized by passing memory without actually every being called - need to make sure dataLength is always at least one\n\t\treturn this.uint16Array[1] || 1;\n\t}\n\tprivate set dataLength(value: number) {\n\t\tAtomics.store(this.uint16Array, 1, value);\n\t}\n\n\tget bufferLength(): number {\n\t\treturn Atomics.load(this.firstBlock.data, BUFFER_LENGTH_INDEX);\n\t}\n\tprivate set bufferLength(value: number) {\n\t\tAtomics.store(this.firstBlock.data, BUFFER_LENGTH_INDEX, value);\n\t}\n\n\tget pointer() {\n\t\treturn this.firstBlock.pointer;\n\t}\n\n\tprivate cachedFullDataBlock?: T;\n\tprivate cachedPointer: number;\n\n\tconstructor(memory: MemoryHeap, config?: SharedVectorConfig<T> | SharedVectorMemory | SharedVectorMemory & SharedVectorConfig<T>) {\n\t\tthis.memory = memory;\n\n\t\tif(config && 'firstBlock' in config) {\n\t\t\tthis.firstBlock = new AllocatedMemory(memory, config.firstBlock);\n\t\t\tthis.uint16Array = new Uint16Array(this.firstBlock.data.buffer, this.firstBlock.bufferByteOffset + TYPE_INDEX * Uint32Array.BYTES_PER_ELEMENT, 2);\n\n\t\t\t// Pre-allocating memory and setting up in specific memory location\n\t\t\tif('type' in config || 'dataLength' in config) {\n\t\t\t\tconst bufferLength = config.bufferLength ?? DEFAULT_SIZE;\n\t\t\t\tlet dataBlock = memory.allocUI32(bufferLength * (config.dataLength ?? 1));\n\t\t\t\tstorePointer(this.firstBlock.data, VECTOR_INDEX, dataBlock.bufferPosition, dataBlock.bufferByteOffset);\n\t\t\t\tthis.bufferLength = bufferLength;\n\t\t\t\tthis.dataLength = (config.dataLength ?? 1);\n\t\t\t}\n\t\t\tif('type' in config) {\n\t\t\t\tconst type = config?.type ?? Uint32Array;\n\t\t\t\tif(type === Uint32Array) {\n\t\t\t\t\tthis.type = TYPE.uint32;\n\t\t\t\t// @ts-expect-error\n\t\t\t\t} else if(type === Int32Array) {\n\t\t\t\t\tthis.type = TYPE.int32;\n\t\t\t\t// @ts-expect-error\n\t\t\t\t} else if(type === Float32Array) {\n\t\t\t\t\tthis.type = TYPE.float32;\n\t\t\t\t}\n\t\t\t}\n\t\t} else {\n\t\t\tthis.firstBlock = memory.allocUI32(SharedVector.ALLOCATE_COUNT);\n\t\t\tthis.uint16Array = new Uint16Array(this.firstBlock.data.buffer, this.firstBlock.bufferByteOffset + TYPE_INDEX * Uint32Array.BYTES_PER_ELEMENT, 2);\n\n\t\t\tlet dataLength = config?.dataLength ?? 1;\n\t\t\tlet bufferLength = config?.bufferLength ?? DEFAULT_SIZE;\n\t\t\tlet dataBlock = memory.allocUI32(bufferLength * dataLength);\n\t\t\tstorePointer(this.firstBlock.data, VECTOR_INDEX, dataBlock.bufferPosition, dataBlock.bufferByteOffset);\n\t\t\tthis.bufferLength = bufferLength;\n\n\t\t\tconst type = config?.type ?? Uint32Array;\n\t\t\tif(type === Uint32Array) {\n\t\t\t\tthis.type = TYPE.uint32;\n\t\t\t// @ts-expect-error\n\t\t\t} else if(type === Int32Array) {\n\t\t\t\tthis.type = TYPE.int32;\n\t\t\t// @ts-expect-error\n\t\t\t} else if(type === Float32Array) {\n\t\t\t\tthis.type = TYPE.float32;\n\t\t\t}\n\t\t\tthis.dataLength = dataLength;\n\t\t}\n\n\t\tthis.cachedPointer = this.firstBlock.data[0];\n\t\tthis.cachedFullDataBlock = this.getFullDataBlock();\n\t}\n\n\tat(index: number): T {\n\t\tlet length = this.length;\n\t\tif(index >= length || index < 0) {\n\t\t\tthrow new Error(`${index} is out of bounds ${length}`);\n\t\t}\n\n\t\tlet dataBlock = this.getFullDataBlock();\n\t\treturn this.getDataBlock(dataBlock, index);\n\t}\n\tget(index: number, dataIndex = 0): number {\n\t\tif(dataIndex >= this.dataLength) {\n\t\t\tthrow new Error(`${dataIndex} is out of dataLength bounds ${this.dataLength}`);\n\t\t} else if(index >= this.length || index < 0) {\n\t\t\tthrow new Error(`${index} is out of bounds ${this.length}`);\n\t\t}\n\n\t\tlet dataBlock = this.getFullDataBlock();\n\t\treturn dataBlock[index * this.dataLength + dataIndex];\n\t}\n\n\tpush(values: number | Array<number>): number {\n\t\tlet dataLength = this.dataLength;\n\t\tconst isSingleNumber = typeof values === 'number';\n\t\tif(!isSingleNumber && values.length > dataLength) {\n\t\t\tthrow new Error(`Can't insert ${values.length} array into shared list of ${dataLength} dataLength`);\n\t\t}\n\n\t\tlet dataBlock = this.getFullDataBlock();\n\t\tlet currentLength = this.length;\n\t\tlet startIndex = dataLength * currentLength;\n\t\tif(isSingleNumber) {\n\t\t\tdataBlock[startIndex] = values;\n\t\t} else {\n\t\t\tdataBlock.set(values, startIndex);\n\t\t}\n\n\t\tlet newLength = Atomics.add(this.firstBlock.data, LENGTH_INDEX, 1) + 1;\n\t\tif(newLength >= this.bufferLength) {\n\t\t\tthis.growBuffer();\n\t\t}\n\n\t\treturn currentLength;\n\t}\n\n\tpop(): T {\n\t\tlet oldLength = Atomics.sub(this.firstBlock.data, LENGTH_INDEX, 1);\n\n\t\tlet dataBlock = this.getFullDataBlock();\n\t\treturn this.getDataBlock(dataBlock, oldLength - 1);\n\t}\n\n\t// Returns the first number irregardless of dataLength - faster way to pop if you don't care about the rest of the data block\n\tpopNumber(): number {\n\t\tconst oldLength = Atomics.sub(this.firstBlock.data, LENGTH_INDEX, 1);\n\t\tconst dataBlock = this.getFullDataBlock();\n\t\treturn dataBlock[(oldLength - 1) * this.dataLength];\n\t}\n\n\tdeleteIndex(index: number) {\n\t\tlet length = this.length;\n\t\tif(index >= length || index < 0) {\n\t\t\tthrow new Error(`${index} is out of bounds ${length}`);\n\t\t}\n\n\t\tlet dataLength = this.dataLength;\n\t\tlet dataBlock = this.getFullDataBlock();\n\t\tfor(let i = index; i < length; i++) {\n\t\t\tfor(let j = 0; j < dataLength; j++) {\n\t\t\t\tdataBlock[i * dataLength + j] = dataBlock[(i + 1) * dataLength + j];\n\t\t\t}\n\t\t}\n\n\t\tAtomics.sub(this.firstBlock.data, LENGTH_INDEX, 1);\n\t}\n\n\tclear() {\n\t\tthis.firstBlock.data[LENGTH_INDEX] = 0;\n\t}\n\n\t*[Symbol.iterator]() {\n\t\tlet dataBlock = this.getFullDataBlock();\n\t\tlet dataLength = this.dataLength;\n\t\tlet totalDataEntries = this.length * dataLength;\n\n\t\tfor(let i = 0; i < totalDataEntries; i += dataLength) {\n\t\t\tyield dataBlock.subarray(i, i + dataLength) as T;\n\t\t}\n\t}\n\n\t// Significantly faster but won't be thread safe if anything is removing/adding to it during iteration\n\tgetCurrentArray(): T {\n\t\tlet dataBlock = this.getFullDataBlock();\n\t\treturn dataBlock.subarray(0, this.length * this.dataLength) as T;\n\t}\n\n\tprivate getFullDataBlock(): T {\n\t\tlet pointerNumber = Atomics.load(this.firstBlock.data, VECTOR_INDEX);\n\t\tif(this.cachedPointer === pointerNumber && this.cachedFullDataBlock) {\n\t\t\treturn this.cachedFullDataBlock;\n\t\t}\n\n\t\tlet pointer = getPointer(pointerNumber);\n\t\tlet rawData = new AllocatedMemory(this.memory, pointer);\n\n\t\tlet data: T;\n\t\tswitch(this.type) {\n\t\t\tcase TYPE.int32:\n\t\t\t\tdata = new Int32Array(rawData.data.buffer, rawData.bufferByteOffset, this.dataLength * this.bufferLength) as T;\n\t\t\t\tbreak;\n\t\t\tcase TYPE.uint32:\n\t\t\t\tdata = new Uint32Array(rawData.data.buffer, rawData.bufferByteOffset, this.dataLength * this.bufferLength) as T;\n\t\t\t\tbreak;\n\t\t\tcase TYPE.float32:\n\t\t\t\tdata = new Float32Array(rawData.data.buffer, rawData.bufferByteOffset, this.dataLength * this.bufferLength) as T;\n\t\t\t\tbreak;\n\t\t\tdefault:\n\t\t\t\tthrow new Error(`Unknown data block type ${this.type}`);\n\t\t}\n\n\t\tthis.cachedPointer = pointerNumber;\n\t\tthis.cachedFullDataBlock = data;\n\n\t\treturn data;\n\t}\n\tprivate getDataBlock(rawData: T, index: number): T {\n\t\tconst start = index * this.dataLength;\n\t\treturn rawData.subarray(start, start + this.dataLength) as T;\n\t}\n\n\tprivate growBuffer() {\n\t\tlet oldBufferLength = this.bufferLength;\n\t\tlet newBufferLength = oldBufferLength * 2;\n\t\tlet dataLength = this.dataLength;\n\n\t\tlet oldPointer = loadPointer(this.firstBlock.data, VECTOR_INDEX);\n\t\tlet oldDataMemory = new AllocatedMemory(this.memory, oldPointer);\n\t\tlet oldDataBlock = this.getFullDataBlock();\n\t\tlet newDataBlock = this.memory.allocUI32(newBufferLength * dataLength);\n\n\t\tlet newData: T;\n\t\tswitch(this.type) {\n\t\t\tcase TYPE.int32:\n\t\t\t\tnewData = new Int32Array(newDataBlock.data.buffer, newDataBlock.bufferByteOffset, dataLength * newBufferLength) as T;\n\t\t\t\tbreak;\n\t\t\tcase TYPE.uint32:\n\t\t\t\tnewData = new Uint32Array(newDataBlock.data.buffer, newDataBlock.bufferByteOffset, dataLength * newBufferLength) as T;\n\t\t\t\tbreak;\n\t\t\tcase TYPE.float32:\n\t\t\t\tnewData = new Float32Array(newDataBlock.data.buffer, newDataBlock.bufferByteOffset, dataLength * newBufferLength) as T;\n\t\t\t\tbreak;\n\t\t\tdefault:\n\t\t\t\tthrow new Error(`Unknown data block type ${this.type}`);\n\t\t}\n\n\t\t// Copy old buffer into new buffer\n\t\tnewData.set(oldDataBlock);\n\n\t\tconst newPointer = newDataBlock.pointer;\n\t\tstoreRawPointer(this.firstBlock.data, VECTOR_INDEX, newPointer);\n\t\tthis.bufferLength = newBufferLength;\n\t\toldDataMemory.free();\n\n\t\tthis.cachedPointer = newPointer;\n\t\tthis.cachedFullDataBlock = newData;\n\t}\n\n\tfree() {\n\t\tlet pointer = loadPointer(this.firstBlock.data, VECTOR_INDEX);\n\t\tlet dataMemory = new AllocatedMemory(this.memory, pointer);\n\n\t\tdataMemory.free();\n\t\tthis.firstBlock.free();\n\t}\n\n\tgetSharedMemory(): SharedVectorMemory {\n\t\treturn {\n\t\t\tfirstBlock: this.firstBlock.getSharedMemory(),\n\t\t};\n\t}\n}\n\ninterface SharedVectorConfig<T extends Uint32Array | Int32Array | Float32Array> {\n\ttype?: TypedArrayConstructor<T>\n\tdataLength?: number\n\n\tbufferLength?: number\n}\ninterface SharedVectorMemory {\n\tfirstBlock: SharedAllocatedMemory\n}\n\nexport type { SharedVectorConfig, SharedVectorMemory };"],"mappings":"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"}
1
+ {"version":3,"file":"shared-vector.js","names":[],"sources":["../src/shared-vector.ts"],"sourcesContent":["import type { SharedAllocatedMemory } from './allocated-memory';\nimport AllocatedMemory from './allocated-memory';\nimport type { TypedArrayConstructor } from './interfaces/typed-array-constructor';\nimport type MemoryHeap from './memory-heap';\nimport { getPointer, loadPointer, storePointer, storeRawPointer } from './utils/pointer';\n\nenum TYPE {\n\tuint32,\n\tint32,\n\tfloat32,\n}\n\nconst VECTOR_INDEX = 0;\nconst LENGTH_INDEX = 1;\nconst BUFFER_LENGTH_INDEX = 2;\nconst TYPE_INDEX = 3;\nconst DEFAULT_SIZE = 4;\nexport default class SharedVector<T extends Uint32Array | Int32Array | Float32Array = Uint32Array> implements Iterable<T> {\n\tstatic readonly ALLOCATE_COUNT = 4;\n\tprivate memory: MemoryHeap;\n\n\t// Pointer, List Length, Buffer Length, Type/DataLength\n\tprivate firstBlock: AllocatedMemory;\n\tprivate uint16Array: Uint16Array;\n\n\tget length(): number {\n\t\treturn Atomics.load(this.firstBlock.data, LENGTH_INDEX);\n\t}\n\t\n\tget type(): number {\n\t\treturn this.uint16Array[0];\n\t}\n\tprivate set type(value: number) {\n\t\tAtomics.store(this.uint16Array, 0, value);\n\t}\n\tget dataLength(): number {\n\t\t// Can technically be initialized by passing memory without actually every being called - need to make sure dataLength is always at least one\n\t\treturn this.uint16Array[1] || 1;\n\t}\n\tprivate set dataLength(value: number) {\n\t\tAtomics.store(this.uint16Array, 1, value);\n\t}\n\n\tget bufferLength(): number {\n\t\treturn Atomics.load(this.firstBlock.data, BUFFER_LENGTH_INDEX);\n\t}\n\tprivate set bufferLength(value: number) {\n\t\tAtomics.store(this.firstBlock.data, BUFFER_LENGTH_INDEX, value);\n\t}\n\n\tget pointer() {\n\t\treturn this.firstBlock.pointer;\n\t}\n\n\tget usedMemory(): number {\n\t\tlet dataMemory = new AllocatedMemory(this.memory, loadPointer(this.firstBlock.data, VECTOR_INDEX));\n\t\treturn this.firstBlock.usedMemory + dataMemory.usedMemory;\n\t}\n\n\tprivate cachedFullDataBlock?: T;\n\tprivate cachedPointer: number;\n\n\tconstructor(memory: MemoryHeap, config?: SharedVectorConfig<T> | SharedVectorMemory | SharedVectorMemory & SharedVectorConfig<T>) {\n\t\tthis.memory = memory;\n\n\t\tif(config && 'firstBlock' in config) {\n\t\t\tthis.firstBlock = new AllocatedMemory(memory, config.firstBlock);\n\t\t\tthis.uint16Array = new Uint16Array(this.firstBlock.data.buffer, this.firstBlock.bufferByteOffset + TYPE_INDEX * Uint32Array.BYTES_PER_ELEMENT, 2);\n\n\t\t\t// Pre-allocating memory and setting up in specific memory location\n\t\t\tif('type' in config || 'dataLength' in config) {\n\t\t\t\tconst bufferLength = config.bufferLength ?? DEFAULT_SIZE;\n\t\t\t\tlet dataBlock = memory.allocUI32(bufferLength * (config.dataLength ?? 1));\n\t\t\t\tstorePointer(this.firstBlock.data, VECTOR_INDEX, dataBlock.bufferPosition, dataBlock.bufferByteOffset);\n\t\t\t\tthis.bufferLength = bufferLength;\n\t\t\t\tthis.dataLength = (config.dataLength ?? 1);\n\t\t\t}\n\t\t\tif('type' in config) {\n\t\t\t\tconst type = config?.type ?? Uint32Array;\n\t\t\t\tif(type === Uint32Array) {\n\t\t\t\t\tthis.type = TYPE.uint32;\n\t\t\t\t// @ts-expect-error\n\t\t\t\t} else if(type === Int32Array) {\n\t\t\t\t\tthis.type = TYPE.int32;\n\t\t\t\t// @ts-expect-error\n\t\t\t\t} else if(type === Float32Array) {\n\t\t\t\t\tthis.type = TYPE.float32;\n\t\t\t\t}\n\t\t\t}\n\t\t} else {\n\t\t\tthis.firstBlock = memory.allocUI32(SharedVector.ALLOCATE_COUNT);\n\t\t\tthis.uint16Array = new Uint16Array(this.firstBlock.data.buffer, this.firstBlock.bufferByteOffset + TYPE_INDEX * Uint32Array.BYTES_PER_ELEMENT, 2);\n\n\t\t\tlet dataLength = config?.dataLength ?? 1;\n\t\t\tlet bufferLength = config?.bufferLength ?? DEFAULT_SIZE;\n\t\t\tlet dataBlock = memory.allocUI32(bufferLength * dataLength);\n\t\t\tstorePointer(this.firstBlock.data, VECTOR_INDEX, dataBlock.bufferPosition, dataBlock.bufferByteOffset);\n\t\t\tthis.bufferLength = bufferLength;\n\n\t\t\tconst type = config?.type ?? Uint32Array;\n\t\t\tif(type === Uint32Array) {\n\t\t\t\tthis.type = TYPE.uint32;\n\t\t\t// @ts-expect-error\n\t\t\t} else if(type === Int32Array) {\n\t\t\t\tthis.type = TYPE.int32;\n\t\t\t// @ts-expect-error\n\t\t\t} else if(type === Float32Array) {\n\t\t\t\tthis.type = TYPE.float32;\n\t\t\t}\n\t\t\tthis.dataLength = dataLength;\n\t\t}\n\n\t\tthis.cachedPointer = this.firstBlock.data[0];\n\t\tthis.cachedFullDataBlock = this.getFullDataBlock();\n\t}\n\n\tat(index: number): T {\n\t\tlet length = this.length;\n\t\tif(index >= length || index < 0) {\n\t\t\tthrow new Error(`${index} is out of bounds ${length}`);\n\t\t}\n\n\t\tlet dataBlock = this.getFullDataBlock();\n\t\treturn this.getDataBlock(dataBlock, index);\n\t}\n\tget(index: number, dataIndex = 0): number {\n\t\tif(dataIndex >= this.dataLength) {\n\t\t\tthrow new Error(`${dataIndex} is out of dataLength bounds ${this.dataLength}`);\n\t\t} else if(index >= this.length || index < 0) {\n\t\t\tthrow new Error(`${index} is out of bounds ${this.length}`);\n\t\t}\n\n\t\tlet dataBlock = this.getFullDataBlock();\n\t\treturn dataBlock[index * this.dataLength + dataIndex];\n\t}\n\n\tpush(values: number | Array<number>): number {\n\t\tlet dataLength = this.dataLength;\n\t\tconst isSingleNumber = typeof values === 'number';\n\t\tif(!isSingleNumber && values.length > dataLength) {\n\t\t\tthrow new Error(`Can't insert ${values.length} array into shared list of ${dataLength} dataLength`);\n\t\t}\n\n\t\tlet dataBlock = this.getFullDataBlock();\n\t\tlet currentLength = this.length;\n\t\tlet startIndex = dataLength * currentLength;\n\t\tif(isSingleNumber) {\n\t\t\tdataBlock[startIndex] = values;\n\t\t} else {\n\t\t\tdataBlock.set(values, startIndex);\n\t\t}\n\n\t\tlet newLength = Atomics.add(this.firstBlock.data, LENGTH_INDEX, 1) + 1;\n\t\tif(newLength >= this.bufferLength) {\n\t\t\tthis.growBuffer();\n\t\t}\n\n\t\treturn currentLength;\n\t}\n\n\tpop(): T {\n\t\tlet oldLength = Atomics.sub(this.firstBlock.data, LENGTH_INDEX, 1);\n\n\t\tlet dataBlock = this.getFullDataBlock();\n\t\treturn this.getDataBlock(dataBlock, oldLength - 1);\n\t}\n\n\t// Returns the first number irregardless of dataLength - faster way to pop if you don't care about the rest of the data block\n\tpopNumber(): number {\n\t\tconst oldLength = Atomics.sub(this.firstBlock.data, LENGTH_INDEX, 1);\n\t\tconst dataBlock = this.getFullDataBlock();\n\t\treturn dataBlock[(oldLength - 1) * this.dataLength];\n\t}\n\n\tdeleteIndex(index: number) {\n\t\tlet length = this.length;\n\t\tif(index >= length || index < 0) {\n\t\t\tthrow new Error(`${index} is out of bounds ${length}`);\n\t\t}\n\n\t\tlet dataLength = this.dataLength;\n\t\tlet dataBlock = this.getFullDataBlock();\n\t\tfor(let i = index; i < length; i++) {\n\t\t\tfor(let j = 0; j < dataLength; j++) {\n\t\t\t\tdataBlock[i * dataLength + j] = dataBlock[(i + 1) * dataLength + j];\n\t\t\t}\n\t\t}\n\n\t\tAtomics.sub(this.firstBlock.data, LENGTH_INDEX, 1);\n\t}\n\n\tclear() {\n\t\tthis.firstBlock.data[LENGTH_INDEX] = 0;\n\t}\n\n\t*[Symbol.iterator]() {\n\t\tlet dataBlock = this.getFullDataBlock();\n\t\tlet dataLength = this.dataLength;\n\t\tlet totalDataEntries = this.length * dataLength;\n\n\t\tfor(let i = 0; i < totalDataEntries; i += dataLength) {\n\t\t\tyield dataBlock.subarray(i, i + dataLength) as T;\n\t\t}\n\t}\n\n\t// Significantly faster but won't be thread safe if anything is removing/adding to it during iteration\n\tgetCurrentArray(): T {\n\t\tlet dataBlock = this.getFullDataBlock();\n\t\treturn dataBlock.subarray(0, this.length * this.dataLength) as T;\n\t}\n\n\tprivate getFullDataBlock(): T {\n\t\tlet pointerNumber = Atomics.load(this.firstBlock.data, VECTOR_INDEX);\n\t\tif(this.cachedPointer === pointerNumber && this.cachedFullDataBlock) {\n\t\t\treturn this.cachedFullDataBlock;\n\t\t}\n\n\t\tlet pointer = getPointer(pointerNumber);\n\t\tlet rawData = new AllocatedMemory(this.memory, pointer);\n\n\t\tlet data: T;\n\t\tswitch(this.type) {\n\t\t\tcase TYPE.int32:\n\t\t\t\tdata = new Int32Array(rawData.data.buffer, rawData.bufferByteOffset, this.dataLength * this.bufferLength) as T;\n\t\t\t\tbreak;\n\t\t\tcase TYPE.uint32:\n\t\t\t\tdata = new Uint32Array(rawData.data.buffer, rawData.bufferByteOffset, this.dataLength * this.bufferLength) as T;\n\t\t\t\tbreak;\n\t\t\tcase TYPE.float32:\n\t\t\t\tdata = new Float32Array(rawData.data.buffer, rawData.bufferByteOffset, this.dataLength * this.bufferLength) as T;\n\t\t\t\tbreak;\n\t\t\tdefault:\n\t\t\t\tthrow new Error(`Unknown data block type ${this.type}`);\n\t\t}\n\n\t\tthis.cachedPointer = pointerNumber;\n\t\tthis.cachedFullDataBlock = data;\n\n\t\treturn data;\n\t}\n\tprivate getDataBlock(rawData: T, index: number): T {\n\t\tconst start = index * this.dataLength;\n\t\treturn rawData.subarray(start, start + this.dataLength) as T;\n\t}\n\n\tprivate growBuffer() {\n\t\tlet oldBufferLength = this.bufferLength;\n\t\tlet newBufferLength = oldBufferLength * 2;\n\t\tlet dataLength = this.dataLength;\n\n\t\tlet oldPointer = loadPointer(this.firstBlock.data, VECTOR_INDEX);\n\t\tlet oldDataMemory = new AllocatedMemory(this.memory, oldPointer);\n\t\tlet oldDataBlock = this.getFullDataBlock();\n\t\tlet newDataBlock = this.memory.allocUI32(newBufferLength * dataLength);\n\n\t\tlet newData: T;\n\t\tswitch(this.type) {\n\t\t\tcase TYPE.int32:\n\t\t\t\tnewData = new Int32Array(newDataBlock.data.buffer, newDataBlock.bufferByteOffset, dataLength * newBufferLength) as T;\n\t\t\t\tbreak;\n\t\t\tcase TYPE.uint32:\n\t\t\t\tnewData = new Uint32Array(newDataBlock.data.buffer, newDataBlock.bufferByteOffset, dataLength * newBufferLength) as T;\n\t\t\t\tbreak;\n\t\t\tcase TYPE.float32:\n\t\t\t\tnewData = new Float32Array(newDataBlock.data.buffer, newDataBlock.bufferByteOffset, dataLength * newBufferLength) as T;\n\t\t\t\tbreak;\n\t\t\tdefault:\n\t\t\t\tthrow new Error(`Unknown data block type ${this.type}`);\n\t\t}\n\n\t\t// Copy old buffer into new buffer\n\t\tnewData.set(oldDataBlock);\n\n\t\tconst newPointer = newDataBlock.pointer;\n\t\tstoreRawPointer(this.firstBlock.data, VECTOR_INDEX, newPointer);\n\t\tthis.bufferLength = newBufferLength;\n\t\toldDataMemory.free();\n\n\t\tthis.cachedPointer = newPointer;\n\t\tthis.cachedFullDataBlock = newData;\n\t}\n\n\tfree() {\n\t\tlet pointer = loadPointer(this.firstBlock.data, VECTOR_INDEX);\n\t\tlet dataMemory = new AllocatedMemory(this.memory, pointer);\n\n\t\tdataMemory.free();\n\t\tthis.firstBlock.free();\n\t}\n\n\tgetSharedMemory(): SharedVectorMemory {\n\t\treturn {\n\t\t\tfirstBlock: this.firstBlock.getSharedMemory(),\n\t\t};\n\t}\n}\n\ninterface SharedVectorConfig<T extends Uint32Array | Int32Array | Float32Array> {\n\ttype?: TypedArrayConstructor<T>\n\tdataLength?: number\n\n\tbufferLength?: number\n}\ninterface SharedVectorMemory {\n\tfirstBlock: SharedAllocatedMemory\n}\n\nexport type { SharedVectorConfig, SharedVectorMemory };"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@daneren2005/shared-memory-objects",
3
- "version": "1.2.0",
3
+ "version": "1.3.0",
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",
@@ -38,6 +38,7 @@
38
38
  "release": "semantic-release",
39
39
  "start": "npm run dev",
40
40
  "test:bench": "vitest bench --run",
41
+ "test:stress": "npm run build && node scripts/stress-shared-list.mjs",
41
42
  "test:bench:watch": "vitest bench",
42
43
  "test:ui": "vitest --ui --api 9527",
43
44
  "test:unit": "vitest run",