@daneren2005/shared-memory-objects 1.6.0 → 1.6.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (43) hide show
  1. package/README.md +3 -2
  2. package/dist/allocated-memory.js.map +1 -1
  3. package/dist/cached-item-list.js.map +1 -1
  4. package/dist/constant-string.js.map +1 -1
  5. package/dist/local-pool.js.map +1 -1
  6. package/dist/lock/read-write-lock.js.map +1 -1
  7. package/dist/lock/simple-lock.js.map +1 -1
  8. package/dist/memory-buffer.js.map +1 -1
  9. package/dist/memory-heap.js.map +1 -1
  10. package/dist/serialize-object.js.map +1 -1
  11. package/dist/shared-list.js.map +1 -1
  12. package/dist/shared-map.d.ts +3 -0
  13. package/dist/shared-map.js +42 -24
  14. package/dist/shared-map.js.map +1 -1
  15. package/dist/shared-pointer-list.js.map +1 -1
  16. package/dist/shared-pool.js.map +1 -1
  17. package/dist/shared-stack.js.map +1 -1
  18. package/dist/shared-string.js.map +1 -1
  19. package/dist/shared-vector.js.map +1 -1
  20. package/dist/spatial/shared-quadtree.js.map +1 -1
  21. package/dist/spatial/shared-spatial-grid.js.map +1 -1
  22. package/dist/spatial/shared-spatial-map.d.ts +2 -0
  23. package/dist/spatial/shared-spatial-map.js +23 -19
  24. package/dist/spatial/shared-spatial-map.js.map +1 -1
  25. package/dist/spatial/spatial-entity.js.map +1 -1
  26. package/dist/spatial/spatial-neighbors.js.map +1 -1
  27. package/dist/utils/16-from-32-float.js.map +1 -1
  28. package/dist/utils/16-from-32-int.js.map +1 -1
  29. package/dist/utils/16-from-32-uint.js.map +1 -1
  30. package/dist/utils/16-from-64-float.js.map +1 -1
  31. package/dist/utils/16-from-64-int.js.map +1 -1
  32. package/dist/utils/16-from-64-uint.js.map +1 -1
  33. package/dist/utils/32-from-64-float.js.map +1 -1
  34. package/dist/utils/32-from-64-int.js.map +1 -1
  35. package/dist/utils/32-from-64-uint.js.map +1 -1
  36. package/dist/utils/array-type.js.map +1 -1
  37. package/dist/utils/atomic-math.js.map +1 -1
  38. package/dist/utils/float32-atomics.js.map +1 -1
  39. package/dist/utils/float64-atomics.js.map +1 -1
  40. package/dist/utils/hash.js.map +1 -1
  41. package/dist/utils/pointer.js.map +1 -1
  42. package/dist/utils/typedarray.js.map +1 -1
  43. package/package.json +8 -8
@@ -1 +1 @@
1
- {"version":3,"file":"shared-list.js","names":[],"sources":["../src/shared-list.ts"],"sourcesContent":["import type { SharedAllocatedMemory } from './allocated-memory';\nimport AllocatedMemory from './allocated-memory';\nimport type MemoryHeap from './memory-heap';\nimport { loadPointer, loadRawPointer, replaceRawPointer, storeRawPointer } from './utils/pointer';\nimport { readLock, readUnlock, tryWriteLock, writeLock, writeUnlock } from './lock/read-write-lock';\nimport { getArrayTypeCode, getByteMultipler, isBigIntType, isFloatType, makeArrayView, type NumericArray, type NumericArrayIO, type TypedArrayConstructor } from './utils/array-type';\n\n// Lock-free singly-linked list. firstBlock doubles as an immortal sentinel node whose next pointer (index 0) is the head.\n// insert appends with a Michael-Scott CAS enqueue and never takes a lock; delete only tombstones a node (a lock-free CAS).\n//\n// Reclamation: freeing a node hands its address back to the allocator, so it is only safe once no thread can still be\n// dereferencing it. insert is proven safe against this by the \"tail rules\" below (it only ever touches a tail node, whose\n// next is 0, and a reclaim never frees such a node), so insert stays completely lock-free. Traversal is NOT safe against a\n// concurrent free (a parked walker would read a recycled node), so iteration takes the SHARED side of a read/write lock and\n// reclaim takes the EXCLUSIVE side. reclaim uses a non-blocking acquire: it runs only when no other thread is mid-iteration\n// (exactly the quiescence we need) and is skipped otherwise, so it never stalls readers. Reclaim is throttled to when dead\n// nodes have caught up to live ones, making it amortized O(1) per delete and keeping the chain within ~2x of live size -\n// the manual compact() is no longer required to bound memory (it remains as a force-everything pass).\nconst FIRST_BLOCK_RECORD_KEEPING_COUNT = 7;\nconst DATA_BLOCK_RECORD_KEEPING_COUNT = 2;\nconst DATA_BLOCK_BYTE_OFFSET = DATA_BLOCK_RECORD_KEEPING_COUNT * Uint32Array.BYTES_PER_ELEMENT;\nconst HEAD_INDEX = 0;\nconst TAIL_INDEX = 1;\nconst LENGTH_INDEX = 2;\n// index 3 holds type (u16) + dataLength (u16)\nconst DEAD_COUNT_INDEX = 4;\n// Two consecutive u32s (readers, pending-writers) backing the reclaim read/write lock (see lock/read-write-lock).\nconst LOCK_INDEX = 5;\nconst NODE_NEXT_INDEX = 0;\nconst NODE_DELETED_INDEX = 1;\n// Only reclaim once at least this many dead nodes have piled up, so tiny lists don't pay a walk on every delete.\nconst RECLAIM_MIN = 16;\n// A pointer packs the buffer position in its low `positionBits` bits and the byte offset in the high bits (see\n// utils/pointer). The split is per-heap, so it is read from the heap and cached rather than a module constant.\nexport default class SharedList<T extends NumericArray = Uint32Array> implements Iterable<SharedListIterable<T>> {\n\tstatic readonly ALLOCATE_COUNT = FIRST_BLOCK_RECORD_KEEPING_COUNT;\n\n\tprivate memory: MemoryHeap;\n\t// Per-heap pointer split, cached from the heap. Hoisted into locals inside the hot loops below.\n\tprivate positionBits: number;\n\tprivate positionMask: number;\n\t/* First block (also the sentinel node - index 0 is its next pointer = list head)\n\t\t32 index 0\n\t\tuint16 0 - head buffer position | sentinel.next\n\t\tuint16 1 - head buffer index\n\t\t32 index 1\n\t\tuint16 2 - tail hint buffer position\n\t\tuint16 3 - tail hint buffer index\n\t\t32 index 2\n\t\tuint32 4 - length\n\t\t32 index 3\n\t\tuint16 6 - type\n\t\tuint16 7 - data length (defaults to 1 number per data)\n\t\t32 index 4 - dead (tombstoned-but-still-linked) node count, drives reclaim throttling\n\t\t32 index 5 - reclaim lock: reader count\n\t\t32 index 6 - reclaim lock: pending-writer count\n\t*/\n\t/* Other blocks\n\t\t32 index 0 - next buffer pointer\n\t\t32 index 1 - deleted flag (0 = live, 1 = tombstoned)\n\t\t32 index 2 => data\n\t*/\n\tprivate firstBlock: AllocatedMemory;\n\tprivate uint16Array: Uint16Array;\n\t// Int32 view over the two reclaim-lock words (readers, pending-writers) inside firstBlock\n\tprivate lockArray: Int32Array;\n\t// type and dataLength are written once at init and never change, so cache them instead of atomic-loading per node\n\tprivate cachedType: number;\n\tprivate cachedDataLength: number;\n\t// u32 units per element: 1 for 32-bit types, 2 for 64-bit (float64/int64/uint64). Node data blocks scale by this.\n\tprivate cachedByteMultipler = 1;\n\t// Whether the value type is a BigInt view (int64/uint64), so insert stores bigint via Atomics; cached to keep the\n\t// per-insert branch off an instanceof check\n\tprivate cachedIsBigInt = false;\n\t// Float views can't be published with Atomics, so insert falls back to a plain store for them\n\tprivate cachedIsFloat = false;\n\t// One full-buffer Uint32Array per buffer position, so traversal indexes shared memory directly instead of\n\t// allocating a fresh view (and a getPointer object) for every node it touches\n\tprivate bufferViews: Array<Uint32Array> = [];\n\tonDelete?: (data: T) => void;\n\n\tget length(): number {\n\t\treturn Atomics.load(this.firstBlock.data, LENGTH_INDEX);\n\t}\n\n\t// Own internal memory plus every allocated node still linked in the chain (tombstoned nodes included, since they stay\n\t// allocated until a reclaim pass unlinks and frees them).\n\tget usedMemory(): number {\n\t\tlet total = this.firstBlock.usedMemory;\n\t\tlet { bufferPosition, bufferByteOffset } = loadPointer(this.firstBlock.data, HEAD_INDEX, this.positionBits);\n\t\twhile(bufferByteOffset) {\n\t\t\tlet node = new AllocatedMemory(this.memory, { bufferPosition, bufferByteOffset });\n\t\t\ttotal += node.usedMemory;\n\t\t\t({ bufferPosition, bufferByteOffset } = loadPointer(node.data, NODE_NEXT_INDEX, this.positionBits));\n\t\t}\n\t\treturn total;\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\tconstructor(memory: MemoryHeap, config?: SharedListConfig<T> | SharedListMemory) {\n\t\tthis.memory = memory;\n\t\tthis.positionBits = memory.positionBits;\n\t\tthis.positionMask = (1 << this.positionBits) - 1;\n\n\t\tif(config && 'firstBlock' in config) {\n\t\t\t// TODO: How to handle referencing memory we don't have access to yet because buffer not synced from worker?\n\t\t\tthis.firstBlock = new AllocatedMemory(memory, config.firstBlock);\n\t\t\tthis.uint16Array = new Uint16Array(this.firstBlock.data.buffer, this.firstBlock.bufferByteOffset + (LENGTH_INDEX + 1) * Uint32Array.BYTES_PER_ELEMENT, 2);\n\t\t} else {\n\t\t\tif(config && config.initWithBlock) {\n\t\t\t\tthis.firstBlock = new AllocatedMemory(memory, config.initWithBlock);\n\t\t\t} else {\n\t\t\t\tthis.firstBlock = memory.allocUI32(FIRST_BLOCK_RECORD_KEEPING_COUNT);\n\t\t\t}\n\t\t\tthis.uint16Array = new Uint16Array(this.firstBlock.data.buffer, this.firstBlock.bufferByteOffset + (LENGTH_INDEX + 1) * Uint32Array.BYTES_PER_ELEMENT, 2);\n\n\t\t\t// Empty list: the tail hint points at the sentinel (firstBlock) so insert never special-cases the first node\n\t\t\tstoreRawPointer(this.firstBlock.data, TAIL_INDEX, this.firstBlock.pointer);\n\n\t\t\tthis.type = getArrayTypeCode(config?.type ?? Uint32Array);\n\t\t\tthis.dataLength = config?.dataLength ?? 1;\n\t\t}\n\n\t\tthis.lockArray = new Int32Array(this.firstBlock.data.buffer, this.firstBlock.bufferByteOffset + LOCK_INDEX * Uint32Array.BYTES_PER_ELEMENT, 2);\n\n\t\tthis.cachedType = Atomics.load(this.uint16Array, 0);\n\t\t// dataLength defaults to at least one even when initialized from raw memory that was never explicitly set\n\t\tthis.cachedDataLength = Math.max(1, Atomics.load(this.uint16Array, 1));\n\t\tthis.cachedByteMultipler = getByteMultipler(this.cachedType);\n\t\tthis.cachedIsBigInt = isBigIntType(this.cachedType);\n\t\tthis.cachedIsFloat = isFloatType(this.cachedType);\n\t}\n\n\tinsert(values: T[number] | Array<T[number]>) {\n\t\tif(!Array.isArray(values)) {\n\t\t\tvalues = [values];\n\t\t}\n\n\t\tlet dataLength = this.cachedDataLength;\n\t\tif(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\t\tlet newBlock = this.memory.allocUI32(DATA_BLOCK_RECORD_KEEPING_COUNT + dataLength * this.cachedByteMultipler);\n\t\tlet newData = this.getDataBlock(newBlock.data.buffer, newBlock.data.byteOffset);\n\t\tlet newBlockPointer = newBlock.pointer;\n\n\t\tlet io = newData as NumericArrayIO;\n\t\tfor(let i = 0; i < values.length; i++) {\n\t\t\tif(this.cachedIsFloat) {\n\t\t\t\t// Float views don't support Atomics; the enqueue CAS below still provides the cross-thread publish\n\t\t\t\tio[i] = values[i];\n\t\t\t} else if(this.cachedIsBigInt) {\n\t\t\t\tAtomics.store(newData as BigInt64Array, i, values[i] as bigint);\n\t\t\t} else {\n\t\t\t\tAtomics.store(newData as Int32Array, i, values[i] as number);\n\t\t\t}\n\t\t}\n\n\t\t// Michael-Scott enqueue: link the node onto tail.next BEFORE swinging the tail hint, so a committed node is never\n\t\t// invisible to a concurrent traversal. The tail hint may lag by one node; enqueuers help advance it.\n\t\tlet positionBits = this.positionBits;\n\t\tlet positionMask = this.positionMask;\n\t\t// eslint-disable-next-line no-constant-condition\n\t\twhile(true) {\n\t\t\tlet tailPointer = loadRawPointer(this.firstBlock.data, TAIL_INDEX);\n\t\t\tlet view = this.getBufferView(tailPointer & positionMask);\n\t\t\t// Tail node lives in a buffer this thread hasn't synced yet - retry until it appears\n\t\t\tif(!view) {\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tlet tailNextIndex = ((tailPointer >>> positionBits) >>> 2) + NODE_NEXT_INDEX;\n\t\t\tlet nextPointer = Atomics.load(view, tailNextIndex);\n\n\t\t\t// Re-check the hint hasn't moved out from under us before acting on what we read\n\t\t\tif(tailPointer !== loadRawPointer(this.firstBlock.data, TAIL_INDEX)) {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tif(nextPointer === 0) {\n\t\t\t\tif(Atomics.compareExchange(view, tailNextIndex, 0, newBlockPointer) === 0) {\n\t\t\t\t\t// Linked. Try to swing the hint forward - failure is harmless, the next enqueuer will help.\n\t\t\t\t\treplaceRawPointer(this.firstBlock.data, TAIL_INDEX, newBlockPointer, tailPointer);\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\t// Tail hint is lagging behind a linked node - help it catch up, then retry\n\t\t\t\treplaceRawPointer(this.firstBlock.data, TAIL_INDEX, nextPointer, tailPointer);\n\t\t\t}\n\t\t}\n\n\t\tAtomics.add(this.firstBlock.data, LENGTH_INDEX, 1);\n\t}\n\n\tdeleteMatch(callback: (values: T, index: number) => boolean): boolean {\n\t\tlet deleted = false;\n\t\tfor(let { data, index, deleteCurrent } of this) {\n\t\t\tif(callback(data, index)) {\n\t\t\t\tif(deleteCurrent()) {\n\t\t\t\t\tdeleted = true;\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t// Reclaim runs after the iterator's read lock is released, so it can win exclusivity when no one else is walking\n\t\tthis.autoReclaim();\n\t\treturn deleted;\n\t}\n\tdeleteIndex(deleteIndex: number): boolean {\n\t\tif(deleteIndex >= this.length || deleteIndex < 0) {\n\t\t\treturn false;\n\t\t}\n\n\t\tlet deleted = false;\n\t\t// Compare on index only so the payload view is never materialized for an index-based delete\n\t\tfor(let item of this) {\n\t\t\tif(item.index === deleteIndex) {\n\t\t\t\tif(item.deleteCurrent()) {\n\t\t\t\t\tdeleted = true;\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tthis.autoReclaim();\n\t\treturn deleted;\n\t}\n\tdeleteValue(deleteValues: T[number] | Array<T[number]>) {\n\t\tif(!Array.isArray(deleteValues)) {\n\t\t\treturn this.deleteMatch(values => values[0] === deleteValues);\n\t\t} else {\n\t\t\treturn this.deleteMatch(values => {\n\t\t\t\tif(values.length !== deleteValues.length) {\n\t\t\t\t\treturn false;\n\t\t\t\t} else {\n\t\t\t\t\tfor(let i = 0; i < values.length; i++) {\n\t\t\t\t\t\tif(values[i] !== deleteValues[i]) {\n\t\t\t\t\t\t\treturn false;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t\treturn true;\n\t\t\t\t}\n\t\t\t});\n\t\t}\n\t}\n\n\t// Force-reclaim every tombstoned node, including a lone tail tombstone that autoReclaim leaves behind. Takes the reclaim\n\t// lock exclusively (blocking), so it is safe against concurrent iterate/delete - but must NOT be called from inside an\n\t// iteration on this same thread, since the walker still holds the read lock and the two would deadlock.\n\tcompact() {\n\t\twriteLock(this.lockArray);\n\t\ttry {\n\t\t\tthis.reclaimTombstones(true);\n\t\t} finally {\n\t\t\twriteUnlock(this.lockArray);\n\t\t}\n\t}\n\n\t// Fires after a delete once dead nodes have caught up to live ones. Grabs the reclaim lock only if no thread is\n\t// mid-iteration (non-blocking) - \"not in the middle of an iteration by another thread\" - and otherwise skips, leaving\n\t// the garbage for the next delete to retry. Amortizes to O(1) per delete while keeping the chain within ~2x of live.\n\tprivate autoReclaim() {\n\t\tlet dead = Atomics.load(this.firstBlock.data, DEAD_COUNT_INDEX);\n\t\tif(dead < RECLAIM_MIN || dead < this.length) {\n\t\t\treturn;\n\t\t}\n\n\t\tif(!tryWriteLock(this.lockArray)) {\n\t\t\treturn;\n\t\t}\n\t\ttry {\n\t\t\tthis.reclaimTombstones(false);\n\t\t} finally {\n\t\t\twriteUnlock(this.lockArray);\n\t\t}\n\t}\n\n\t// Physically unlinks and frees tombstoned nodes. The caller MUST hold the reclaim lock exclusively, so no iterator is\n\t// walking and no other reclaim runs concurrently - that is what rules out freeing a node another walker is parked on and\n\t// the stale-predecessor races of arbitrary lock-free deletion. insert stays lock-free and runs concurrently here, so the\n\t// tail rules keep it safe: a node whose next is 0 might be the enqueue target, so unless freeTail it is left in place, and\n\t// every freed node has the tail hint swung off it first (any insert holding a stale hint then re-checks and discards).\n\tprivate reclaimTombstones(freeTail: boolean): number {\n\t\tlet prevPointer = this.firstBlock.pointer;\n\t\tlet prevView = this.firstBlock.data;\n\t\tlet prevNextIndex = NODE_NEXT_INDEX;\n\t\tlet positionBits = this.positionBits;\n\t\tlet positionMask = this.positionMask;\n\t\tlet freed = 0;\n\t\tlet nextPointer = loadRawPointer(prevView, prevNextIndex);\n\t\twhile(nextPointer) {\n\t\t\tlet position = nextPointer & positionMask;\n\t\t\tlet memPool = this.memory.buffers[position];\n\t\t\tif(!memPool) {\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tlet view = this.getBufferView(position)!;\n\t\t\tlet byteOffset = nextPointer >>> positionBits;\n\t\t\tlet nodeIndex = byteOffset >>> 2;\n\t\t\tlet followingPointer = Atomics.load(view, nodeIndex + NODE_NEXT_INDEX);\n\n\t\t\tif(Atomics.load(view, nodeIndex + NODE_DELETED_INDEX)) {\n\t\t\t\tif(followingPointer === 0 && !freeTail) {\n\t\t\t\t\t// Tail tombstone: a concurrent insert may be about to CAS its next, so leave it for compact() or the next\n\t\t\t\t\t// append (which turns it into a safe-to-free interior node)\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t\t// Unlink first so the following node stays reachable, then swing the tail hint off the node before freeing it,\n\t\t\t\t// then hand the memory back. onDelete already fired when the node was tombstoned.\n\t\t\t\tstoreRawPointer(prevView, prevNextIndex, followingPointer);\n\t\t\t\treplaceRawPointer(this.firstBlock.data, TAIL_INDEX, prevPointer, nextPointer);\n\t\t\t\tmemPool.free(byteOffset);\n\t\t\t\tfreed++;\n\t\t\t} else {\n\t\t\t\tprevPointer = nextPointer;\n\t\t\t\tprevView = view;\n\t\t\t\tprevNextIndex = nodeIndex + NODE_NEXT_INDEX;\n\t\t\t}\n\n\t\t\tnextPointer = followingPointer;\n\t\t}\n\n\t\tif(freeTail) {\n\t\t\t// Quiescent full pass: the last surviving node (or the sentinel) is unambiguously the tail\n\t\t\tstoreRawPointer(this.firstBlock.data, TAIL_INDEX, prevPointer);\n\t\t}\n\t\tif(freed) {\n\t\t\tAtomics.sub(this.firstBlock.data, DEAD_COUNT_INDEX, freed);\n\t\t}\n\t\treturn freed;\n\t}\n\n\tclear() {\n\t\twriteLock(this.lockArray);\n\t\ttry {\n\t\t\tthis.clearLocked();\n\t\t} finally {\n\t\t\twriteUnlock(this.lockArray);\n\t\t}\n\t}\n\tprivate clearLocked() {\n\t\t// Detach the whole chain, then free it. Reclaim lock is held, so no iterator is walking the chain we are freeing.\n\t\tlet firstBlockPointer;\n\t\tlet updateWorked = false;\n\t\twhile(!updateWorked) {\n\t\t\tfirstBlockPointer = loadRawPointer(this.firstBlock.data, HEAD_INDEX);\n\t\t\t// Already cleared\n\t\t\tif(!firstBlockPointer) {\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tupdateWorked = replaceRawPointer(this.firstBlock.data, HEAD_INDEX, 0, firstBlockPointer);\n\t\t}\n\t\t// Shouldn't be possible to hit: making Typescript happy\n\t\tif(!firstBlockPointer) {\n\t\t\treturn;\n\t\t}\n\n\t\t// Reset the tail hint back to the sentinel\n\t\tstoreRawPointer(this.firstBlock.data, TAIL_INDEX, this.firstBlock.pointer);\n\n\t\tlet liveItems = 0;\n\t\tlet positionBits = this.positionBits;\n\t\tlet positionMask = this.positionMask;\n\t\tlet nextBlockPointer = firstBlockPointer;\n\t\twhile(nextBlockPointer) {\n\t\t\tlet position = nextBlockPointer & positionMask;\n\t\t\tlet memPool = this.memory.buffers[position];\n\t\t\t// Short circuit iterations if we can't access memory\n\t\t\tif(!memPool) {\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\tlet view = this.getBufferView(position)!;\n\t\t\tlet byteOffset = nextBlockPointer >>> positionBits;\n\t\t\tlet nodeIndex = byteOffset >>> 2;\n\t\t\tnextBlockPointer = Atomics.load(view, nodeIndex + NODE_NEXT_INDEX);\n\n\t\t\tif(!Atomics.load(view, nodeIndex + NODE_DELETED_INDEX)) {\n\t\t\t\tliveItems++;\n\t\t\t\tif(this.onDelete) {\n\t\t\t\t\tthis.onDelete(this.getDataBlock(view.buffer, byteOffset));\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tmemPool.free(byteOffset);\n\t\t}\n\n\t\t// Only subtract the live nodes we detached so a concurrent insert's increment stays accurate\n\t\tAtomics.sub(this.firstBlock.data, LENGTH_INDEX, liveItems);\n\t\t// The whole chain (dead nodes included) is gone; no new dead nodes can appear while we hold the reclaim lock\n\t\tAtomics.store(this.firstBlock.data, DEAD_COUNT_INDEX, 0);\n\t}\n\n\t*[Symbol.iterator]() {\n\t\tlet currentIndex = 0;\n\t\tlet positionBits = this.positionBits;\n\t\tlet positionMask = this.positionMask;\n\t\t// Shared read lock: many walkers (and lock-free inserts) run at once, but it blocks a reclaim from freeing a node out\n\t\t// from under us. Held for the whole walk and released in finally, so an early break/throw still frees it.\n\t\treadLock(this.lockArray);\n\t\ttry {\n\t\t\tlet nextPointer = loadRawPointer(this.firstBlock.data, HEAD_INDEX);\n\t\t\twhile(nextPointer) {\n\t\t\t\tlet position = nextPointer & positionMask;\n\t\t\t\tlet view = this.getBufferView(position);\n\t\t\t\t// Short circuit iterations if we can't access memory\n\t\t\t\tif(!view) {\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tlet byteOffset = nextPointer >>> positionBits;\n\t\t\t\tlet nodeIndex = byteOffset >>> 2;\n\t\t\t\tnextPointer = Atomics.load(view, nodeIndex + NODE_NEXT_INDEX);\n\n\t\t\t\t// Skip tombstoned nodes - they stay linked until a reclaim pass unlinks and frees them\n\t\t\t\tif(Atomics.load(view, nodeIndex + NODE_DELETED_INDEX)) {\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\n\t\t\t\tlet buffer = view.buffer;\n\t\t\t\tlet deletedIndex = nodeIndex + NODE_DELETED_INDEX;\n\t\t\t\t// Plain object with an eager data view: V8 keeps this monomorphic and fast. A lazy `get data()` accessor is far\n\t\t\t\t// slower to allocate than the payload view it would save, so pay for the view up front.\n\t\t\t\tyield {\n\t\t\t\t\tdata: this.getDataBlock(buffer, byteOffset),\n\t\t\t\t\tindex: currentIndex,\n\t\t\t\t\t// Logical delete: tombstone the node and drop length. Returns false if another thread beat us to it.\n\t\t\t\t\tdeleteCurrent: (): boolean => {\n\t\t\t\t\t\tif(Atomics.compareExchange(view, deletedIndex, 0, 1) === 0) {\n\t\t\t\t\t\t\tAtomics.sub(this.firstBlock.data, LENGTH_INDEX, 1);\n\t\t\t\t\t\t\t// Count the dead node so autoReclaim knows when garbage has caught up to live nodes\n\t\t\t\t\t\t\tAtomics.add(this.firstBlock.data, DEAD_COUNT_INDEX, 1);\n\t\t\t\t\t\t\tif(this.onDelete) {\n\t\t\t\t\t\t\t\tthis.onDelete(this.getDataBlock(buffer, byteOffset));\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\treturn true;\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\treturn false;\n\t\t\t\t\t},\n\t\t\t\t};\n\n\t\t\t\tcurrentIndex++;\n\t\t\t}\n\t\t} finally {\n\t\t\treadUnlock(this.lockArray);\n\t\t}\n\t}\n\n\tforEach(callback: (data: T) => void) {\n\t\tfor(let value of this) {\n\t\t\tcallback(value.data);\n\t\t}\n\t}\n\n\tgetSharedMemory(): SharedListMemory {\n\t\treturn {\n\t\t\tfirstBlock: this.firstBlock.getSharedMemory(),\n\t\t};\n\t}\n\n\tprivate getBufferView(position: number): Uint32Array | undefined {\n\t\tlet view = this.bufferViews[position];\n\t\tif(view) {\n\t\t\treturn view;\n\t\t}\n\n\t\tlet buffer = this.memory.buffers[position];\n\t\tif(!buffer) {\n\t\t\treturn undefined;\n\t\t}\n\n\t\tview = new Uint32Array(buffer.buf);\n\t\tthis.bufferViews[position] = view;\n\t\treturn view;\n\t}\n\n\tprivate getDataBlock(buffer: ArrayBufferLike, nodeByteOffset: number): T {\n\t\tconst startIndex = nodeByteOffset + DATA_BLOCK_BYTE_OFFSET;\n\t\treturn makeArrayView(this.cachedType, buffer, startIndex, this.cachedDataLength) as T;\n\t}\n\n\tfree() {\n\t\tlet { bufferPosition: nextBlockPosition, bufferByteOffset: nextBlockByteOffset } = loadPointer(this.firstBlock.data, HEAD_INDEX, this.positionBits);\n\t\twhile(nextBlockByteOffset) {\n\t\t\tlet allocatedMemory = new AllocatedMemory(this.memory, {\n\t\t\t\tbufferPosition: nextBlockPosition,\n\t\t\t\tbufferByteOffset: nextBlockByteOffset,\n\t\t\t});\n\n\t\t\t({ bufferPosition: nextBlockPosition, bufferByteOffset: nextBlockByteOffset } = loadPointer(allocatedMemory.data, NODE_NEXT_INDEX, this.positionBits));\n\n\t\t\tif(this.onDelete && !Atomics.load(allocatedMemory.data, NODE_DELETED_INDEX)) {\n\t\t\t\tthis.onDelete(this.getDataBlock(allocatedMemory.data.buffer, allocatedMemory.data.byteOffset));\n\t\t\t}\n\n\t\t\tallocatedMemory.free();\n\t\t}\n\n\t\tthis.firstBlock.free();\n\t}\n}\n\ninterface SharedListConfig<T extends NumericArray> {\n\tinitWithBlock?: SharedAllocatedMemory\n\ttype?: TypedArrayConstructor<T>\n\tdataLength?: number\n}\ninterface SharedListMemory {\n\tfirstBlock: SharedAllocatedMemory\n}\n\ninterface SharedListIterable<T extends NumericArray> {\n\tdata: T\n\tindex: number\n\tdeleteCurrent: () => boolean\n}\n\nexport { type SharedListMemory };\n"],"mappings":"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"}
1
+ {"version":3,"file":"shared-list.js","names":["AllocatedMemory","FIRST_BLOCK_RECORD_KEEPING_COUNT","DATA_BLOCK_RECORD_KEEPING_COUNT","DATA_BLOCK_BYTE_OFFSET","HEAD_INDEX","TAIL_INDEX","LENGTH_INDEX","DEAD_COUNT_INDEX","LOCK_INDEX","NODE_NEXT_INDEX","NODE_DELETED_INDEX","RECLAIM_MIN","SharedList","total","loadPointer","bufferByteOffset","node","value","memory","config","storeRawPointer","getArrayTypeCode","getByteMultipler","isBigIntType","isFloatType","values","dataLength","newBlock","newData","newBlockPointer","io","i","positionBits","positionMask","tailPointer","loadRawPointer","view","tailNextIndex","nextPointer","deleted","callback","data","index","deleteCurrent","deleteIndex","item","deleteValues","dead","tryWriteLock","prevPointer","prevView","prevNextIndex","freed","position","memPool","byteOffset","nodeIndex","followingPointer","freeTail","replaceRawPointer","firstBlockPointer","updateWorked","liveItems","nextBlockPointer","currentIndex","buffer","deletedIndex","startIndex","nodeByteOffset","makeArrayView","nextBlockPosition","nextBlockByteOffset","allocatedMemory"],"sources":["../src/shared-list.ts"],"sourcesContent":["import type { SharedAllocatedMemory } from './allocated-memory';\nimport AllocatedMemory from './allocated-memory';\nimport type MemoryHeap from './memory-heap';\nimport { loadPointer, loadRawPointer, replaceRawPointer, storeRawPointer } from './utils/pointer';\nimport { readLock, readUnlock, tryWriteLock, writeLock, writeUnlock } from './lock/read-write-lock';\nimport { getArrayTypeCode, getByteMultipler, isBigIntType, isFloatType, makeArrayView, type NumericArray, type NumericArrayIO, type TypedArrayConstructor } from './utils/array-type';\n\n// Lock-free singly-linked list. firstBlock doubles as an immortal sentinel node whose next pointer (index 0) is the head.\n// insert appends with a Michael-Scott CAS enqueue and never takes a lock; delete only tombstones a node (a lock-free CAS).\n//\n// Reclamation: freeing a node hands its address back to the allocator, so it is only safe once no thread can still be\n// dereferencing it. insert is proven safe against this by the \"tail rules\" below (it only ever touches a tail node, whose\n// next is 0, and a reclaim never frees such a node), so insert stays completely lock-free. Traversal is NOT safe against a\n// concurrent free (a parked walker would read a recycled node), so iteration takes the SHARED side of a read/write lock and\n// reclaim takes the EXCLUSIVE side. reclaim uses a non-blocking acquire: it runs only when no other thread is mid-iteration\n// (exactly the quiescence we need) and is skipped otherwise, so it never stalls readers. Reclaim is throttled to when dead\n// nodes have caught up to live ones, making it amortized O(1) per delete and keeping the chain within ~2x of live size -\n// the manual compact() is no longer required to bound memory (it remains as a force-everything pass).\nconst FIRST_BLOCK_RECORD_KEEPING_COUNT = 7;\nconst DATA_BLOCK_RECORD_KEEPING_COUNT = 2;\nconst DATA_BLOCK_BYTE_OFFSET = DATA_BLOCK_RECORD_KEEPING_COUNT * Uint32Array.BYTES_PER_ELEMENT;\nconst HEAD_INDEX = 0;\nconst TAIL_INDEX = 1;\nconst LENGTH_INDEX = 2;\n// index 3 holds type (u16) + dataLength (u16)\nconst DEAD_COUNT_INDEX = 4;\n// Two consecutive u32s (readers, pending-writers) backing the reclaim read/write lock (see lock/read-write-lock).\nconst LOCK_INDEX = 5;\nconst NODE_NEXT_INDEX = 0;\nconst NODE_DELETED_INDEX = 1;\n// Only reclaim once at least this many dead nodes have piled up, so tiny lists don't pay a walk on every delete.\nconst RECLAIM_MIN = 16;\n// A pointer packs the buffer position in its low `positionBits` bits and the byte offset in the high bits (see\n// utils/pointer). The split is per-heap, so it is read from the heap and cached rather than a module constant.\nexport default class SharedList<T extends NumericArray = Uint32Array> implements Iterable<SharedListIterable<T>> {\n\tstatic readonly ALLOCATE_COUNT = FIRST_BLOCK_RECORD_KEEPING_COUNT;\n\n\tprivate memory: MemoryHeap;\n\t// Per-heap pointer split, cached from the heap. Hoisted into locals inside the hot loops below.\n\tprivate positionBits: number;\n\tprivate positionMask: number;\n\t/* First block (also the sentinel node - index 0 is its next pointer = list head)\n\t\t32 index 0\n\t\tuint16 0 - head buffer position | sentinel.next\n\t\tuint16 1 - head buffer index\n\t\t32 index 1\n\t\tuint16 2 - tail hint buffer position\n\t\tuint16 3 - tail hint buffer index\n\t\t32 index 2\n\t\tuint32 4 - length\n\t\t32 index 3\n\t\tuint16 6 - type\n\t\tuint16 7 - data length (defaults to 1 number per data)\n\t\t32 index 4 - dead (tombstoned-but-still-linked) node count, drives reclaim throttling\n\t\t32 index 5 - reclaim lock: reader count\n\t\t32 index 6 - reclaim lock: pending-writer count\n\t*/\n\t/* Other blocks\n\t\t32 index 0 - next buffer pointer\n\t\t32 index 1 - deleted flag (0 = live, 1 = tombstoned)\n\t\t32 index 2 => data\n\t*/\n\tprivate firstBlock: AllocatedMemory;\n\tprivate uint16Array: Uint16Array;\n\t// Int32 view over the two reclaim-lock words (readers, pending-writers) inside firstBlock\n\tprivate lockArray: Int32Array;\n\t// type and dataLength are written once at init and never change, so cache them instead of atomic-loading per node\n\tprivate cachedType: number;\n\tprivate cachedDataLength: number;\n\t// u32 units per element: 1 for 32-bit types, 2 for 64-bit (float64/int64/uint64). Node data blocks scale by this.\n\tprivate cachedByteMultipler = 1;\n\t// Whether the value type is a BigInt view (int64/uint64), so insert stores bigint via Atomics; cached to keep the\n\t// per-insert branch off an instanceof check\n\tprivate cachedIsBigInt = false;\n\t// Float views can't be published with Atomics, so insert falls back to a plain store for them\n\tprivate cachedIsFloat = false;\n\t// One full-buffer Uint32Array per buffer position, so traversal indexes shared memory directly instead of\n\t// allocating a fresh view (and a getPointer object) for every node it touches\n\tprivate bufferViews: Array<Uint32Array> = [];\n\tonDelete?: (data: T) => void;\n\n\tget length(): number {\n\t\treturn Atomics.load(this.firstBlock.data, LENGTH_INDEX);\n\t}\n\n\t// Own internal memory plus every allocated node still linked in the chain (tombstoned nodes included, since they stay\n\t// allocated until a reclaim pass unlinks and frees them).\n\tget usedMemory(): number {\n\t\tlet total = this.firstBlock.usedMemory;\n\t\tlet { bufferPosition, bufferByteOffset } = loadPointer(this.firstBlock.data, HEAD_INDEX, this.positionBits);\n\t\twhile(bufferByteOffset) {\n\t\t\tlet node = new AllocatedMemory(this.memory, { bufferPosition, bufferByteOffset });\n\t\t\ttotal += node.usedMemory;\n\t\t\t({ bufferPosition, bufferByteOffset } = loadPointer(node.data, NODE_NEXT_INDEX, this.positionBits));\n\t\t}\n\t\treturn total;\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\tconstructor(memory: MemoryHeap, config?: SharedListConfig<T> | SharedListMemory) {\n\t\tthis.memory = memory;\n\t\tthis.positionBits = memory.positionBits;\n\t\tthis.positionMask = (1 << this.positionBits) - 1;\n\n\t\tif(config && 'firstBlock' in config) {\n\t\t\t// TODO: How to handle referencing memory we don't have access to yet because buffer not synced from worker?\n\t\t\tthis.firstBlock = new AllocatedMemory(memory, config.firstBlock);\n\t\t\tthis.uint16Array = new Uint16Array(this.firstBlock.data.buffer, this.firstBlock.bufferByteOffset + (LENGTH_INDEX + 1) * Uint32Array.BYTES_PER_ELEMENT, 2);\n\t\t} else {\n\t\t\tif(config && config.initWithBlock) {\n\t\t\t\tthis.firstBlock = new AllocatedMemory(memory, config.initWithBlock);\n\t\t\t} else {\n\t\t\t\tthis.firstBlock = memory.allocUI32(FIRST_BLOCK_RECORD_KEEPING_COUNT);\n\t\t\t}\n\t\t\tthis.uint16Array = new Uint16Array(this.firstBlock.data.buffer, this.firstBlock.bufferByteOffset + (LENGTH_INDEX + 1) * Uint32Array.BYTES_PER_ELEMENT, 2);\n\n\t\t\t// Empty list: the tail hint points at the sentinel (firstBlock) so insert never special-cases the first node\n\t\t\tstoreRawPointer(this.firstBlock.data, TAIL_INDEX, this.firstBlock.pointer);\n\n\t\t\tthis.type = getArrayTypeCode(config?.type ?? Uint32Array);\n\t\t\tthis.dataLength = config?.dataLength ?? 1;\n\t\t}\n\n\t\tthis.lockArray = new Int32Array(this.firstBlock.data.buffer, this.firstBlock.bufferByteOffset + LOCK_INDEX * Uint32Array.BYTES_PER_ELEMENT, 2);\n\n\t\tthis.cachedType = Atomics.load(this.uint16Array, 0);\n\t\t// dataLength defaults to at least one even when initialized from raw memory that was never explicitly set\n\t\tthis.cachedDataLength = Math.max(1, Atomics.load(this.uint16Array, 1));\n\t\tthis.cachedByteMultipler = getByteMultipler(this.cachedType);\n\t\tthis.cachedIsBigInt = isBigIntType(this.cachedType);\n\t\tthis.cachedIsFloat = isFloatType(this.cachedType);\n\t}\n\n\tinsert(values: T[number] | Array<T[number]>) {\n\t\tif(!Array.isArray(values)) {\n\t\t\tvalues = [values];\n\t\t}\n\n\t\tlet dataLength = this.cachedDataLength;\n\t\tif(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\t\tlet newBlock = this.memory.allocUI32(DATA_BLOCK_RECORD_KEEPING_COUNT + dataLength * this.cachedByteMultipler);\n\t\tlet newData = this.getDataBlock(newBlock.data.buffer, newBlock.data.byteOffset);\n\t\tlet newBlockPointer = newBlock.pointer;\n\n\t\tlet io = newData as NumericArrayIO;\n\t\tfor(let i = 0; i < values.length; i++) {\n\t\t\tif(this.cachedIsFloat) {\n\t\t\t\t// Float views don't support Atomics; the enqueue CAS below still provides the cross-thread publish\n\t\t\t\tio[i] = values[i];\n\t\t\t} else if(this.cachedIsBigInt) {\n\t\t\t\tAtomics.store(newData as BigInt64Array, i, values[i] as bigint);\n\t\t\t} else {\n\t\t\t\tAtomics.store(newData as Int32Array, i, values[i] as number);\n\t\t\t}\n\t\t}\n\n\t\t// Michael-Scott enqueue: link the node onto tail.next BEFORE swinging the tail hint, so a committed node is never\n\t\t// invisible to a concurrent traversal. The tail hint may lag by one node; enqueuers help advance it.\n\t\tlet positionBits = this.positionBits;\n\t\tlet positionMask = this.positionMask;\n\t\t// eslint-disable-next-line no-constant-condition\n\t\twhile(true) {\n\t\t\tlet tailPointer = loadRawPointer(this.firstBlock.data, TAIL_INDEX);\n\t\t\tlet view = this.getBufferView(tailPointer & positionMask);\n\t\t\t// Tail node lives in a buffer this thread hasn't synced yet - retry until it appears\n\t\t\tif(!view) {\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tlet tailNextIndex = ((tailPointer >>> positionBits) >>> 2) + NODE_NEXT_INDEX;\n\t\t\tlet nextPointer = Atomics.load(view, tailNextIndex);\n\n\t\t\t// Re-check the hint hasn't moved out from under us before acting on what we read\n\t\t\tif(tailPointer !== loadRawPointer(this.firstBlock.data, TAIL_INDEX)) {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tif(nextPointer === 0) {\n\t\t\t\tif(Atomics.compareExchange(view, tailNextIndex, 0, newBlockPointer) === 0) {\n\t\t\t\t\t// Linked. Try to swing the hint forward - failure is harmless, the next enqueuer will help.\n\t\t\t\t\treplaceRawPointer(this.firstBlock.data, TAIL_INDEX, newBlockPointer, tailPointer);\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\t// Tail hint is lagging behind a linked node - help it catch up, then retry\n\t\t\t\treplaceRawPointer(this.firstBlock.data, TAIL_INDEX, nextPointer, tailPointer);\n\t\t\t}\n\t\t}\n\n\t\tAtomics.add(this.firstBlock.data, LENGTH_INDEX, 1);\n\t}\n\n\tdeleteMatch(callback: (values: T, index: number) => boolean): boolean {\n\t\tlet deleted = false;\n\t\tfor(let { data, index, deleteCurrent } of this) {\n\t\t\tif(callback(data, index)) {\n\t\t\t\tif(deleteCurrent()) {\n\t\t\t\t\tdeleted = true;\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t// Reclaim runs after the iterator's read lock is released, so it can win exclusivity when no one else is walking\n\t\tthis.autoReclaim();\n\t\treturn deleted;\n\t}\n\tdeleteIndex(deleteIndex: number): boolean {\n\t\tif(deleteIndex >= this.length || deleteIndex < 0) {\n\t\t\treturn false;\n\t\t}\n\n\t\tlet deleted = false;\n\t\t// Compare on index only so the payload view is never materialized for an index-based delete\n\t\tfor(let item of this) {\n\t\t\tif(item.index === deleteIndex) {\n\t\t\t\tif(item.deleteCurrent()) {\n\t\t\t\t\tdeleted = true;\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tthis.autoReclaim();\n\t\treturn deleted;\n\t}\n\tdeleteValue(deleteValues: T[number] | Array<T[number]>) {\n\t\tif(!Array.isArray(deleteValues)) {\n\t\t\treturn this.deleteMatch(values => values[0] === deleteValues);\n\t\t} else {\n\t\t\treturn this.deleteMatch(values => {\n\t\t\t\tif(values.length !== deleteValues.length) {\n\t\t\t\t\treturn false;\n\t\t\t\t} else {\n\t\t\t\t\tfor(let i = 0; i < values.length; i++) {\n\t\t\t\t\t\tif(values[i] !== deleteValues[i]) {\n\t\t\t\t\t\t\treturn false;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t\treturn true;\n\t\t\t\t}\n\t\t\t});\n\t\t}\n\t}\n\n\t// Force-reclaim every tombstoned node, including a lone tail tombstone that autoReclaim leaves behind. Takes the reclaim\n\t// lock exclusively (blocking), so it is safe against concurrent iterate/delete - but must NOT be called from inside an\n\t// iteration on this same thread, since the walker still holds the read lock and the two would deadlock.\n\tcompact() {\n\t\twriteLock(this.lockArray);\n\t\ttry {\n\t\t\tthis.reclaimTombstones(true);\n\t\t} finally {\n\t\t\twriteUnlock(this.lockArray);\n\t\t}\n\t}\n\n\t// Fires after a delete once dead nodes have caught up to live ones. Grabs the reclaim lock only if no thread is\n\t// mid-iteration (non-blocking) - \"not in the middle of an iteration by another thread\" - and otherwise skips, leaving\n\t// the garbage for the next delete to retry. Amortizes to O(1) per delete while keeping the chain within ~2x of live.\n\tprivate autoReclaim() {\n\t\tlet dead = Atomics.load(this.firstBlock.data, DEAD_COUNT_INDEX);\n\t\tif(dead < RECLAIM_MIN || dead < this.length) {\n\t\t\treturn;\n\t\t}\n\n\t\tif(!tryWriteLock(this.lockArray)) {\n\t\t\treturn;\n\t\t}\n\t\ttry {\n\t\t\tthis.reclaimTombstones(false);\n\t\t} finally {\n\t\t\twriteUnlock(this.lockArray);\n\t\t}\n\t}\n\n\t// Physically unlinks and frees tombstoned nodes. The caller MUST hold the reclaim lock exclusively, so no iterator is\n\t// walking and no other reclaim runs concurrently - that is what rules out freeing a node another walker is parked on and\n\t// the stale-predecessor races of arbitrary lock-free deletion. insert stays lock-free and runs concurrently here, so the\n\t// tail rules keep it safe: a node whose next is 0 might be the enqueue target, so unless freeTail it is left in place, and\n\t// every freed node has the tail hint swung off it first (any insert holding a stale hint then re-checks and discards).\n\tprivate reclaimTombstones(freeTail: boolean): number {\n\t\tlet prevPointer = this.firstBlock.pointer;\n\t\tlet prevView = this.firstBlock.data;\n\t\tlet prevNextIndex = NODE_NEXT_INDEX;\n\t\tlet positionBits = this.positionBits;\n\t\tlet positionMask = this.positionMask;\n\t\tlet freed = 0;\n\t\tlet nextPointer = loadRawPointer(prevView, prevNextIndex);\n\t\twhile(nextPointer) {\n\t\t\tlet position = nextPointer & positionMask;\n\t\t\tlet memPool = this.memory.buffers[position];\n\t\t\tif(!memPool) {\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tlet view = this.getBufferView(position)!;\n\t\t\tlet byteOffset = nextPointer >>> positionBits;\n\t\t\tlet nodeIndex = byteOffset >>> 2;\n\t\t\tlet followingPointer = Atomics.load(view, nodeIndex + NODE_NEXT_INDEX);\n\n\t\t\tif(Atomics.load(view, nodeIndex + NODE_DELETED_INDEX)) {\n\t\t\t\tif(followingPointer === 0 && !freeTail) {\n\t\t\t\t\t// Tail tombstone: a concurrent insert may be about to CAS its next, so leave it for compact() or the next\n\t\t\t\t\t// append (which turns it into a safe-to-free interior node)\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t\t// Unlink first so the following node stays reachable, then swing the tail hint off the node before freeing it,\n\t\t\t\t// then hand the memory back. onDelete already fired when the node was tombstoned.\n\t\t\t\tstoreRawPointer(prevView, prevNextIndex, followingPointer);\n\t\t\t\treplaceRawPointer(this.firstBlock.data, TAIL_INDEX, prevPointer, nextPointer);\n\t\t\t\tmemPool.free(byteOffset);\n\t\t\t\tfreed++;\n\t\t\t} else {\n\t\t\t\tprevPointer = nextPointer;\n\t\t\t\tprevView = view;\n\t\t\t\tprevNextIndex = nodeIndex + NODE_NEXT_INDEX;\n\t\t\t}\n\n\t\t\tnextPointer = followingPointer;\n\t\t}\n\n\t\tif(freeTail) {\n\t\t\t// Quiescent full pass: the last surviving node (or the sentinel) is unambiguously the tail\n\t\t\tstoreRawPointer(this.firstBlock.data, TAIL_INDEX, prevPointer);\n\t\t}\n\t\tif(freed) {\n\t\t\tAtomics.sub(this.firstBlock.data, DEAD_COUNT_INDEX, freed);\n\t\t}\n\t\treturn freed;\n\t}\n\n\tclear() {\n\t\twriteLock(this.lockArray);\n\t\ttry {\n\t\t\tthis.clearLocked();\n\t\t} finally {\n\t\t\twriteUnlock(this.lockArray);\n\t\t}\n\t}\n\tprivate clearLocked() {\n\t\t// Detach the whole chain, then free it. Reclaim lock is held, so no iterator is walking the chain we are freeing.\n\t\tlet firstBlockPointer;\n\t\tlet updateWorked = false;\n\t\twhile(!updateWorked) {\n\t\t\tfirstBlockPointer = loadRawPointer(this.firstBlock.data, HEAD_INDEX);\n\t\t\t// Already cleared\n\t\t\tif(!firstBlockPointer) {\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tupdateWorked = replaceRawPointer(this.firstBlock.data, HEAD_INDEX, 0, firstBlockPointer);\n\t\t}\n\t\t// Shouldn't be possible to hit: making Typescript happy\n\t\tif(!firstBlockPointer) {\n\t\t\treturn;\n\t\t}\n\n\t\t// Reset the tail hint back to the sentinel\n\t\tstoreRawPointer(this.firstBlock.data, TAIL_INDEX, this.firstBlock.pointer);\n\n\t\tlet liveItems = 0;\n\t\tlet positionBits = this.positionBits;\n\t\tlet positionMask = this.positionMask;\n\t\tlet nextBlockPointer = firstBlockPointer;\n\t\twhile(nextBlockPointer) {\n\t\t\tlet position = nextBlockPointer & positionMask;\n\t\t\tlet memPool = this.memory.buffers[position];\n\t\t\t// Short circuit iterations if we can't access memory\n\t\t\tif(!memPool) {\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\tlet view = this.getBufferView(position)!;\n\t\t\tlet byteOffset = nextBlockPointer >>> positionBits;\n\t\t\tlet nodeIndex = byteOffset >>> 2;\n\t\t\tnextBlockPointer = Atomics.load(view, nodeIndex + NODE_NEXT_INDEX);\n\n\t\t\tif(!Atomics.load(view, nodeIndex + NODE_DELETED_INDEX)) {\n\t\t\t\tliveItems++;\n\t\t\t\tif(this.onDelete) {\n\t\t\t\t\tthis.onDelete(this.getDataBlock(view.buffer, byteOffset));\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tmemPool.free(byteOffset);\n\t\t}\n\n\t\t// Only subtract the live nodes we detached so a concurrent insert's increment stays accurate\n\t\tAtomics.sub(this.firstBlock.data, LENGTH_INDEX, liveItems);\n\t\t// The whole chain (dead nodes included) is gone; no new dead nodes can appear while we hold the reclaim lock\n\t\tAtomics.store(this.firstBlock.data, DEAD_COUNT_INDEX, 0);\n\t}\n\n\t*[Symbol.iterator]() {\n\t\tlet currentIndex = 0;\n\t\tlet positionBits = this.positionBits;\n\t\tlet positionMask = this.positionMask;\n\t\t// Shared read lock: many walkers (and lock-free inserts) run at once, but it blocks a reclaim from freeing a node out\n\t\t// from under us. Held for the whole walk and released in finally, so an early break/throw still frees it.\n\t\treadLock(this.lockArray);\n\t\ttry {\n\t\t\tlet nextPointer = loadRawPointer(this.firstBlock.data, HEAD_INDEX);\n\t\t\twhile(nextPointer) {\n\t\t\t\tlet position = nextPointer & positionMask;\n\t\t\t\tlet view = this.getBufferView(position);\n\t\t\t\t// Short circuit iterations if we can't access memory\n\t\t\t\tif(!view) {\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tlet byteOffset = nextPointer >>> positionBits;\n\t\t\t\tlet nodeIndex = byteOffset >>> 2;\n\t\t\t\tnextPointer = Atomics.load(view, nodeIndex + NODE_NEXT_INDEX);\n\n\t\t\t\t// Skip tombstoned nodes - they stay linked until a reclaim pass unlinks and frees them\n\t\t\t\tif(Atomics.load(view, nodeIndex + NODE_DELETED_INDEX)) {\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\n\t\t\t\tlet buffer = view.buffer;\n\t\t\t\tlet deletedIndex = nodeIndex + NODE_DELETED_INDEX;\n\t\t\t\t// Plain object with an eager data view: V8 keeps this monomorphic and fast. A lazy `get data()` accessor is far\n\t\t\t\t// slower to allocate than the payload view it would save, so pay for the view up front.\n\t\t\t\tyield {\n\t\t\t\t\tdata: this.getDataBlock(buffer, byteOffset),\n\t\t\t\t\tindex: currentIndex,\n\t\t\t\t\t// Logical delete: tombstone the node and drop length. Returns false if another thread beat us to it.\n\t\t\t\t\tdeleteCurrent: (): boolean => {\n\t\t\t\t\t\tif(Atomics.compareExchange(view, deletedIndex, 0, 1) === 0) {\n\t\t\t\t\t\t\tAtomics.sub(this.firstBlock.data, LENGTH_INDEX, 1);\n\t\t\t\t\t\t\t// Count the dead node so autoReclaim knows when garbage has caught up to live nodes\n\t\t\t\t\t\t\tAtomics.add(this.firstBlock.data, DEAD_COUNT_INDEX, 1);\n\t\t\t\t\t\t\tif(this.onDelete) {\n\t\t\t\t\t\t\t\tthis.onDelete(this.getDataBlock(buffer, byteOffset));\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\treturn true;\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\treturn false;\n\t\t\t\t\t},\n\t\t\t\t};\n\n\t\t\t\tcurrentIndex++;\n\t\t\t}\n\t\t} finally {\n\t\t\treadUnlock(this.lockArray);\n\t\t}\n\t}\n\n\tforEach(callback: (data: T) => void) {\n\t\tfor(let value of this) {\n\t\t\tcallback(value.data);\n\t\t}\n\t}\n\n\tgetSharedMemory(): SharedListMemory {\n\t\treturn {\n\t\t\tfirstBlock: this.firstBlock.getSharedMemory(),\n\t\t};\n\t}\n\n\tprivate getBufferView(position: number): Uint32Array | undefined {\n\t\tlet view = this.bufferViews[position];\n\t\tif(view) {\n\t\t\treturn view;\n\t\t}\n\n\t\tlet buffer = this.memory.buffers[position];\n\t\tif(!buffer) {\n\t\t\treturn undefined;\n\t\t}\n\n\t\tview = new Uint32Array(buffer.buf);\n\t\tthis.bufferViews[position] = view;\n\t\treturn view;\n\t}\n\n\tprivate getDataBlock(buffer: ArrayBufferLike, nodeByteOffset: number): T {\n\t\tconst startIndex = nodeByteOffset + DATA_BLOCK_BYTE_OFFSET;\n\t\treturn makeArrayView(this.cachedType, buffer, startIndex, this.cachedDataLength) as T;\n\t}\n\n\tfree() {\n\t\tlet { bufferPosition: nextBlockPosition, bufferByteOffset: nextBlockByteOffset } = loadPointer(this.firstBlock.data, HEAD_INDEX, this.positionBits);\n\t\twhile(nextBlockByteOffset) {\n\t\t\tlet allocatedMemory = new AllocatedMemory(this.memory, {\n\t\t\t\tbufferPosition: nextBlockPosition,\n\t\t\t\tbufferByteOffset: nextBlockByteOffset,\n\t\t\t});\n\n\t\t\t({ bufferPosition: nextBlockPosition, bufferByteOffset: nextBlockByteOffset } = loadPointer(allocatedMemory.data, NODE_NEXT_INDEX, this.positionBits));\n\n\t\t\tif(this.onDelete && !Atomics.load(allocatedMemory.data, NODE_DELETED_INDEX)) {\n\t\t\t\tthis.onDelete(this.getDataBlock(allocatedMemory.data.buffer, allocatedMemory.data.byteOffset));\n\t\t\t}\n\n\t\t\tallocatedMemory.free();\n\t\t}\n\n\t\tthis.firstBlock.free();\n\t}\n}\n\ninterface SharedListConfig<T extends NumericArray> {\n\tinitWithBlock?: SharedAllocatedMemory\n\ttype?: TypedArrayConstructor<T>\n\tdataLength?: number\n}\ninterface SharedListMemory {\n\tfirstBlock: SharedAllocatedMemory\n}\n\ninterface SharedListIterable<T extends NumericArray> {\n\tdata: T\n\tindex: number\n\tdeleteCurrent: () => boolean\n}\n\nexport { type SharedListMemory };\n"],"mappings":"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"}
@@ -9,6 +9,7 @@ export default class SharedMap<K extends string | number, V extends NumericArray
9
9
  private lock;
10
10
  private cachedType;
11
11
  private cachedValueUnits;
12
+ private readonly maxTableCapacity;
12
13
  private cachedTablePointer;
13
14
  private cachedTableMemory?;
14
15
  private cachedSlots?;
@@ -28,6 +29,8 @@ export default class SharedMap<K extends string | number, V extends NumericArray
28
29
  setAll(entries: Iterable<readonly [K, V[number]]>): void;
29
30
  private setNoLock;
30
31
  update(key: K, updater: (current: V[number] | undefined) => V[number]): V[number];
32
+ private resizeForInsert;
33
+ private reuseTombstone;
31
34
  get(key: K): V[number] | undefined;
32
35
  getAll(keys: Iterable<K>): Array<V[number] | undefined>;
33
36
  private getNoLock;
@@ -12,6 +12,7 @@ var u = 0, d = 1, f = 2, p = 16, m = 3, h = 4, g = 0, _ = 1, v = 2, y = 3, b = 4
12
12
  lock;
13
13
  cachedType;
14
14
  cachedValueUnits = 1;
15
+ maxTableCapacity;
15
16
  cachedTablePointer = 0;
16
17
  cachedTableMemory;
17
18
  cachedSlots;
@@ -54,7 +55,7 @@ var u = 0, d = 1, f = 2, p = 16, m = 3, h = 4, g = 0, _ = 1, v = 2, y = 3, b = 4
54
55
  this.memory = e, t && "firstBlock" in t ? t.firstBlock instanceof n ? (this.pointerMemory = t.firstBlock, this.initialize(t), this.ownsFirstBlock = !1) : this.pointerMemory = new n(e, {
55
56
  length: S.ALLOCATE_COUNT,
56
57
  ...t.firstBlock
57
- }) : (this.pointerMemory = e.allocUI32(S.ALLOCATE_COUNT), this.initialize(t)), this.lock = new Int32Array(this.pointerMemory.data.buffer, this.pointerMemory.bufferByteOffset + _ * Uint32Array.BYTES_PER_ELEMENT, 1), this.cachedType = Atomics.load(this.pointerMemory.data, x), this.cachedValueUnits = i(this.cachedType);
58
+ }) : (this.pointerMemory = e.allocUI32(S.ALLOCATE_COUNT), this.initialize(t)), this.lock = new Int32Array(this.pointerMemory.data.buffer, this.pointerMemory.bufferByteOffset + _ * Uint32Array.BYTES_PER_ELEMENT, 1), this.cachedType = Atomics.load(this.pointerMemory.data, x), this.cachedValueUnits = i(this.cachedType), this.maxTableCapacity = 2 ** Math.floor(Math.log2(this.memory.maxAllocationLength / (2 + this.cachedValueUnits)));
58
59
  }
59
60
  initialize(e) {
60
61
  let n = e && "type" in e ? e.type : void 0, a = r(n ?? Uint32Array);
@@ -80,8 +81,8 @@ var u = 0, d = 1, f = 2, p = 16, m = 3, h = 4, g = 0, _ = 1, v = 2, y = 3, b = 4
80
81
  let e = this.pointerMemory.data, n = e[v] + e[y] + t.length;
81
82
  if (n * h > e[b] * m) {
82
83
  let t = e[b];
83
- for (; n * h > t * m;) t *= 2;
84
- this.resize(t);
84
+ for (; n * h > t * m && t < this.maxTableCapacity;) t *= 2;
85
+ t > e[b] && this.resize(t);
85
86
  }
86
87
  for (let [e, n] of t) this.setNoLock(e, n);
87
88
  } finally {
@@ -90,7 +91,7 @@ var u = 0, d = 1, f = 2, p = 16, m = 3, h = 4, g = 0, _ = 1, v = 2, y = 3, b = 4
90
91
  }
91
92
  setNoLock(e, t) {
92
93
  let n = c(e), r = this.pointerMemory.data;
93
- (r[v] + r[y] + 1) * h > r[b] * m && this.resize(), this.ensureTable();
94
+ (r[v] + r[y] + 1) * h > r[b] * m && this.resizeForInsert(e), this.ensureTable();
94
95
  let i = this.cachedSlots, a = this.cachedValues, o = this.cachedCapacity, s = o - 1, p = o, g = l(n) & s, _ = -1;
95
96
  for (;;) {
96
97
  let e = i[g];
@@ -99,8 +100,13 @@ var u = 0, d = 1, f = 2, p = 16, m = 3, h = 4, g = 0, _ = 1, v = 2, y = 3, b = 4
99
100
  i[e] = d, i[p + e] = n, a[e] = t, r[v]++, _ >= 0 && r[y]--;
100
101
  return;
101
102
  }
102
- if (e === f) _ < 0 && (_ = g);
103
- else if (i[p + g] === n) {
103
+ if (e === f) {
104
+ if (_ < 0) _ = g;
105
+ else if (g === _) {
106
+ this.reuseTombstone(g, n, t);
107
+ return;
108
+ }
109
+ } else if (i[p + g] === n) {
104
110
  a[g] = t;
105
111
  return;
106
112
  }
@@ -111,26 +117,37 @@ var u = 0, d = 1, f = 2, p = 16, m = 3, h = 4, g = 0, _ = 1, v = 2, y = 3, b = 4
111
117
  let n = c(e);
112
118
  o(this.lock);
113
119
  try {
114
- let e = this.pointerMemory.data;
115
- (e[v] + e[y] + 1) * h > e[b] * m && this.resize(), this.ensureTable();
116
- let r = this.cachedSlots, i = this.cachedValues, a = this.cachedCapacity, o = a - 1, s = a, c = l(n) & o, p = -1;
120
+ let r = this.pointerMemory.data;
121
+ (r[v] + r[y] + 1) * h > r[b] * m && this.resizeForInsert(e), this.ensureTable();
122
+ let i = this.cachedSlots, a = this.cachedValues, o = this.cachedCapacity, s = o - 1, c = l(n) & s, p = -1;
117
123
  for (;;) {
118
- let a = r[c];
119
- if (a === u) {
120
- let a = t(void 0), o = p >= 0 ? p : c;
121
- return r[o] = d, r[s + o] = n, i[o] = a, e[v]++, p >= 0 && e[y]--, a;
124
+ let e = i[c];
125
+ if (e === u) {
126
+ let e = t(void 0), s = p >= 0 ? p : c;
127
+ return i[s] = d, i[o + s] = n, a[s] = e, r[v]++, p >= 0 && r[y]--, e;
122
128
  }
123
- if (a === f) p < 0 && (p = c);
124
- else if (r[s + c] === n) {
125
- let e = t(i[c]);
126
- return i[c] = e, e;
129
+ if (e === f) {
130
+ if (c === p) return this.reuseTombstone(c, n, t);
131
+ p < 0 && (p = c);
132
+ } else if (i[o + c] === n) {
133
+ let e = t(a[c]);
134
+ return a[c] = e, e;
127
135
  }
128
- c = c + 1 & o;
136
+ c = c + 1 & s;
129
137
  }
130
138
  } finally {
131
139
  s(this.lock);
132
140
  }
133
141
  }
142
+ resizeForInsert(e) {
143
+ let t = this.pointerMemory.data[v], n = this.pointerMemory.data[b];
144
+ if (n < this.maxTableCapacity || (t + 1) * h <= n * m) this.resize();
145
+ else if (t === n && this.getNoLock(e) === void 0) throw Error(`SharedMap is full at capacity ${n}: a larger table would exceed the largest contiguous allocation. Use larger buffers or shard across multiple maps.`);
146
+ }
147
+ reuseTombstone(e, t, n) {
148
+ let r = typeof n == "function" ? n(void 0) : n, i = this.cachedSlots, a = this.cachedValues;
149
+ return i[e] = d, i[this.cachedCapacity + e] = t, a[e] = r, this.pointerMemory.data[v]++, this.pointerMemory.data[y]--, r;
150
+ }
134
151
  get(e) {
135
152
  o(this.lock);
136
153
  try {
@@ -152,13 +169,13 @@ var u = 0, d = 1, f = 2, p = 16, m = 3, h = 4, g = 0, _ = 1, v = 2, y = 3, b = 4
152
169
  getNoLock(e) {
153
170
  let t = c(e);
154
171
  this.ensureTable();
155
- let n = this.cachedSlots, r = this.cachedValues, i = this.cachedCapacity, a = i - 1, o = i, s = l(t) & a;
156
- for (;;) {
172
+ let n = this.cachedSlots, r = this.cachedValues, i = this.cachedCapacity, a = i - 1, o = i, s = l(t) & a, f = s;
173
+ do {
157
174
  let e = n[s];
158
175
  if (e === u) return;
159
176
  if (e === d && n[o + s] === t) return r[s];
160
177
  s = s + 1 & a;
161
- }
178
+ } while (s !== f);
162
179
  }
163
180
  has(e) {
164
181
  return this.get(e) !== void 0;
@@ -168,13 +185,14 @@ var u = 0, d = 1, f = 2, p = 16, m = 3, h = 4, g = 0, _ = 1, v = 2, y = 3, b = 4
168
185
  o(this.lock);
169
186
  try {
170
187
  this.ensureTable();
171
- let e = this.pointerMemory.data, n = this.cachedSlots, r = this.cachedCapacity, i = r - 1, a = r, o = l(t) & i;
172
- for (;;) {
188
+ let e = this.pointerMemory.data, n = this.cachedSlots, r = this.cachedCapacity, i = r - 1, a = r, o = l(t) & i, s = o;
189
+ do {
173
190
  let r = n[o];
174
191
  if (r === u) return !1;
175
192
  if (r === d && n[a + o] === t) return n[o] = f, e[v]--, e[y]++, !0;
176
193
  o = o + 1 & i;
177
- }
194
+ } while (o !== s);
195
+ return !1;
178
196
  } finally {
179
197
  s(this.lock);
180
198
  }
@@ -1 +1 @@
1
- {"version":3,"file":"shared-map.js","names":[],"sources":["../src/shared-map.ts"],"sourcesContent":["import AllocatedMemory, { type SharedAllocatedMemory } from './allocated-memory';\nimport type MemoryHeap from './memory-heap';\nimport { getPointer, storeRawPointer } from './utils/pointer';\nimport { get32BitHash, mix32 } from './utils/hash';\nimport { lock, unlock, SIMPLE_LOCK_ALLOCATE_COUNT } from './lock/simple-lock';\nimport { getArrayTypeCode, getByteMultipler, makeArrayView, type NumericArray, type NumericArrayIO, type TypedArrayConstructor } from './utils/array-type';\n\n// Open-addressing hash map with linear probing, held in one contiguous table (no per-bucket lists, no per-operation\n// allocation). A single exclusive lock guards the table so slot reads/writes inside the critical section can be plain\n// memory access instead of per-slot Atomics; the lock's acquire/release provides the cross-thread happens-before.\n//\n// The table uses a Structure-of-Arrays layout: three contiguous regions - states | keys | values - so probing touches\n// the dense states and keys regions (more candidates per cache line) and iteration walks values densely. capacity is\n// always a power of two, so slot = mix(key) & (capacity - 1). state distinguishes EMPTY/FULL/TOMBSTONE, so key 0 and\n// value 0 need no sentinel.\n//\n// Keys are always stored as the 32-bit hash (a numeric key is its own hash); values are stored in the typed array the\n// map was created with (Uint32Array by default, or Int32Array / Float32Array). Iteration is a best-effort scan that is\n// not lock-guarded, so it must not run concurrently with mutation from another thread. A single allocation can't span\n// buffers, so the table is bounded by MemoryHeap.maxAllocationLength.\n\nconst EMPTY = 0;\nconst FULL = 1;\nconst TOMBSTONE = 2;\n\nconst DEFAULT_CAPACITY = 16;\n// Grow when (occupied + tombstones) / capacity exceeds this. Kept as a fraction to avoid floating point in the hot path.\nconst LOAD_NUMERATOR = 3;\nconst LOAD_DENOMINATOR = 4;\n\n// Metadata block layout (pointerMemory)\nconst TABLE_POINTER_INDEX = 0;\nconst LOCK_INDEX = 1;\nconst LENGTH_INDEX = 2;\nconst TOMBSTONE_INDEX = 3;\nconst CAPACITY_INDEX = 4;\nconst TYPE_INDEX = 5;\n\nexport default class SharedMap<K extends string | number, V extends NumericArray = Uint32Array> {\n\tstatic readonly ALLOCATE_COUNT = 6;\n\n\tprivate memory: MemoryHeap;\n\tprivate pointerMemory: AllocatedMemory;\n\t// False when pointerMemory is a slice of another structure's memory (e.g. inlined in SharedQuadtree); that owner frees it\n\tprivate ownsFirstBlock = true;\n\tprivate lock: Int32Array;\n\t// Value type is written once at creation and never changes, so resolve it once instead of reading metadata per op\n\tprivate cachedType: number;\n\t// u32 units per value slot: 1 for 32-bit types, 2 for 64-bit (float64/int64/uint64). The values region and every\n\t// table allocation scale by this.\n\tprivate cachedValueUnits = 1;\n\n\t// Cache the resolved views so most operations avoid rebuilding them. Rebuild whenever the table pointer OR the capacity\n\t// changes: the pointer alone is not enough because the allocator recycles freed addresses (split:false), so a rehash can\n\t// land a differently-sized table at the same address (ABA). The cached views are plain windows into shared memory, so\n\t// they always reflect the current bytes at that address; only the derived capacity (mask/offsets) can go stale, and the\n\t// capacity check catches exactly that.\n\tprivate cachedTablePointer = 0;\n\tprivate cachedTableMemory?: AllocatedMemory;\n\tprivate cachedSlots?: Uint32Array; // whole block as u32: states region, then keys region\n\tprivate cachedValues?: V; // typed view over the values region\n\tprivate cachedCapacity = 0;\n\t// Callers hold the lock, so the acquire fence already published the latest pointer/capacity - plain reads are enough\n\t// and avoid two Atomics.load per operation on the hot path.\n\tprivate ensureTable() {\n\t\tlet data = this.pointerMemory.data;\n\t\tlet pointer = data[TABLE_POINTER_INDEX];\n\t\tlet capacity = data[CAPACITY_INDEX];\n\t\tif(pointer === this.cachedTablePointer && capacity === this.cachedCapacity && this.cachedSlots) {\n\t\t\treturn;\n\t\t}\n\n\t\tlet table = new AllocatedMemory(this.memory, getPointer(pointer, this.memory.positionBits));\n\t\tthis.cachedTableMemory = table;\n\t\tthis.cachedSlots = table.data;\n\t\tthis.cachedValues = this.makeValuesView(table, capacity);\n\t\tthis.cachedCapacity = capacity;\n\t\tthis.cachedTablePointer = pointer;\n\t}\n\tprivate makeValuesView(table: AllocatedMemory, capacity: number): V {\n\t\t// states and keys each take one u32 per slot, so the values region starts after 2 * capacity u32s\n\t\tlet byteOffset = table.bufferByteOffset + capacity * 2 * Uint32Array.BYTES_PER_ELEMENT;\n\t\treturn makeArrayView(this.cachedType, table.data.buffer, byteOffset, capacity) as V;\n\t}\n\t// u32s a table of this capacity needs: states + keys (one each per slot) plus the values region, which is\n\t// cachedValueUnits u32s per slot.\n\tprivate tableLength(capacity: number): number {\n\t\treturn capacity * (2 + this.cachedValueUnits);\n\t}\n\n\tget length(): number {\n\t\treturn Atomics.load(this.pointerMemory.data, LENGTH_INDEX);\n\t}\n\tget capacity(): number {\n\t\treturn Atomics.load(this.pointerMemory.data, CAPACITY_INDEX);\n\t}\n\tget maxHash(): number {\n\t\treturn this.capacity;\n\t}\n\tget type(): number {\n\t\treturn this.cachedType;\n\t}\n\t// Metadata block plus the single table allocation the map currently points at.\n\tget usedMemory(): number {\n\t\tlock(this.lock);\n\t\ttry {\n\t\t\tthis.ensureTable();\n\t\t\t// When inlined, the owner already counts pointerMemory as part of its own firstBlock\n\t\t\tlet ownMemory = this.ownsFirstBlock ? this.pointerMemory.usedMemory : 0;\n\t\t\treturn ownMemory + this.cachedTableMemory!.usedMemory;\n\t\t} finally {\n\t\t\tunlock(this.lock);\n\t\t}\n\t}\n\n\tconstructor(memory: MemoryHeap, config?: SharedMapConfig<V> | SharedMapMemory | SharedMapMemory & SharedMapConfig<V>) {\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 SharedQuadtree) for us to initialize in\n\t\t\t// place; a plain pointer is the serialized clone path, whose table already exists and is read from metadata.\n\t\t\tif(config.firstBlock instanceof AllocatedMemory) {\n\t\t\t\tthis.pointerMemory = config.firstBlock;\n\t\t\t\tthis.initialize(config);\n\t\t\t\tthis.ownsFirstBlock = false;\n\t\t\t} else {\n\t\t\t\tthis.pointerMemory = new AllocatedMemory(memory, { length: SharedMap.ALLOCATE_COUNT, ...config.firstBlock });\n\t\t\t}\n\t\t} else {\n\t\t\tthis.pointerMemory = memory.allocUI32(SharedMap.ALLOCATE_COUNT);\n\t\t\tthis.initialize(config);\n\t\t}\n\t\tthis.lock = new Int32Array(this.pointerMemory.data.buffer, this.pointerMemory.bufferByteOffset + LOCK_INDEX * Uint32Array.BYTES_PER_ELEMENT, SIMPLE_LOCK_ALLOCATE_COUNT);\n\t\tthis.cachedType = Atomics.load(this.pointerMemory.data, TYPE_INDEX);\n\t\tthis.cachedValueUnits = getByteMultipler(this.cachedType);\n\t}\n\n\t// Allocate the initial table and write the metadata. pointerMemory must already be set. Used by both the fresh-alloc\n\t// path and the inline-in-place path (where pointerMemory is a slice reserved by the owner).\n\tprivate initialize(config?: SharedMapConfig<V> | SharedMapMemory) {\n\t\tlet type = config && 'type' in config ? config.type : undefined;\n\t\tlet typeCode = getArrayTypeCode(type ?? Uint32Array);\n\t\tthis.cachedValueUnits = getByteMultipler(typeCode);\n\t\tlet requestedCapacity = config && 'capacity' in config ? config.capacity : undefined;\n\t\tlet capacity = DEFAULT_CAPACITY;\n\t\twhile(capacity < (requestedCapacity ?? DEFAULT_CAPACITY)) {\n\t\t\tcapacity *= 2;\n\t\t}\n\t\tif(capacity * (2 + this.cachedValueUnits) > this.memory.maxAllocationLength) {\n\t\t\tthrow new Error(`SharedMap capacity ${capacity} exceeds the largest contiguous allocation`);\n\t\t}\n\t\tlet table = this.memory.allocUI32(capacity * (2 + this.cachedValueUnits));\n\t\tstoreRawPointer(this.pointerMemory.data, TABLE_POINTER_INDEX, table.pointer);\n\t\tAtomics.store(this.pointerMemory.data, CAPACITY_INDEX, capacity);\n\t\tAtomics.store(this.pointerMemory.data, TYPE_INDEX, typeCode);\n\t}\n\n\tset(key: K, value: V[number]) {\n\t\tlock(this.lock);\n\t\ttry {\n\t\t\tthis.setNoLock(key, value);\n\t\t} finally {\n\t\t\tunlock(this.lock);\n\t\t}\n\t}\n\n\tsetAll(entries: Iterable<readonly [K, V[number]]>) {\n\t\tlet batch = Array.from(entries);\n\t\tlock(this.lock);\n\t\ttry {\n\t\t\tlet data = this.pointerMemory.data;\n\t\t\tlet projected = data[LENGTH_INDEX] + data[TOMBSTONE_INDEX] + batch.length;\n\t\t\tif(projected * LOAD_DENOMINATOR > data[CAPACITY_INDEX] * LOAD_NUMERATOR) {\n\t\t\t\tlet capacity = data[CAPACITY_INDEX];\n\t\t\t\twhile(projected * LOAD_DENOMINATOR > capacity * LOAD_NUMERATOR) {\n\t\t\t\t\tcapacity *= 2;\n\t\t\t\t}\n\t\t\t\tthis.resize(capacity);\n\t\t\t}\n\t\t\tfor(let [key, value] of batch) {\n\t\t\t\tthis.setNoLock(key, value);\n\t\t\t}\n\t\t} finally {\n\t\t\tunlock(this.lock);\n\t\t}\n\t}\n\n\tprivate setNoLock(key: K, value: V[number]) {\n\t\tlet fullHashKey = get32BitHash(key);\n\t\tlet data = this.pointerMemory.data;\n\t\tlet length = data[LENGTH_INDEX];\n\t\tlet tombstones = data[TOMBSTONE_INDEX];\n\t\tif((length + tombstones + 1) * LOAD_DENOMINATOR > data[CAPACITY_INDEX] * LOAD_NUMERATOR) {\n\t\t\tthis.resize();\n\t\t}\n\n\t\tthis.ensureTable();\n\t\tlet slots = this.cachedSlots!;\n\t\tlet values = this.cachedValues! as NumericArrayIO;\n\t\tlet capacity = this.cachedCapacity;\n\t\tlet mask = capacity - 1;\n\t\tlet keysBase = capacity;\n\t\tlet idx = mix32(fullHashKey) & mask;\n\t\tlet firstTomb = -1;\n\t\t// eslint-disable-next-line no-constant-condition\n\t\twhile(true) {\n\t\t\tlet state = slots[idx];\n\t\t\tif(state === EMPTY) {\n\t\t\t\tlet target = firstTomb >= 0 ? firstTomb : idx;\n\t\t\t\tslots[target] = FULL;\n\t\t\t\tslots[keysBase + target] = fullHashKey;\n\t\t\t\tvalues[target] = value;\n\t\t\t\tdata[LENGTH_INDEX]++;\n\t\t\t\tif(firstTomb >= 0) {\n\t\t\t\t\tdata[TOMBSTONE_INDEX]--;\n\t\t\t\t}\n\t\t\t\treturn;\n\t\t\t} else if(state === TOMBSTONE) {\n\t\t\t\tif(firstTomb < 0) {\n\t\t\t\t\tfirstTomb = idx;\n\t\t\t\t}\n\t\t\t} else if(slots[keysBase + idx] === fullHashKey) {\n\t\t\t\tvalues[idx] = value;\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tidx = (idx + 1) & mask;\n\t\t}\n\t}\n\n\t// Atomic read-modify-write under the table lock: resolves the current value (undefined if the key is\n\t// absent), calls updater, and stores the result - so two threads mutating the same key never lose an\n\t// update the way a separate get() + set() can. updater runs inside the lock and must be synchronous and\n\t// must not touch this same map (the lock is not reentrant).\n\tupdate(key: K, updater: (current: V[number] | undefined) => V[number]): V[number] {\n\t\tlet fullHashKey = get32BitHash(key);\n\n\t\tlock(this.lock);\n\t\ttry {\n\t\t\tlet data = this.pointerMemory.data;\n\t\t\tlet length = data[LENGTH_INDEX];\n\t\t\tlet tombstones = data[TOMBSTONE_INDEX];\n\t\t\tif((length + tombstones + 1) * LOAD_DENOMINATOR > data[CAPACITY_INDEX] * LOAD_NUMERATOR) {\n\t\t\t\tthis.resize();\n\t\t\t}\n\n\t\t\tthis.ensureTable();\n\t\t\tlet slots = this.cachedSlots!;\n\t\t\tlet values = this.cachedValues! as NumericArrayIO;\n\t\t\tlet capacity = this.cachedCapacity;\n\t\t\tlet mask = capacity - 1;\n\t\t\tlet keysBase = capacity;\n\t\t\tlet idx = mix32(fullHashKey) & mask;\n\t\t\tlet firstTomb = -1;\n\t\t\t// eslint-disable-next-line no-constant-condition\n\t\t\twhile(true) {\n\t\t\t\tlet state = slots[idx];\n\t\t\t\tif(state === EMPTY) {\n\t\t\t\t\tlet newValue = updater(undefined);\n\t\t\t\t\tlet target = firstTomb >= 0 ? firstTomb : idx;\n\t\t\t\t\tslots[target] = FULL;\n\t\t\t\t\tslots[keysBase + target] = fullHashKey;\n\t\t\t\t\tvalues[target] = newValue;\n\t\t\t\t\tdata[LENGTH_INDEX]++;\n\t\t\t\t\tif(firstTomb >= 0) {\n\t\t\t\t\t\tdata[TOMBSTONE_INDEX]--;\n\t\t\t\t\t}\n\t\t\t\t\treturn newValue;\n\t\t\t\t} else if(state === TOMBSTONE) {\n\t\t\t\t\tif(firstTomb < 0) {\n\t\t\t\t\t\tfirstTomb = idx;\n\t\t\t\t\t}\n\t\t\t\t} else if(slots[keysBase + idx] === fullHashKey) {\n\t\t\t\t\tlet newValue = updater(values[idx]);\n\t\t\t\t\tvalues[idx] = newValue;\n\t\t\t\t\treturn newValue;\n\t\t\t\t}\n\n\t\t\t\tidx = (idx + 1) & mask;\n\t\t\t}\n\t\t} finally {\n\t\t\tunlock(this.lock);\n\t\t}\n\t}\n\n\tget(key: K): V[number] | undefined {\n\t\tlock(this.lock);\n\t\ttry {\n\t\t\treturn this.getNoLock(key);\n\t\t} finally {\n\t\t\tunlock(this.lock);\n\t\t}\n\t}\n\n\tgetAll(keys: Iterable<K>): Array<V[number] | undefined> {\n\t\tlock(this.lock);\n\t\ttry {\n\t\t\tlet values: Array<V[number] | undefined> = [];\n\t\t\tfor(let key of keys) {\n\t\t\t\tvalues.push(this.getNoLock(key));\n\t\t\t}\n\t\t\treturn values;\n\t\t} finally {\n\t\t\tunlock(this.lock);\n\t\t}\n\t}\n\n\tprivate getNoLock(key: K): V[number] | undefined {\n\t\tlet fullHashKey = get32BitHash(key);\n\t\tthis.ensureTable();\n\t\tlet slots = this.cachedSlots!;\n\t\tlet values = this.cachedValues! as NumericArrayIO;\n\t\tlet capacity = this.cachedCapacity;\n\t\tlet mask = capacity - 1;\n\t\tlet keysBase = capacity;\n\t\tlet idx = mix32(fullHashKey) & mask;\n\t\t// eslint-disable-next-line no-constant-condition\n\t\twhile(true) {\n\t\t\tlet state = slots[idx];\n\t\t\tif(state === EMPTY) {\n\t\t\t\treturn undefined;\n\t\t\t}\n\t\t\tif(state === FULL && slots[keysBase + idx] === fullHashKey) {\n\t\t\t\treturn values[idx];\n\t\t\t}\n\t\t\tidx = (idx + 1) & mask;\n\t\t}\n\t}\n\thas(key: K): boolean {\n\t\treturn this.get(key) !== undefined;\n\t}\n\n\tdelete(key: K): boolean {\n\t\tlet fullHashKey = get32BitHash(key);\n\n\t\tlock(this.lock);\n\t\ttry {\n\t\t\tthis.ensureTable();\n\t\t\tlet data = this.pointerMemory.data;\n\t\t\tlet slots = this.cachedSlots!;\n\t\t\tlet capacity = this.cachedCapacity;\n\t\t\tlet mask = capacity - 1;\n\t\t\tlet keysBase = capacity;\n\t\t\tlet idx = mix32(fullHashKey) & mask;\n\t\t\t// eslint-disable-next-line no-constant-condition\n\t\t\twhile(true) {\n\t\t\t\tlet state = slots[idx];\n\t\t\t\tif(state === EMPTY) {\n\t\t\t\t\treturn false;\n\t\t\t\t}\n\t\t\t\tif(state === FULL && slots[keysBase + idx] === fullHashKey) {\n\t\t\t\t\tslots[idx] = TOMBSTONE;\n\t\t\t\t\tdata[LENGTH_INDEX]--;\n\t\t\t\t\tdata[TOMBSTONE_INDEX]++;\n\t\t\t\t\treturn true;\n\t\t\t\t}\n\t\t\t\tidx = (idx + 1) & mask;\n\t\t\t}\n\t\t} finally {\n\t\t\tunlock(this.lock);\n\t\t}\n\t}\n\n\t// Best-effort scan (see class note): not lock-guarded, so must not run concurrently with writes from another thread.\n\t*[Symbol.iterator](): IterableIterator<[number, V[number]]> {\n\t\tthis.ensureTable();\n\t\tlet slots = this.cachedSlots!;\n\t\tlet values = this.cachedValues! as NumericArrayIO;\n\t\tlet capacity = this.cachedCapacity;\n\t\tlet keysBase = capacity;\n\t\tfor(let i = 0; i < capacity; i++) {\n\t\t\tif(slots[i] === FULL) {\n\t\t\t\tyield [slots[keysBase + i], values[i]];\n\t\t\t}\n\t\t}\n\t}\n\n\t// Rehash into a fresh table. Doubles capacity when live entries are crowding the table; when tombstones are the cause,\n\t// a same-size table already reclaims them. Caller must hold the lock.\n\tprivate resize(requestedCapacity?: number) {\n\t\tthis.ensureTable();\n\t\tlet oldTable = this.cachedTableMemory!;\n\t\tlet oldSlots = this.cachedSlots!;\n\t\tlet oldValues = this.cachedValues! as NumericArrayIO;\n\t\tlet oldCapacity = this.cachedCapacity;\n\t\tlet oldKeysBase = oldCapacity;\n\t\tlet length = Atomics.load(this.pointerMemory.data, LENGTH_INDEX);\n\n\t\tlet newCapacity = requestedCapacity ?? oldCapacity;\n\t\tif(requestedCapacity === undefined && (length + 1) * LOAD_DENOMINATOR > oldCapacity * LOAD_NUMERATOR) {\n\t\t\tnewCapacity = oldCapacity * 2;\n\t\t}\n\t\t// A table is one contiguous allocation, so it can never exceed what a single buffer can hand out. Fail here with an\n\t\t// actionable message instead of letting allocUI32 throw a cryptic \"Unable to allocate N numbers\" from deep inside the\n\t\t// heap. Mirrors the same guard in initialize().\n\t\tif(this.tableLength(newCapacity) > this.memory.maxAllocationLength) {\n\t\t\tlet maxEntries = Math.floor(this.memory.maxAllocationLength / (2 + this.cachedValueUnits) * LOAD_NUMERATOR / LOAD_DENOMINATOR);\n\t\t\tthrow new Error(`SharedMap can't grow to capacity ${newCapacity}: its table would exceed the largest contiguous `\n\t\t\t\t+ `allocation (${this.memory.maxAllocationLength} u32s, ~${maxEntries} entries). Use larger buffers or shard across multiple maps.`);\n\t\t}\n\t\tlet newTable = this.memory.allocUI32(this.tableLength(newCapacity));\n\t\tlet newSlots = newTable.data;\n\t\tlet newValues = this.makeValuesView(newTable, newCapacity) as NumericArrayIO;\n\t\tlet mask = newCapacity - 1;\n\t\tlet newKeysBase = newCapacity;\n\n\t\tfor(let i = 0; i < oldCapacity; i++) {\n\t\t\tif(oldSlots[i] !== FULL) {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tlet key = oldSlots[oldKeysBase + i];\n\t\t\tlet value = oldValues[i];\n\t\t\tlet idx = mix32(key) & mask;\n\t\t\t// Fresh table has no tombstones, so probe straight to the first empty slot\n\t\t\twhile(newSlots[idx] !== EMPTY) {\n\t\t\t\tidx = (idx + 1) & mask;\n\t\t\t}\n\t\t\tnewSlots[idx] = FULL;\n\t\t\tnewSlots[newKeysBase + idx] = key;\n\t\t\tnewValues[idx] = value;\n\t\t}\n\n\t\tstoreRawPointer(this.pointerMemory.data, TABLE_POINTER_INDEX, newTable.pointer);\n\t\tAtomics.store(this.pointerMemory.data, CAPACITY_INDEX, newCapacity);\n\t\tAtomics.store(this.pointerMemory.data, TOMBSTONE_INDEX, 0);\n\t\t// Invalidate the cached views so the next access rebuilds against the new table\n\t\tthis.cachedTablePointer = 0;\n\t\tthis.cachedSlots = undefined;\n\t\toldTable.free();\n\t}\n\n\tfree() {\n\t\tthis.ensureTable();\n\t\tthis.cachedTableMemory!.free();\n\t\tif(this.ownsFirstBlock) {\n\t\t\tthis.pointerMemory.free();\n\t\t}\n\t}\n\n\tgetSharedMemory(): SharedMapMemory {\n\t\treturn {\n\t\t\tfirstBlock: this.pointerMemory.getSharedMemory(),\n\t\t};\n\t}\n}\n\ninterface SharedMapConfig<V extends NumericArray> {\n\ttype?: TypedArrayConstructor<V>\n\tcapacity?: number\n}\ninterface SharedMapMemory {\n\tfirstBlock: SharedAllocatedMemory | AllocatedMemory\n}\n\nexport type { SharedMapConfig, SharedMapMemory };\n"],"mappings":"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"}
1
+ {"version":3,"file":"shared-map.js","names":["AllocatedMemory","EMPTY","FULL","TOMBSTONE","DEFAULT_CAPACITY","LOAD_NUMERATOR","LOAD_DENOMINATOR","TABLE_POINTER_INDEX","LOCK_INDEX","LENGTH_INDEX","TOMBSTONE_INDEX","CAPACITY_INDEX","TYPE_INDEX","SharedMap","data","pointer","capacity","table","getPointer","byteOffset","makeArrayView","memory","config","getByteMultipler","type","typeCode","getArrayTypeCode","requestedCapacity","storeRawPointer","key","value","batch","entries","projected","fullHashKey","get32BitHash","slots","values","mask","keysBase","idx","mix32","firstTomb","state","target","newValue","updater","length","valueOrUpdater","keys","start","oldTable","oldSlots","oldValues","oldCapacity","oldKeysBase","newCapacity","maxEntries","newTable","newSlots","newValues","newKeysBase","i"],"sources":["../src/shared-map.ts"],"sourcesContent":["import AllocatedMemory, { type SharedAllocatedMemory } from './allocated-memory';\nimport type MemoryHeap from './memory-heap';\nimport { getPointer, storeRawPointer } from './utils/pointer';\nimport { get32BitHash, mix32 } from './utils/hash';\nimport { lock, unlock, SIMPLE_LOCK_ALLOCATE_COUNT } from './lock/simple-lock';\nimport { getArrayTypeCode, getByteMultipler, makeArrayView, type NumericArray, type NumericArrayIO, type TypedArrayConstructor } from './utils/array-type';\n\n// Open-addressing hash map with linear probing, held in one contiguous table (no per-bucket lists, no per-operation\n// allocation). A single exclusive lock guards the table so slot reads/writes inside the critical section can be plain\n// memory access instead of per-slot Atomics; the lock's acquire/release provides the cross-thread happens-before.\n//\n// The table uses a Structure-of-Arrays layout: three contiguous regions - states | keys | values - so probing touches\n// the dense states and keys regions (more candidates per cache line) and iteration walks values densely. capacity is\n// always a power of two, so slot = mix(key) & (capacity - 1). state distinguishes EMPTY/FULL/TOMBSTONE, so key 0 and\n// value 0 need no sentinel.\n//\n// Keys are always stored as the 32-bit hash (a numeric key is its own hash); values are stored in the typed array the\n// map was created with (Uint32Array by default, or Int32Array / Float32Array). Iteration is a best-effort scan that is\n// not lock-guarded, so it must not run concurrently with mutation from another thread. A single allocation can't span\n// buffers, so the table is bounded by MemoryHeap.maxAllocationLength.\n\nconst EMPTY = 0;\nconst FULL = 1;\nconst TOMBSTONE = 2;\n\nconst DEFAULT_CAPACITY = 16;\n// Grow when (occupied + tombstones) / capacity exceeds this. Kept as a fraction to avoid floating point in the hot path.\nconst LOAD_NUMERATOR = 3;\nconst LOAD_DENOMINATOR = 4;\n\n// Metadata block layout (pointerMemory)\nconst TABLE_POINTER_INDEX = 0;\nconst LOCK_INDEX = 1;\nconst LENGTH_INDEX = 2;\nconst TOMBSTONE_INDEX = 3;\nconst CAPACITY_INDEX = 4;\nconst TYPE_INDEX = 5;\n\nexport default class SharedMap<K extends string | number, V extends NumericArray = Uint32Array> {\n\tstatic readonly ALLOCATE_COUNT = 6;\n\n\tprivate memory: MemoryHeap;\n\tprivate pointerMemory: AllocatedMemory;\n\t// False when pointerMemory is a slice of another structure's memory (e.g. inlined in SharedQuadtree); that owner frees it\n\tprivate ownsFirstBlock = true;\n\tprivate lock: Int32Array;\n\t// Value type is written once at creation and never changes, so resolve it once instead of reading metadata per op\n\tprivate cachedType: number;\n\t// u32 units per value slot: 1 for 32-bit types, 2 for 64-bit (float64/int64/uint64). The values region and every\n\t// table allocation scale by this.\n\tprivate cachedValueUnits = 1;\n\tprivate readonly maxTableCapacity: number;\n\n\t// Cache the resolved views so most operations avoid rebuilding them. Rebuild whenever the table pointer OR the capacity\n\t// changes: the pointer alone is not enough because the allocator recycles freed addresses (split:false), so a rehash can\n\t// land a differently-sized table at the same address (ABA). The cached views are plain windows into shared memory, so\n\t// they always reflect the current bytes at that address; only the derived capacity (mask/offsets) can go stale, and the\n\t// capacity check catches exactly that.\n\tprivate cachedTablePointer = 0;\n\tprivate cachedTableMemory?: AllocatedMemory;\n\tprivate cachedSlots?: Uint32Array; // whole block as u32: states region, then keys region\n\tprivate cachedValues?: V; // typed view over the values region\n\tprivate cachedCapacity = 0;\n\t// Callers hold the lock, so the acquire fence already published the latest pointer/capacity - plain reads are enough\n\t// and avoid two Atomics.load per operation on the hot path.\n\tprivate ensureTable() {\n\t\tlet data = this.pointerMemory.data;\n\t\tlet pointer = data[TABLE_POINTER_INDEX];\n\t\tlet capacity = data[CAPACITY_INDEX];\n\t\tif(pointer === this.cachedTablePointer && capacity === this.cachedCapacity && this.cachedSlots) {\n\t\t\treturn;\n\t\t}\n\n\t\tlet table = new AllocatedMemory(this.memory, getPointer(pointer, this.memory.positionBits));\n\t\tthis.cachedTableMemory = table;\n\t\tthis.cachedSlots = table.data;\n\t\tthis.cachedValues = this.makeValuesView(table, capacity);\n\t\tthis.cachedCapacity = capacity;\n\t\tthis.cachedTablePointer = pointer;\n\t}\n\tprivate makeValuesView(table: AllocatedMemory, capacity: number): V {\n\t\t// states and keys each take one u32 per slot, so the values region starts after 2 * capacity u32s\n\t\tlet byteOffset = table.bufferByteOffset + capacity * 2 * Uint32Array.BYTES_PER_ELEMENT;\n\t\treturn makeArrayView(this.cachedType, table.data.buffer, byteOffset, capacity) as V;\n\t}\n\t// u32s a table of this capacity needs: states + keys (one each per slot) plus the values region, which is\n\t// cachedValueUnits u32s per slot.\n\tprivate tableLength(capacity: number): number {\n\t\treturn capacity * (2 + this.cachedValueUnits);\n\t}\n\n\tget length(): number {\n\t\treturn Atomics.load(this.pointerMemory.data, LENGTH_INDEX);\n\t}\n\tget capacity(): number {\n\t\treturn Atomics.load(this.pointerMemory.data, CAPACITY_INDEX);\n\t}\n\tget maxHash(): number {\n\t\treturn this.capacity;\n\t}\n\tget type(): number {\n\t\treturn this.cachedType;\n\t}\n\t// Metadata block plus the single table allocation the map currently points at.\n\tget usedMemory(): number {\n\t\tlock(this.lock);\n\t\ttry {\n\t\t\tthis.ensureTable();\n\t\t\t// When inlined, the owner already counts pointerMemory as part of its own firstBlock\n\t\t\tlet ownMemory = this.ownsFirstBlock ? this.pointerMemory.usedMemory : 0;\n\t\t\treturn ownMemory + this.cachedTableMemory!.usedMemory;\n\t\t} finally {\n\t\t\tunlock(this.lock);\n\t\t}\n\t}\n\n\tconstructor(memory: MemoryHeap, config?: SharedMapConfig<V> | SharedMapMemory | SharedMapMemory & SharedMapConfig<V>) {\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 SharedQuadtree) for us to initialize in\n\t\t\t// place; a plain pointer is the serialized clone path, whose table already exists and is read from metadata.\n\t\t\tif(config.firstBlock instanceof AllocatedMemory) {\n\t\t\t\tthis.pointerMemory = config.firstBlock;\n\t\t\t\tthis.initialize(config);\n\t\t\t\tthis.ownsFirstBlock = false;\n\t\t\t} else {\n\t\t\t\tthis.pointerMemory = new AllocatedMemory(memory, { length: SharedMap.ALLOCATE_COUNT, ...config.firstBlock });\n\t\t\t}\n\t\t} else {\n\t\t\tthis.pointerMemory = memory.allocUI32(SharedMap.ALLOCATE_COUNT);\n\t\t\tthis.initialize(config);\n\t\t}\n\t\tthis.lock = new Int32Array(this.pointerMemory.data.buffer, this.pointerMemory.bufferByteOffset + LOCK_INDEX * Uint32Array.BYTES_PER_ELEMENT, SIMPLE_LOCK_ALLOCATE_COUNT);\n\t\tthis.cachedType = Atomics.load(this.pointerMemory.data, TYPE_INDEX);\n\t\tthis.cachedValueUnits = getByteMultipler(this.cachedType);\n\t\tthis.maxTableCapacity = 2 ** Math.floor(Math.log2(this.memory.maxAllocationLength / (2 + this.cachedValueUnits)));\n\t}\n\n\t// Allocate the initial table and write the metadata. pointerMemory must already be set. Used by both the fresh-alloc\n\t// path and the inline-in-place path (where pointerMemory is a slice reserved by the owner).\n\tprivate initialize(config?: SharedMapConfig<V> | SharedMapMemory) {\n\t\tlet type = config && 'type' in config ? config.type : undefined;\n\t\tlet typeCode = getArrayTypeCode(type ?? Uint32Array);\n\t\tthis.cachedValueUnits = getByteMultipler(typeCode);\n\t\tlet requestedCapacity = config && 'capacity' in config ? config.capacity : undefined;\n\t\tlet capacity = DEFAULT_CAPACITY;\n\t\twhile(capacity < (requestedCapacity ?? DEFAULT_CAPACITY)) {\n\t\t\tcapacity *= 2;\n\t\t}\n\t\tif(capacity * (2 + this.cachedValueUnits) > this.memory.maxAllocationLength) {\n\t\t\tthrow new Error(`SharedMap capacity ${capacity} exceeds the largest contiguous allocation`);\n\t\t}\n\t\tlet table = this.memory.allocUI32(capacity * (2 + this.cachedValueUnits));\n\t\tstoreRawPointer(this.pointerMemory.data, TABLE_POINTER_INDEX, table.pointer);\n\t\tAtomics.store(this.pointerMemory.data, CAPACITY_INDEX, capacity);\n\t\tAtomics.store(this.pointerMemory.data, TYPE_INDEX, typeCode);\n\t}\n\n\tset(key: K, value: V[number]) {\n\t\tlock(this.lock);\n\t\ttry {\n\t\t\tthis.setNoLock(key, value);\n\t\t} finally {\n\t\t\tunlock(this.lock);\n\t\t}\n\t}\n\n\tsetAll(entries: Iterable<readonly [K, V[number]]>) {\n\t\tlet batch = Array.from(entries);\n\t\tlock(this.lock);\n\t\ttry {\n\t\t\tlet data = this.pointerMemory.data;\n\t\t\tlet projected = data[LENGTH_INDEX] + data[TOMBSTONE_INDEX] + batch.length;\n\t\t\tif(projected * LOAD_DENOMINATOR > data[CAPACITY_INDEX] * LOAD_NUMERATOR) {\n\t\t\t\tlet capacity = data[CAPACITY_INDEX];\n\t\t\t\twhile(projected * LOAD_DENOMINATOR > capacity * LOAD_NUMERATOR && capacity < this.maxTableCapacity) {\n\t\t\t\t\tcapacity *= 2;\n\t\t\t\t}\n\t\t\t\tif(capacity > data[CAPACITY_INDEX]) {\n\t\t\t\t\tthis.resize(capacity);\n\t\t\t\t}\n\t\t\t}\n\t\t\tfor(let [key, value] of batch) {\n\t\t\t\tthis.setNoLock(key, value);\n\t\t\t}\n\t\t} finally {\n\t\t\tunlock(this.lock);\n\t\t}\n\t}\n\n\tprivate setNoLock(key: K, value: V[number]) {\n\t\tlet fullHashKey = get32BitHash(key);\n\t\tlet data = this.pointerMemory.data;\n\t\tlet length = data[LENGTH_INDEX];\n\t\tlet tombstones = data[TOMBSTONE_INDEX];\n\t\tif((length + tombstones + 1) * LOAD_DENOMINATOR > data[CAPACITY_INDEX] * LOAD_NUMERATOR) {\n\t\t\tthis.resizeForInsert(key);\n\t\t}\n\n\t\tthis.ensureTable();\n\t\tlet slots = this.cachedSlots!;\n\t\tlet values = this.cachedValues! as NumericArrayIO;\n\t\tlet capacity = this.cachedCapacity;\n\t\tlet mask = capacity - 1;\n\t\tlet keysBase = capacity;\n\t\tlet idx = mix32(fullHashKey) & mask;\n\t\tlet firstTomb = -1;\n\t\t// eslint-disable-next-line no-constant-condition\n\t\twhile(true) {\n\t\t\tlet state = slots[idx];\n\t\t\tif(state === EMPTY) {\n\t\t\t\tlet target = firstTomb >= 0 ? firstTomb : idx;\n\t\t\t\tslots[target] = FULL;\n\t\t\t\tslots[keysBase + target] = fullHashKey;\n\t\t\t\tvalues[target] = value;\n\t\t\t\tdata[LENGTH_INDEX]++;\n\t\t\t\tif(firstTomb >= 0) {\n\t\t\t\t\tdata[TOMBSTONE_INDEX]--;\n\t\t\t\t}\n\t\t\t\treturn;\n\t\t\t} else if(state === TOMBSTONE) {\n\t\t\t\tif(firstTomb < 0) {\n\t\t\t\t\tfirstTomb = idx;\n\t\t\t\t} else if(idx === firstTomb) {\n\t\t\t\t\t// Returning to the first tombstone means every key was checked without finding an empty slot.\n\t\t\t\t\tthis.reuseTombstone(idx, fullHashKey, value);\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t} else if(slots[keysBase + idx] === fullHashKey) {\n\t\t\t\tvalues[idx] = value;\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tidx = (idx + 1) & mask;\n\t\t}\n\t}\n\n\t// Atomic read-modify-write under the table lock: resolves the current value (undefined if the key is\n\t// absent), calls updater, and stores the result - so two threads mutating the same key never lose an\n\t// update the way a separate get() + set() can. updater runs inside the lock and must be synchronous and\n\t// must not touch this same map (the lock is not reentrant).\n\tupdate(key: K, updater: (current: V[number] | undefined) => V[number]): V[number] {\n\t\tlet fullHashKey = get32BitHash(key);\n\n\t\tlock(this.lock);\n\t\ttry {\n\t\t\tlet data = this.pointerMemory.data;\n\t\t\tlet length = data[LENGTH_INDEX];\n\t\t\tlet tombstones = data[TOMBSTONE_INDEX];\n\t\t\tif((length + tombstones + 1) * LOAD_DENOMINATOR > data[CAPACITY_INDEX] * LOAD_NUMERATOR) {\n\t\t\t\tthis.resizeForInsert(key);\n\t\t\t}\n\n\t\t\tthis.ensureTable();\n\t\t\tlet slots = this.cachedSlots!;\n\t\t\tlet values = this.cachedValues! as NumericArrayIO;\n\t\t\tlet capacity = this.cachedCapacity;\n\t\t\tlet mask = capacity - 1;\n\t\t\tlet idx = mix32(fullHashKey) & mask;\n\t\t\tlet firstTomb = -1;\n\t\t\t// eslint-disable-next-line no-constant-condition\n\t\t\twhile(true) {\n\t\t\t\tlet state = slots[idx];\n\t\t\t\tif(state === EMPTY) {\n\t\t\t\t\tlet newValue = updater(undefined);\n\t\t\t\t\tlet target = firstTomb >= 0 ? firstTomb : idx;\n\t\t\t\t\tslots[target] = FULL;\n\t\t\t\t\tslots[capacity + target] = fullHashKey;\n\t\t\t\t\tvalues[target] = newValue;\n\t\t\t\t\tdata[LENGTH_INDEX]++;\n\t\t\t\t\tif(firstTomb >= 0) {\n\t\t\t\t\t\tdata[TOMBSTONE_INDEX]--;\n\t\t\t\t\t}\n\t\t\t\t\treturn newValue;\n\t\t\t\t} else if(state === TOMBSTONE) {\n\t\t\t\t\tif(idx === firstTomb) {\n\t\t\t\t\t\treturn this.reuseTombstone(idx, fullHashKey, updater);\n\t\t\t\t\t}\n\t\t\t\t\tif(firstTomb < 0) {\n\t\t\t\t\t\tfirstTomb = idx;\n\t\t\t\t\t}\n\t\t\t\t} else if(slots[capacity + idx] === fullHashKey) {\n\t\t\t\t\tlet newValue = updater(values[idx]);\n\t\t\t\t\tvalues[idx] = newValue;\n\t\t\t\t\treturn newValue;\n\t\t\t\t}\n\n\t\t\t\tidx = (idx + 1) & mask;\n\t\t\t}\n\t\t} finally {\n\t\t\tunlock(this.lock);\n\t\t}\n\t}\n\n\t// Keep rare capacity handling out of the insertion loops so they stay small enough to inline.\n\tprivate resizeForInsert(key: K) {\n\t\tlet length = this.pointerMemory.data[LENGTH_INDEX];\n\t\tlet capacity = this.pointerMemory.data[CAPACITY_INDEX];\n\t\tif(capacity < this.maxTableCapacity || (length + 1) * LOAD_DENOMINATOR <= capacity * LOAD_NUMERATOR) {\n\t\t\tthis.resize();\n\t\t} else if(length === capacity && this.getNoLock(key) === undefined) {\n\t\t\tthrow new Error(`SharedMap is full at capacity ${capacity}: a larger table would exceed the largest contiguous allocation. `\n\t\t\t\t+ 'Use larger buffers or shard across multiple maps.');\n\t\t}\n\t}\n\n\tprivate reuseTombstone(idx: number, fullHashKey: number, valueOrUpdater: V[number] | ((current: V[number] | undefined) => V[number])): V[number] {\n\t\tlet newValue = typeof valueOrUpdater === 'function' ? valueOrUpdater(undefined) : valueOrUpdater;\n\t\tlet slots = this.cachedSlots!;\n\t\tlet values = this.cachedValues! as NumericArrayIO;\n\t\tslots[idx] = FULL;\n\t\tslots[this.cachedCapacity + idx] = fullHashKey;\n\t\tvalues[idx] = newValue;\n\t\tthis.pointerMemory.data[LENGTH_INDEX]++;\n\t\tthis.pointerMemory.data[TOMBSTONE_INDEX]--;\n\t\treturn newValue;\n\t}\n\n\tget(key: K): V[number] | undefined {\n\t\tlock(this.lock);\n\t\ttry {\n\t\t\treturn this.getNoLock(key);\n\t\t} finally {\n\t\t\tunlock(this.lock);\n\t\t}\n\t}\n\n\tgetAll(keys: Iterable<K>): Array<V[number] | undefined> {\n\t\tlock(this.lock);\n\t\ttry {\n\t\t\tlet values: Array<V[number] | undefined> = [];\n\t\t\tfor(let key of keys) {\n\t\t\t\tvalues.push(this.getNoLock(key));\n\t\t\t}\n\t\t\treturn values;\n\t\t} finally {\n\t\t\tunlock(this.lock);\n\t\t}\n\t}\n\n\tprivate getNoLock(key: K): V[number] | undefined {\n\t\tlet fullHashKey = get32BitHash(key);\n\t\tthis.ensureTable();\n\t\tlet slots = this.cachedSlots!;\n\t\tlet values = this.cachedValues! as NumericArrayIO;\n\t\tlet capacity = this.cachedCapacity;\n\t\tlet mask = capacity - 1;\n\t\tlet keysBase = capacity;\n\t\tlet idx = mix32(fullHashKey) & mask;\n\t\tlet start = idx;\n\t\tdo {\n\t\t\tlet state = slots[idx];\n\t\t\tif(state === EMPTY) {\n\t\t\t\treturn undefined;\n\t\t\t}\n\t\t\tif(state === FULL && slots[keysBase + idx] === fullHashKey) {\n\t\t\t\treturn values[idx];\n\t\t\t}\n\t\t\tidx = (idx + 1) & mask;\n\t\t} while(idx !== start);\n\t\treturn undefined;\n\t}\n\thas(key: K): boolean {\n\t\treturn this.get(key) !== undefined;\n\t}\n\n\tdelete(key: K): boolean {\n\t\tlet fullHashKey = get32BitHash(key);\n\n\t\tlock(this.lock);\n\t\ttry {\n\t\t\tthis.ensureTable();\n\t\t\tlet data = this.pointerMemory.data;\n\t\t\tlet slots = this.cachedSlots!;\n\t\t\tlet capacity = this.cachedCapacity;\n\t\t\tlet mask = capacity - 1;\n\t\t\tlet keysBase = capacity;\n\t\t\tlet idx = mix32(fullHashKey) & mask;\n\t\t\tlet start = idx;\n\t\t\tdo {\n\t\t\t\tlet state = slots[idx];\n\t\t\t\tif(state === EMPTY) {\n\t\t\t\t\treturn false;\n\t\t\t\t}\n\t\t\t\tif(state === FULL && slots[keysBase + idx] === fullHashKey) {\n\t\t\t\t\tslots[idx] = TOMBSTONE;\n\t\t\t\t\tdata[LENGTH_INDEX]--;\n\t\t\t\t\tdata[TOMBSTONE_INDEX]++;\n\t\t\t\t\treturn true;\n\t\t\t\t}\n\t\t\t\tidx = (idx + 1) & mask;\n\t\t\t} while(idx !== start);\n\t\t\treturn false;\n\t\t} finally {\n\t\t\tunlock(this.lock);\n\t\t}\n\t}\n\n\t// Best-effort scan (see class note): not lock-guarded, so must not run concurrently with writes from another thread.\n\t*[Symbol.iterator](): IterableIterator<[number, V[number]]> {\n\t\tthis.ensureTable();\n\t\tlet slots = this.cachedSlots!;\n\t\tlet values = this.cachedValues! as NumericArrayIO;\n\t\tlet capacity = this.cachedCapacity;\n\t\tlet keysBase = capacity;\n\t\tfor(let i = 0; i < capacity; i++) {\n\t\t\tif(slots[i] === FULL) {\n\t\t\t\tyield [slots[keysBase + i], values[i]];\n\t\t\t}\n\t\t}\n\t}\n\n\t// Rehash into a fresh table. Doubles capacity when live entries are crowding the table; when tombstones are the cause,\n\t// a same-size table already reclaims them. Caller must hold the lock.\n\tprivate resize(requestedCapacity?: number) {\n\t\tthis.ensureTable();\n\t\tlet oldTable = this.cachedTableMemory!;\n\t\tlet oldSlots = this.cachedSlots!;\n\t\tlet oldValues = this.cachedValues! as NumericArrayIO;\n\t\tlet oldCapacity = this.cachedCapacity;\n\t\tlet oldKeysBase = oldCapacity;\n\t\tlet length = Atomics.load(this.pointerMemory.data, LENGTH_INDEX);\n\n\t\tlet newCapacity = requestedCapacity ?? oldCapacity;\n\t\tif(requestedCapacity === undefined && (length + 1) * LOAD_DENOMINATOR > oldCapacity * LOAD_NUMERATOR) {\n\t\t\tnewCapacity = oldCapacity * 2;\n\t\t}\n\t\t// A table is one contiguous allocation, so it can never exceed what a single buffer can hand out. Fail here with an\n\t\t// actionable message instead of letting allocUI32 throw a cryptic \"Unable to allocate N numbers\" from deep inside the\n\t\t// heap. Mirrors the same guard in initialize().\n\t\tif(this.tableLength(newCapacity) > this.memory.maxAllocationLength) {\n\t\t\tlet maxEntries = Math.floor(this.memory.maxAllocationLength / (2 + this.cachedValueUnits) * LOAD_NUMERATOR / LOAD_DENOMINATOR);\n\t\t\tthrow new Error(`SharedMap can't grow to capacity ${newCapacity}: its table would exceed the largest contiguous `\n\t\t\t\t+ `allocation (${this.memory.maxAllocationLength} u32s, ~${maxEntries} entries). Use larger buffers or shard across multiple maps.`);\n\t\t}\n\t\tlet newTable = this.memory.allocUI32(this.tableLength(newCapacity));\n\t\tlet newSlots = newTable.data;\n\t\tlet newValues = this.makeValuesView(newTable, newCapacity) as NumericArrayIO;\n\t\tlet mask = newCapacity - 1;\n\t\tlet newKeysBase = newCapacity;\n\n\t\tfor(let i = 0; i < oldCapacity; i++) {\n\t\t\tif(oldSlots[i] !== FULL) {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tlet key = oldSlots[oldKeysBase + i];\n\t\t\tlet value = oldValues[i];\n\t\t\tlet idx = mix32(key) & mask;\n\t\t\t// Fresh table has no tombstones, so probe straight to the first empty slot\n\t\t\twhile(newSlots[idx] !== EMPTY) {\n\t\t\t\tidx = (idx + 1) & mask;\n\t\t\t}\n\t\t\tnewSlots[idx] = FULL;\n\t\t\tnewSlots[newKeysBase + idx] = key;\n\t\t\tnewValues[idx] = value;\n\t\t}\n\n\t\tstoreRawPointer(this.pointerMemory.data, TABLE_POINTER_INDEX, newTable.pointer);\n\t\tAtomics.store(this.pointerMemory.data, CAPACITY_INDEX, newCapacity);\n\t\tAtomics.store(this.pointerMemory.data, TOMBSTONE_INDEX, 0);\n\t\t// Invalidate the cached views so the next access rebuilds against the new table\n\t\tthis.cachedTablePointer = 0;\n\t\tthis.cachedSlots = undefined;\n\t\toldTable.free();\n\t}\n\n\tfree() {\n\t\tthis.ensureTable();\n\t\tthis.cachedTableMemory!.free();\n\t\tif(this.ownsFirstBlock) {\n\t\t\tthis.pointerMemory.free();\n\t\t}\n\t}\n\n\tgetSharedMemory(): SharedMapMemory {\n\t\treturn {\n\t\t\tfirstBlock: this.pointerMemory.getSharedMemory(),\n\t\t};\n\t}\n}\n\ninterface SharedMapConfig<V extends NumericArray> {\n\ttype?: TypedArrayConstructor<V>\n\tcapacity?: number\n}\ninterface SharedMapMemory {\n\tfirstBlock: SharedAllocatedMemory | AllocatedMemory\n}\n\nexport type { SharedMapConfig, SharedMapMemory };\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"shared-pointer-list.js","names":[],"sources":["../src/shared-pointer-list.ts"],"sourcesContent":["import AllocatedMemory from './allocated-memory';\nimport type MemoryHeap from './memory-heap';\nimport type { SharedListMemory } from './shared-list';\nimport SharedList from './shared-list';\nimport { createPointer, loadPointer } from './utils/pointer';\n\nexport default abstract class SharedPointerList<T extends PointerItem> implements Iterable<T> {\n\tprivate memory: MemoryHeap;\n\tprivate list: SharedList;\n\n\tconstructor(heap: MemoryHeap, memory?: SharedListMemory) {\n\t\tthis.memory = heap;\n\n\t\tif(memory) {\n\t\t\tthis.list = new SharedList(heap, memory);\n\t\t} else {\n\t\t\tthis.list = new SharedList(heap);\n\t\t}\n\t}\n\n\tget length() {\n\t\treturn this.list.length;\n\t}\n\n\tinsert(item: T) {\n\t\tthis.list.insert(createPointer(item.memory.bufferPosition, item.memory.bufferByteOffset, this.memory.positionBits));\n\t}\n\tdelete(item: T) {\n\t\tlet deleted = this.list.deleteValue(createPointer(item.memory.bufferPosition, item.memory.bufferByteOffset, this.memory.positionBits));\n\t\t// This wrapper is single-threaded per instance, so reclaim the tombstoned node right away\n\t\tthis.list.compact();\n\t\treturn deleted;\n\t}\n\n\tcompact() {\n\t\tthis.list.compact();\n\t}\n\n\t*[Symbol.iterator]() {\n\t\tlet iterator = this.list[Symbol.iterator]();\n\n\t\tfor(let { data: pointerData } of iterator) {\n\t\t\tlet { bufferPosition, bufferByteOffset } = loadPointer(pointerData, 0, this.memory.positionBits);\n\t\t\tlet allocatedMemory = new AllocatedMemory(this.memory, {\n\t\t\t\tbufferPosition,\n\t\t\t\tbufferByteOffset,\n\t\t\t});\n\t\t\tyield this.createItem(allocatedMemory);\n\t\t}\n\t}\n\tforEach(callback: (item: T) => void) {\n\t\tfor(let value of this) {\n\t\t\tcallback(value);\n\t\t}\n\t}\n\n\tfind(callback: (item: T) => boolean): T | undefined {\n\t\tfor(let value of this) {\n\t\t\tif(callback(value)) {\n\t\t\t\treturn value;\n\t\t\t}\n\t\t}\n\n\t\treturn undefined;\n\t}\n\n\tgetSharedMemory() {\n\t\treturn this.list.getSharedMemory();\n\t}\n\n\tprotected abstract createItem(allocatedMemory: AllocatedMemory): T;\n\n\tfree() {\n\t\tfor(let item of this) {\n\t\t\t// NOTE: Anything that allocates it's own memory (ie: type for Item) needs to call free for that class so we can clear all memory recursively\n\t\t\tif('free' in item && typeof item.free === 'function') {\n\t\t\t\titem.free();\n\t\t\t} else {\n\t\t\t\titem.memory.free();\n\t\t\t}\n\t\t}\n\n\t\tthis.list.free();\n\t}\n}\n\ninterface PointerItem {\n\treadonly memory: AllocatedMemory\n}"],"mappings":";;;;AAMA,IAA8B,IAA9B,MAA8F;CAC7F;CACA;CAEA,YAAY,GAAkB,GAA2B;EAGxD,AAFA,KAAK,SAAS,GAEd,AAGC,KAAK,OAHH,IACU,IAAI,EAAW,GAAM,CAAM,IAE3B,IAAI,EAAW,CAAI;CAEjC;CAEA,IAAI,SAAS;EACZ,OAAO,KAAK,KAAK;CAClB;CAEA,OAAO,GAAS;EACf,KAAK,KAAK,OAAO,EAAc,EAAK,OAAO,gBAAgB,EAAK,OAAO,kBAAkB,KAAK,OAAO,YAAY,CAAC;CACnH;CACA,OAAO,GAAS;EACf,IAAI,IAAU,KAAK,KAAK,YAAY,EAAc,EAAK,OAAO,gBAAgB,EAAK,OAAO,kBAAkB,KAAK,OAAO,YAAY,CAAC;EAGrI,OADA,KAAK,KAAK,QAAQ,GACX;CACR;CAEA,UAAU;EACT,KAAK,KAAK,QAAQ;CACnB;CAEA,EAAE,OAAO,YAAY;EACpB,IAAI,IAAW,KAAK,KAAK,OAAO,SAAS,CAAC;EAE1C,KAAI,IAAI,EAAE,MAAM,OAAiB,GAAU;GAC1C,IAAI,EAAE,mBAAgB,wBAAqB,EAAY,GAAa,GAAG,KAAK,OAAO,YAAY,GAC3F,IAAkB,IAAI,EAAgB,KAAK,QAAQ;IACtD;IACA;GACD,CAAC;GACD,MAAM,KAAK,WAAW,CAAe;EACtC;CACD;CACA,QAAQ,GAA6B;EACpC,KAAI,IAAI,KAAS,MAChB,EAAS,CAAK;CAEhB;CAEA,KAAK,GAA+C;EACnD,KAAI,IAAI,KAAS,MAChB,IAAG,EAAS,CAAK,GAChB,OAAO;CAKV;CAEA,kBAAkB;EACjB,OAAO,KAAK,KAAK,gBAAgB;CAClC;CAIA,OAAO;EACN,KAAI,IAAI,KAAQ,MAEf,AAAG,UAAU,KAAQ,OAAO,EAAK,QAAS,aACzC,EAAK,KAAK,IAEV,EAAK,OAAO,KAAK;EAInB,KAAK,KAAK,KAAK;CAChB;AACD"}
1
+ {"version":3,"file":"shared-pointer-list.js","names":["AllocatedMemory","SharedList","SharedPointerList","heap","memory","createPointer","item","deleted","iterator","pointerData","loadPointer","allocatedMemory","value","callback"],"sources":["../src/shared-pointer-list.ts"],"sourcesContent":["import AllocatedMemory from './allocated-memory';\nimport type MemoryHeap from './memory-heap';\nimport type { SharedListMemory } from './shared-list';\nimport SharedList from './shared-list';\nimport { createPointer, loadPointer } from './utils/pointer';\n\nexport default abstract class SharedPointerList<T extends PointerItem> implements Iterable<T> {\n\tprivate memory: MemoryHeap;\n\tprivate list: SharedList;\n\n\tconstructor(heap: MemoryHeap, memory?: SharedListMemory) {\n\t\tthis.memory = heap;\n\n\t\tif(memory) {\n\t\t\tthis.list = new SharedList(heap, memory);\n\t\t} else {\n\t\t\tthis.list = new SharedList(heap);\n\t\t}\n\t}\n\n\tget length() {\n\t\treturn this.list.length;\n\t}\n\n\tinsert(item: T) {\n\t\tthis.list.insert(createPointer(item.memory.bufferPosition, item.memory.bufferByteOffset, this.memory.positionBits));\n\t}\n\tdelete(item: T) {\n\t\tlet deleted = this.list.deleteValue(createPointer(item.memory.bufferPosition, item.memory.bufferByteOffset, this.memory.positionBits));\n\t\t// This wrapper is single-threaded per instance, so reclaim the tombstoned node right away\n\t\tthis.list.compact();\n\t\treturn deleted;\n\t}\n\n\tcompact() {\n\t\tthis.list.compact();\n\t}\n\n\t*[Symbol.iterator]() {\n\t\tlet iterator = this.list[Symbol.iterator]();\n\n\t\tfor(let { data: pointerData } of iterator) {\n\t\t\tlet { bufferPosition, bufferByteOffset } = loadPointer(pointerData, 0, this.memory.positionBits);\n\t\t\tlet allocatedMemory = new AllocatedMemory(this.memory, {\n\t\t\t\tbufferPosition,\n\t\t\t\tbufferByteOffset,\n\t\t\t});\n\t\t\tyield this.createItem(allocatedMemory);\n\t\t}\n\t}\n\tforEach(callback: (item: T) => void) {\n\t\tfor(let value of this) {\n\t\t\tcallback(value);\n\t\t}\n\t}\n\n\tfind(callback: (item: T) => boolean): T | undefined {\n\t\tfor(let value of this) {\n\t\t\tif(callback(value)) {\n\t\t\t\treturn value;\n\t\t\t}\n\t\t}\n\n\t\treturn undefined;\n\t}\n\n\tgetSharedMemory() {\n\t\treturn this.list.getSharedMemory();\n\t}\n\n\tprotected abstract createItem(allocatedMemory: AllocatedMemory): T;\n\n\tfree() {\n\t\tfor(let item of this) {\n\t\t\t// NOTE: Anything that allocates it's own memory (ie: type for Item) needs to call free for that class so we can clear all memory recursively\n\t\t\tif('free' in item && typeof item.free === 'function') {\n\t\t\t\titem.free();\n\t\t\t} else {\n\t\t\t\titem.memory.free();\n\t\t\t}\n\t\t}\n\n\t\tthis.list.free();\n\t}\n}\n\ninterface PointerItem {\n\treadonly memory: AllocatedMemory\n}"],"mappings":";;;;AAMA,IAA8BE,IAA9B,MAA8F;CAC7F;CACA;CAEA,YAAY,GAAkB,GAA2B;EAGxD,AAFA,KAAK,SAASC,GAEd,AAGC,KAAK,OAHHC,IACU,IAAIH,EAAWE,GAAMC,CAAM,IAE3B,IAAIH,EAAWE,CAAI;CAEjC;CAEA,IAAI,SAAS;EACZ,OAAO,KAAK,KAAK;CAClB;CAEA,OAAO,GAAS;EACf,KAAK,KAAK,OAAOE,EAAcC,EAAK,OAAO,gBAAgBA,EAAK,OAAO,kBAAkB,KAAK,OAAO,YAAY,CAAC;CACnH;CACA,OAAO,GAAS;EACf,IAAIC,IAAU,KAAK,KAAK,YAAYF,EAAcC,EAAK,OAAO,gBAAgBA,EAAK,OAAO,kBAAkB,KAAK,OAAO,YAAY,CAAC;EAGrI,OADA,KAAK,KAAK,QAAQ,GACXC;CACR;CAEA,UAAU;EACT,KAAK,KAAK,QAAQ;CACnB;CAEA,EAAE,OAAO,YAAY;EACpB,IAAIC,IAAW,KAAK,KAAK,OAAO,SAAS,CAAC;EAE1C,KAAI,IAAI,EAAE,MAAMC,OAAiBD,GAAU;GAC1C,IAAI,EAAE,mBAAgB,wBAAqBE,EAAYD,GAAa,GAAG,KAAK,OAAO,YAAY,GAC3FE,IAAkB,IAAIX,EAAgB,KAAK,QAAQ;IACtD;IACA;GACD,CAAC;GACD,MAAM,KAAK,WAAWW,CAAe;EACtC;CACD;CACA,QAAQ,GAA6B;EACpC,KAAI,IAAIC,KAAS,MAChB,EAASA,CAAK;CAEhB;CAEA,KAAK,GAA+C;EACnD,KAAI,IAAIA,KAAS,MAChB,IAAGC,EAASD,CAAK,GAChB,OAAOA;CAKV;CAEA,kBAAkB;EACjB,OAAO,KAAK,KAAK,gBAAgB;CAClC;CAIA,OAAO;EACN,KAAI,IAAIN,KAAQ,MAEf,AAAG,UAAUA,KAAQ,OAAOA,EAAK,QAAS,aACzCA,EAAK,KAAK,IAEVA,EAAK,OAAO,KAAK;EAInB,KAAK,KAAK,KAAK;CAChB;AACD"}
@@ -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 { lock, unlock } from './lock/simple-lock';\nimport type MemoryHeap from './memory-heap';\nimport SharedStack from './shared-stack';\nimport { getPointer, loadRawPointer, storeRawPointer } from './utils/pointer';\nimport { getArrayTypeCode, getByteMultipler, makeArrayView, type NumericArray, type NumericArrayIO, type TypedArrayConstructor } from './utils/array-type';\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 NumericArray = Uint32Array> implements Iterable<T> {\n\tstatic readonly ALLOCATE_COUNT = 6;\n\n\t// u32s a pool with this config needs for its firstBlock, including both inlined stacks. Lets an owner (e.g.\n\t// SharedQuadtree) reserve one contiguous slice and hand it back through the config's firstBlock to inline the pool.\n\tstatic getAllocateCount(memory: MemoryHeap, config?: SharedPoolConfig<NumericArray>): number {\n\t\tlet maxChunkSize = config?.maxChunkSize ?? DEFAULT_MAX_CHUNK_SIZE;\n\t\tlet maxLength = config?.maxLength ?? DEFAULT_MAX_LENGTH;\n\t\tlet maxRecycledLength = config?.maxRecycledLength ?? DEFAULT_MAX_RECYCLED_LENGTH;\n\t\tlet maxSegmentLength = SharedStack.getMaxSegmentLength(memory.maxAllocationLength);\n\t\tlet pointerStackAllocateCount = SharedStack.getAllocateCount(maxLength / maxChunkSize, undefined, maxSegmentLength);\n\t\tlet recycleStackAllocateCount = SharedStack.getAllocateCount(maxRecycledLength, undefined, maxSegmentLength);\n\t\treturn SharedPool.ALLOCATE_COUNT + pointerStackAllocateCount + recycleStackAllocateCount;\n\t}\n\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\t// False when firstBlock is a slice of another structure's memory (e.g. inlined in SharedQuadtree); that owner frees it\n\tprivate ownsFirstBlock: boolean;\n\n\t// Chunk views cached by pointer index. An array (dense, small integer keys) resolves faster than an object on the hot\n\t// get/set path and never needs rehashing.\n\tprivate cachedFullDataBlock: Array<T | undefined> = [];\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.ownsFirstBlock ? this.firstBlock.usedMemory : 0;\n\t\tfor(let pointer of this.pointerStack) {\n\t\t\ttotal += new AllocatedMemory(this.memory, getPointer(pointer, this.memory.positionBits)).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 | SharedPoolMemory & SharedPoolConfig<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 SharedQuadtree); 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\n\t\t\t\t? config.firstBlock\n\t\t\t\t: new AllocatedMemory(memory, { length: SharedPool.ALLOCATE_COUNT, ...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 || 'dataLength' in config || 'maxLength' in config || 'maxChunkSize' in config || 'maxRecycledLength' 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.pointerStack = new SharedStack(memory, {\n\t\t\t\t\tfirstBlock: getPointer(loadRawPointer(this.firstBlock.data, POINTER_STACK_INDEX), memory.positionBits),\n\t\t\t\t});\n\t\t\t\tthis.recycleStack = new SharedStack(memory, {\n\t\t\t\t\tfirstBlock: getPointer(loadRawPointer(this.firstBlock.data, RECYCLE_STACK_INDEX), memory.positionBits),\n\t\t\t\t});\n\t\t\t\tthis.ownsFirstBlock = true;\n\t\t\t}\n\t\t} else {\n\t\t\tthis.firstBlock = memory.allocUI32(SharedPool.getAllocateCount(memory, config));\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.initialize(config);\n\t\t\tthis.ownsFirstBlock = true;\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 = getByteMultipler(this.cachedType);\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\t// Lay out the two inlined stacks (their firstBlocks sliced from ours), record their pointers, and seed the first chunk.\n\t// firstBlock and uint16Array must already be set. Used by both the fresh-alloc path and the inline-in-place path.\n\tprivate initialize(config?: SharedPoolConfig<T>) {\n\t\tlet dataLength = config?.dataLength ?? 1;\n\t\tlet maxChunkSize = config?.maxChunkSize ?? DEFAULT_MAX_CHUNK_SIZE;\n\t\tlet maxLength = config?.maxLength ?? DEFAULT_MAX_LENGTH;\n\t\tlet maxRecycledLength = config?.maxRecycledLength ?? DEFAULT_MAX_RECYCLED_LENGTH;\n\n\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// stacks cap their segments at what a buffer can hold, so size the inlined blocks against that same cap.\n\t\tlet maxSegmentLength = SharedStack.getMaxSegmentLength(this.memory.maxAllocationLength);\n\t\tlet pointerStackMaxLength = maxLength / maxChunkSize;\n\t\tlet pointerStackAllocateCount = SharedStack.getAllocateCount(pointerStackMaxLength, undefined, maxSegmentLength);\n\t\tlet recycleStackAllocateCount = SharedStack.getAllocateCount(maxRecycledLength, undefined, maxSegmentLength);\n\n\t\t// Inline both stacks' firstBlocks right after the pool header so the whole spine is one contiguous allocation\n\t\tthis.pointerStack = new SharedStack(this.memory, {\n\t\t\tfirstBlock: this.firstBlock.getSubAllocation(SharedPool.ALLOCATE_COUNT, pointerStackAllocateCount),\n\t\t\ttype: Uint32Array,\n\t\t\tmaxLength: pointerStackMaxLength,\n\t\t});\n\t\tstoreRawPointer(this.firstBlock.data, POINTER_STACK_INDEX, this.pointerStack.pointer);\n\t\tthis.recycleStack = new SharedStack(this.memory, {\n\t\t\tfirstBlock: this.firstBlock.getSubAllocation(SharedPool.ALLOCATE_COUNT + pointerStackAllocateCount, recycleStackAllocateCount),\n\t\t\ttype: Uint32Array,\n\t\t\tmaxLength: maxRecycledLength,\n\t\t});\n\t\tstoreRawPointer(this.firstBlock.data, RECYCLE_STACK_INDEX, this.recycleStack.pointer);\n\n\t\tlet typeCode = getArrayTypeCode(config?.type ?? Uint32Array);\n\t\tthis.type = typeCode;\n\t\tthis.dataLength = dataLength;\n\t\tthis.maxChunkSize = maxChunkSize;\n\n\t\tlet byteMultipler = getByteMultipler(typeCode);\n\t\tlet firstArray = this.memory.allocUI32(maxChunkSize * dataLength * byteMultipler);\n\t\tthis.pointerStack.push(firstArray.pointer);\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): T[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) as NumericArrayIO;\n\t\treturn dataBlock[(index % this.cachedMaxChunkSize) * dataLength + dataIndex];\n\t}\n\tset(index: number, dataIndex: number, value: T[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) as NumericArrayIO;\n\t\tdataBlock[(index % this.cachedMaxChunkSize) * dataLength + dataIndex] = value;\n\t}\n\n\t// Resolve the chunk view holding an index once. Callers touching several fields of the same record read/write straight\n\t// into this view at blockOffset(index) + fieldIndex, avoiding a repeated chunk lookup per field.\n\tblockFor(index: number): T {\n\t\treturn this.getFullDataBlock(index);\n\t}\n\t// Base slot of a record within the view blockFor(index) returns.\n\tblockOffset(index: number): number {\n\t\treturn (index % this.cachedMaxChunkSize) * this.cachedDataLength;\n\t}\n\n\tpush(values: T[number] | Array<T[number]>): number {\n\t\tlet dataLength = this.cachedDataLength;\n\t\tconst isSingleValue = typeof values !== 'object';\n\t\tif(!isSingleValue && 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.length === 0 ? undefined : 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) as NumericArrayIO;\n\t\tlet startIndex = dataLength * (newIndex % this.cachedMaxChunkSize);\n\t\tif(isSingleValue) {\n\t\t\tdataBlock[startIndex] = values;\n\t\t} else {\n\t\t\tfor(let i = 0; i < values.length; i++) {\n\t\t\t\tdataBlock[startIndex + i] = values[i];\n\t\t\t}\n\t\t}\n\n\t\treturn newIndex;\n\t}\n\n\treserveContiguous(count: number): number {\n\t\tif(!Number.isInteger(count) || count < 0) {\n\t\t\tthrow new Error(`Can't reserve ${count} pool entries`);\n\t\t}\n\t\treturn Atomics.add(this.firstBlock.data, LENGTH_INDEX, count);\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\t*entries(): IterableIterator<[number, T]> {\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\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 [i, 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), this.memory.positionBits));\n\n\t\tlet blockLength = this.cachedDataLength * this.cachedMaxChunkSize;\n\t\tlet data = makeArrayView(this.cachedType, array.data.buffer, array.bufferByteOffset, blockLength) as 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, this.memory.positionBits));\n\t\t\tmemory.free();\n\t\t}\n\t\tthis.pointerStack.free();\n\t\tif(this.ownsFirstBlock) {\n\t\t\tthis.firstBlock.free();\n\t\t}\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 NumericArray> {\n\tmaxChunkSize?: number\n\tmaxLength?: number\n\tmaxRecycledLength?: number\n\ttype?: TypedArrayConstructor<T>\n\tdataLength?: number\n}\ninterface SharedPoolMemory {\n\tfirstBlock: SharedAllocatedMemory | AllocatedMemory\n}\n\nexport type { SharedPoolConfig, SharedPoolMemory };\n"],"mappings":"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"}
1
+ {"version":3,"file":"shared-pool.js","names":["AllocatedMemory","SharedStack","LENGTH_INDEX","TYPE_INDEX","MAX_CHUNK_SIZE_INDEX","GROW_LOCK_INDEX","POINTER_STACK_INDEX","RECYCLE_STACK_INDEX","DEFAULT_MAX_CHUNK_SIZE","DEFAULT_MAX_LENGTH","DEFAULT_MAX_RECYCLED_LENGTH","SharedPool","maxChunkSize","config","maxLength","maxRecycledLength","maxSegmentLength","memory","pointerStackAllocateCount","recycleStackAllocateCount","value","total","pointer","getPointer","loadRawPointer","getByteMultipler","dataLength","pointerStackMaxLength","storeRawPointer","typeCode","getArrayTypeCode","byteMultipler","firstArray","dataBlock","index","dataIndex","isSingleValue","values","newIndex","startIndex","i","count","recycledValues","length","dataBlockIndex","newDataBlockIndex","pointerIndex","cachedDataBlock","newArray","array","blockLength","data","makeArrayView","start","rawData"],"sources":["../src/shared-pool.ts"],"sourcesContent":["import type { SharedAllocatedMemory } from './allocated-memory';\nimport AllocatedMemory from './allocated-memory';\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';\nimport { getArrayTypeCode, getByteMultipler, makeArrayView, type NumericArray, type NumericArrayIO, type TypedArrayConstructor } from './utils/array-type';\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 NumericArray = Uint32Array> implements Iterable<T> {\n\tstatic readonly ALLOCATE_COUNT = 6;\n\n\t// u32s a pool with this config needs for its firstBlock, including both inlined stacks. Lets an owner (e.g.\n\t// SharedQuadtree) reserve one contiguous slice and hand it back through the config's firstBlock to inline the pool.\n\tstatic getAllocateCount(memory: MemoryHeap, config?: SharedPoolConfig<NumericArray>): number {\n\t\tlet maxChunkSize = config?.maxChunkSize ?? DEFAULT_MAX_CHUNK_SIZE;\n\t\tlet maxLength = config?.maxLength ?? DEFAULT_MAX_LENGTH;\n\t\tlet maxRecycledLength = config?.maxRecycledLength ?? DEFAULT_MAX_RECYCLED_LENGTH;\n\t\tlet maxSegmentLength = SharedStack.getMaxSegmentLength(memory.maxAllocationLength);\n\t\tlet pointerStackAllocateCount = SharedStack.getAllocateCount(maxLength / maxChunkSize, undefined, maxSegmentLength);\n\t\tlet recycleStackAllocateCount = SharedStack.getAllocateCount(maxRecycledLength, undefined, maxSegmentLength);\n\t\treturn SharedPool.ALLOCATE_COUNT + pointerStackAllocateCount + recycleStackAllocateCount;\n\t}\n\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\t// False when firstBlock is a slice of another structure's memory (e.g. inlined in SharedQuadtree); that owner frees it\n\tprivate ownsFirstBlock: boolean;\n\n\t// Chunk views cached by pointer index. An array (dense, small integer keys) resolves faster than an object on the hot\n\t// get/set path and never needs rehashing.\n\tprivate cachedFullDataBlock: Array<T | undefined> = [];\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.ownsFirstBlock ? this.firstBlock.usedMemory : 0;\n\t\tfor(let pointer of this.pointerStack) {\n\t\t\ttotal += new AllocatedMemory(this.memory, getPointer(pointer, this.memory.positionBits)).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 | SharedPoolMemory & SharedPoolConfig<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 SharedQuadtree); 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\n\t\t\t\t? config.firstBlock\n\t\t\t\t: new AllocatedMemory(memory, { length: SharedPool.ALLOCATE_COUNT, ...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 || 'dataLength' in config || 'maxLength' in config || 'maxChunkSize' in config || 'maxRecycledLength' 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.pointerStack = new SharedStack(memory, {\n\t\t\t\t\tfirstBlock: getPointer(loadRawPointer(this.firstBlock.data, POINTER_STACK_INDEX), memory.positionBits),\n\t\t\t\t});\n\t\t\t\tthis.recycleStack = new SharedStack(memory, {\n\t\t\t\t\tfirstBlock: getPointer(loadRawPointer(this.firstBlock.data, RECYCLE_STACK_INDEX), memory.positionBits),\n\t\t\t\t});\n\t\t\t\tthis.ownsFirstBlock = true;\n\t\t\t}\n\t\t} else {\n\t\t\tthis.firstBlock = memory.allocUI32(SharedPool.getAllocateCount(memory, config));\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.initialize(config);\n\t\t\tthis.ownsFirstBlock = true;\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 = getByteMultipler(this.cachedType);\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\t// Lay out the two inlined stacks (their firstBlocks sliced from ours), record their pointers, and seed the first chunk.\n\t// firstBlock and uint16Array must already be set. Used by both the fresh-alloc path and the inline-in-place path.\n\tprivate initialize(config?: SharedPoolConfig<T>) {\n\t\tlet dataLength = config?.dataLength ?? 1;\n\t\tlet maxChunkSize = config?.maxChunkSize ?? DEFAULT_MAX_CHUNK_SIZE;\n\t\tlet maxLength = config?.maxLength ?? DEFAULT_MAX_LENGTH;\n\t\tlet maxRecycledLength = config?.maxRecycledLength ?? DEFAULT_MAX_RECYCLED_LENGTH;\n\n\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// stacks cap their segments at what a buffer can hold, so size the inlined blocks against that same cap.\n\t\tlet maxSegmentLength = SharedStack.getMaxSegmentLength(this.memory.maxAllocationLength);\n\t\tlet pointerStackMaxLength = maxLength / maxChunkSize;\n\t\tlet pointerStackAllocateCount = SharedStack.getAllocateCount(pointerStackMaxLength, undefined, maxSegmentLength);\n\t\tlet recycleStackAllocateCount = SharedStack.getAllocateCount(maxRecycledLength, undefined, maxSegmentLength);\n\n\t\t// Inline both stacks' firstBlocks right after the pool header so the whole spine is one contiguous allocation\n\t\tthis.pointerStack = new SharedStack(this.memory, {\n\t\t\tfirstBlock: this.firstBlock.getSubAllocation(SharedPool.ALLOCATE_COUNT, pointerStackAllocateCount),\n\t\t\ttype: Uint32Array,\n\t\t\tmaxLength: pointerStackMaxLength,\n\t\t});\n\t\tstoreRawPointer(this.firstBlock.data, POINTER_STACK_INDEX, this.pointerStack.pointer);\n\t\tthis.recycleStack = new SharedStack(this.memory, {\n\t\t\tfirstBlock: this.firstBlock.getSubAllocation(SharedPool.ALLOCATE_COUNT + pointerStackAllocateCount, recycleStackAllocateCount),\n\t\t\ttype: Uint32Array,\n\t\t\tmaxLength: maxRecycledLength,\n\t\t});\n\t\tstoreRawPointer(this.firstBlock.data, RECYCLE_STACK_INDEX, this.recycleStack.pointer);\n\n\t\tlet typeCode = getArrayTypeCode(config?.type ?? Uint32Array);\n\t\tthis.type = typeCode;\n\t\tthis.dataLength = dataLength;\n\t\tthis.maxChunkSize = maxChunkSize;\n\n\t\tlet byteMultipler = getByteMultipler(typeCode);\n\t\tlet firstArray = this.memory.allocUI32(maxChunkSize * dataLength * byteMultipler);\n\t\tthis.pointerStack.push(firstArray.pointer);\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): T[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) as NumericArrayIO;\n\t\treturn dataBlock[(index % this.cachedMaxChunkSize) * dataLength + dataIndex];\n\t}\n\tset(index: number, dataIndex: number, value: T[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) as NumericArrayIO;\n\t\tdataBlock[(index % this.cachedMaxChunkSize) * dataLength + dataIndex] = value;\n\t}\n\n\t// Resolve the chunk view holding an index once. Callers touching several fields of the same record read/write straight\n\t// into this view at blockOffset(index) + fieldIndex, avoiding a repeated chunk lookup per field.\n\tblockFor(index: number): T {\n\t\treturn this.getFullDataBlock(index);\n\t}\n\t// Base slot of a record within the view blockFor(index) returns.\n\tblockOffset(index: number): number {\n\t\treturn (index % this.cachedMaxChunkSize) * this.cachedDataLength;\n\t}\n\n\tpush(values: T[number] | Array<T[number]>): number {\n\t\tlet dataLength = this.cachedDataLength;\n\t\tconst isSingleValue = typeof values !== 'object';\n\t\tif(!isSingleValue && 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.length === 0 ? undefined : 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) as NumericArrayIO;\n\t\tlet startIndex = dataLength * (newIndex % this.cachedMaxChunkSize);\n\t\tif(isSingleValue) {\n\t\t\tdataBlock[startIndex] = values;\n\t\t} else {\n\t\t\tfor(let i = 0; i < values.length; i++) {\n\t\t\t\tdataBlock[startIndex + i] = values[i];\n\t\t\t}\n\t\t}\n\n\t\treturn newIndex;\n\t}\n\n\treserveContiguous(count: number): number {\n\t\tif(!Number.isInteger(count) || count < 0) {\n\t\t\tthrow new Error(`Can't reserve ${count} pool entries`);\n\t\t}\n\t\treturn Atomics.add(this.firstBlock.data, LENGTH_INDEX, count);\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\t*entries(): IterableIterator<[number, T]> {\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\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 [i, 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), this.memory.positionBits));\n\n\t\tlet blockLength = this.cachedDataLength * this.cachedMaxChunkSize;\n\t\tlet data = makeArrayView(this.cachedType, array.data.buffer, array.bufferByteOffset, blockLength) as 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, this.memory.positionBits));\n\t\t\tmemory.free();\n\t\t}\n\t\tthis.pointerStack.free();\n\t\tif(this.ownsFirstBlock) {\n\t\t\tthis.firstBlock.free();\n\t\t}\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 NumericArray> {\n\tmaxChunkSize?: number\n\tmaxLength?: number\n\tmaxRecycledLength?: number\n\ttype?: TypedArrayConstructor<T>\n\tdataLength?: number\n}\ninterface SharedPoolMemory {\n\tfirstBlock: SharedAllocatedMemory | AllocatedMemory\n}\n\nexport type { SharedPoolConfig, SharedPoolMemory };\n"],"mappings":"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"}