@daneren2005/shared-memory-objects 1.5.2 → 1.6.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/allocated-memory.js.map +1 -1
- package/dist/cached-item-list.js.map +1 -1
- package/dist/constant-string.js.map +1 -1
- package/dist/local-pool.js.map +1 -1
- package/dist/lock/read-write-lock.js.map +1 -1
- package/dist/lock/simple-lock.js.map +1 -1
- package/dist/memory-buffer.js.map +1 -1
- package/dist/memory-heap.js.map +1 -1
- package/dist/serialize-object.js.map +1 -1
- package/dist/shared-list.js.map +1 -1
- package/dist/shared-map.d.ts +3 -0
- package/dist/shared-map.js +42 -24
- package/dist/shared-map.js.map +1 -1
- package/dist/shared-pointer-list.js.map +1 -1
- package/dist/shared-pool.js.map +1 -1
- package/dist/shared-stack.js.map +1 -1
- package/dist/shared-string.js.map +1 -1
- package/dist/shared-vector.js.map +1 -1
- package/dist/spatial/shared-quadtree.js.map +1 -1
- package/dist/spatial/shared-spatial-grid.js.map +1 -1
- package/dist/spatial/shared-spatial-map.js.map +1 -1
- package/dist/spatial/spatial-entity.js.map +1 -1
- package/dist/spatial/spatial-neighbors.js.map +1 -1
- package/dist/utils/16-from-32-array.d.ts +1 -4
- package/dist/utils/16-from-32-array.js +2 -18
- package/dist/utils/16-from-32-float.d.ts +4 -0
- package/dist/utils/16-from-32-float.js +20 -0
- package/dist/utils/16-from-32-float.js.map +1 -0
- package/dist/utils/16-from-32-int.d.ts +4 -0
- package/dist/utils/16-from-32-int.js +18 -0
- package/dist/utils/16-from-32-int.js.map +1 -0
- package/dist/utils/16-from-32-uint.d.ts +4 -0
- package/dist/utils/16-from-32-uint.js +18 -0
- package/dist/utils/16-from-32-uint.js.map +1 -0
- package/dist/utils/16-from-64-array.d.ts +1 -2
- package/dist/utils/16-from-64-array.js +2 -17
- package/dist/utils/16-from-64-float.d.ts +4 -0
- package/dist/utils/16-from-64-float.js +30 -0
- package/dist/utils/16-from-64-float.js.map +1 -0
- package/dist/utils/16-from-64-int.d.ts +4 -0
- package/dist/utils/16-from-64-int.js +23 -0
- package/dist/utils/16-from-64-int.js.map +1 -0
- package/dist/utils/16-from-64-uint.d.ts +4 -0
- package/dist/utils/16-from-64-uint.js +23 -0
- package/dist/utils/16-from-64-uint.js.map +1 -0
- package/dist/utils/32-from-64-float.d.ts +4 -0
- package/dist/utils/32-from-64-float.js +20 -0
- package/dist/utils/32-from-64-float.js.map +1 -0
- package/dist/utils/32-from-64-int.d.ts +4 -0
- package/dist/utils/32-from-64-int.js +18 -0
- package/dist/utils/32-from-64-int.js.map +1 -0
- package/dist/utils/32-from-64-uint.d.ts +4 -0
- package/dist/utils/32-from-64-uint.js +18 -0
- package/dist/utils/32-from-64-uint.js.map +1 -0
- package/dist/utils/array-type.js.map +1 -1
- package/dist/utils/atomic-math.js.map +1 -1
- package/dist/utils/float32-atomics.js.map +1 -1
- package/dist/utils/float64-atomics.js.map +1 -1
- package/dist/utils/hash.js.map +1 -1
- package/dist/utils/pointer.js.map +1 -1
- package/dist/utils/typedarray.js.map +1 -1
- package/package.json +10 -10
- package/dist/utils/16-from-32-array.js.map +0 -1
- package/dist/utils/16-from-64-array.js.map +0 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"allocated-memory.js","names":[],"sources":["../src/allocated-memory.ts"],"sourcesContent":["import type { TypedArray } from './interfaces/typed-array';\nimport type { TypedArrayConstructor } from './interfaces/typed-array-constructor';\nimport type MemoryBuffer from './memory-buffer';\nimport type MemoryHeap from './memory-heap';\nimport { createPointer } from './utils/pointer';\n\nexport default class AllocatedMemory {\n\tprivate readonly memory: MemoryHeap;\n\n\treadonly bufferPosition: number;\n\tget bufferByteOffset(): number {\n\t\treturn this.data.byteOffset;\n\t}\n\tget pointer(): number {\n\t\treturn createPointer(this.bufferPosition, this.bufferByteOffset, this.memory.positionBits);\n\t}\n\tprivate buffer: MemoryBuffer;\n\tdata: Uint32Array;\n\n\t// Bytes this allocation occupies in its buffer (aligned data size, excluding the block header)\n\tget usedMemory(): number {\n\t\treturn this.buffer.bytesFor(this.data.byteOffset) ?? 0;\n\t}\n\n\tconstructor(memory: MemoryHeap, config: AllocatedMemoryConfig | SharedAllocatedMemory | AllocatedMemoryPointer) {\n\t\tthis.memory = memory;\n\n\t\tif('buffer' in config) {\n\t\t\tthis.data = config.data;\n\t\t\tthis.buffer = config.buffer;\n\t\t\tthis.bufferPosition = this.memory.buffers.indexOf(config.buffer);\n\t\t} else {\n\t\t\tthis.bufferPosition = config.bufferPosition;\n\t\t\tthis.buffer = memory.buffers[config.bufferPosition];\n\n\t\t\tconst length = 'length' in config\n\t\t\t\t? config.length\n\t\t\t\t: this.buffer.allocationLengthAt(config.bufferByteOffset);\n\t\t\tthis.data = new Uint32Array(this.buffer.buf, config.bufferByteOffset, length);\n\t\t}\n\t}\n\n\tgetArray<T extends TypedArray>(type: TypedArrayConstructor<T>, offset: number, length: number): T {\n\t\tif(import.meta.env.MODE === 'development' || import.meta.env.MODE === 'test') {\n\t\t\tif((offset + length) * type.BYTES_PER_ELEMENT > this.data.byteLength) {\n\t\t\t\tconst message = 'Trying to grab more memory from AllocatedMemory.getArray then we have: '\n\t\t\t\t\t+ `${offset * type.BYTES_PER_ELEMENT} + ${length * type.BYTES_PER_ELEMENT} > ${this.data.byteLength}`;\n\t\t\t\tif(import.meta.env.MODE === 'test') {\n\t\t\t\t\tthrow new Error(message);\n\t\t\t\t} else {\n\t\t\t\t\tconsole.warn(message);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\treturn new type(this.data.buffer, this.data.byteOffset + offset * type.BYTES_PER_ELEMENT, length);\n\t}\n\t// A child AllocatedMemory over a slice of this block, sharing the same buffer. Built through the {data, buffer} path\n\t// (same as a fresh allocUI32) so callers stay on the cheap, monomorphic construction branch instead of re-resolving a\n\t// pointer. Used to inline sub-structures (e.g. SharedPool's stacks) inside one contiguous allocation.\n\tgetSubAllocation(offset: number, length: number): AllocatedMemory {\n\t\treturn new AllocatedMemory(this.memory, {\n\t\t\tdata: new Uint32Array(this.data.buffer, this.bufferByteOffset + offset * Uint32Array.BYTES_PER_ELEMENT, length),\n\t\t\tbuffer: this.buffer,\n\t\t});\n\t}\n\n\tgetArrayMemory(offset: number, length: number): SharedAllocatedMemory {\n\t\tif(import.meta.env.MODE === 'development') {\n\t\t\tif(offset + length > this.data.length) {\n\t\t\t\tconsole.warn(`Trying to grab more memory from AllocatedMemory.getArrayMemory then we have: ${offset} + ${length} > ${this.data.length}`);\n\t\t\t}\n\t\t}\n\n\t\treturn {\n\t\t\tbufferPosition: this.bufferPosition,\n\t\t\tbufferByteOffset: this.bufferByteOffset + offset * this.data.BYTES_PER_ELEMENT,\n\t\t\tlength,\n\t\t};\n\t}\n\n\tfree() {\n\t\t// NOTE: From worker thread you can't pass the array, you have to pass an explicit address to free\n\t\tthis.buffer.free(this.data.byteOffset);\n\t}\n\n\tgetSharedMemory(): SharedAllocatedMemory {\n\t\treturn {\n\t\t\tbufferPosition: this.bufferPosition,\n\t\t\tbufferByteOffset: this.bufferByteOffset,\n\t\t\tlength: this.data.length,\n\t\t};\n\t}\n}\n\ninterface AllocatedMemoryConfig {\n\tdata: Uint32Array\n\tbuffer: MemoryBuffer\n}\n\ninterface SharedAllocatedMemory {\n\tbufferPosition: number\n\tbufferByteOffset: number\n\tlength?: number\n}\ninterface AllocatedMemoryPointer {\n\tbufferPosition: number\n\tbufferByteOffset: number\n}\nexport type { AllocatedMemoryPointer, SharedAllocatedMemory };"],"mappings":";;AAMA,
|
|
1
|
+
{"version":3,"file":"allocated-memory.js","names":["AllocatedMemory","createPointer","memory","config","length","type","offset"],"sources":["../src/allocated-memory.ts"],"sourcesContent":["import type { TypedArray } from './interfaces/typed-array';\nimport type { TypedArrayConstructor } from './interfaces/typed-array-constructor';\nimport type MemoryBuffer from './memory-buffer';\nimport type MemoryHeap from './memory-heap';\nimport { createPointer } from './utils/pointer';\n\nexport default class AllocatedMemory {\n\tprivate readonly memory: MemoryHeap;\n\n\treadonly bufferPosition: number;\n\tget bufferByteOffset(): number {\n\t\treturn this.data.byteOffset;\n\t}\n\tget pointer(): number {\n\t\treturn createPointer(this.bufferPosition, this.bufferByteOffset, this.memory.positionBits);\n\t}\n\tprivate buffer: MemoryBuffer;\n\tdata: Uint32Array;\n\n\t// Bytes this allocation occupies in its buffer (aligned data size, excluding the block header)\n\tget usedMemory(): number {\n\t\treturn this.buffer.bytesFor(this.data.byteOffset) ?? 0;\n\t}\n\n\tconstructor(memory: MemoryHeap, config: AllocatedMemoryConfig | SharedAllocatedMemory | AllocatedMemoryPointer) {\n\t\tthis.memory = memory;\n\n\t\tif('buffer' in config) {\n\t\t\tthis.data = config.data;\n\t\t\tthis.buffer = config.buffer;\n\t\t\tthis.bufferPosition = this.memory.buffers.indexOf(config.buffer);\n\t\t} else {\n\t\t\tthis.bufferPosition = config.bufferPosition;\n\t\t\tthis.buffer = memory.buffers[config.bufferPosition];\n\n\t\t\tconst length = 'length' in config\n\t\t\t\t? config.length\n\t\t\t\t: this.buffer.allocationLengthAt(config.bufferByteOffset);\n\t\t\tthis.data = new Uint32Array(this.buffer.buf, config.bufferByteOffset, length);\n\t\t}\n\t}\n\n\tgetArray<T extends TypedArray>(type: TypedArrayConstructor<T>, offset: number, length: number): T {\n\t\tif(import.meta.env.MODE === 'development' || import.meta.env.MODE === 'test') {\n\t\t\tif((offset + length) * type.BYTES_PER_ELEMENT > this.data.byteLength) {\n\t\t\t\tconst message = 'Trying to grab more memory from AllocatedMemory.getArray then we have: '\n\t\t\t\t\t+ `${offset * type.BYTES_PER_ELEMENT} + ${length * type.BYTES_PER_ELEMENT} > ${this.data.byteLength}`;\n\t\t\t\tif(import.meta.env.MODE === 'test') {\n\t\t\t\t\tthrow new Error(message);\n\t\t\t\t} else {\n\t\t\t\t\tconsole.warn(message);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\treturn new type(this.data.buffer, this.data.byteOffset + offset * type.BYTES_PER_ELEMENT, length);\n\t}\n\t// A child AllocatedMemory over a slice of this block, sharing the same buffer. Built through the {data, buffer} path\n\t// (same as a fresh allocUI32) so callers stay on the cheap, monomorphic construction branch instead of re-resolving a\n\t// pointer. Used to inline sub-structures (e.g. SharedPool's stacks) inside one contiguous allocation.\n\tgetSubAllocation(offset: number, length: number): AllocatedMemory {\n\t\treturn new AllocatedMemory(this.memory, {\n\t\t\tdata: new Uint32Array(this.data.buffer, this.bufferByteOffset + offset * Uint32Array.BYTES_PER_ELEMENT, length),\n\t\t\tbuffer: this.buffer,\n\t\t});\n\t}\n\n\tgetArrayMemory(offset: number, length: number): SharedAllocatedMemory {\n\t\tif(import.meta.env.MODE === 'development') {\n\t\t\tif(offset + length > this.data.length) {\n\t\t\t\tconsole.warn(`Trying to grab more memory from AllocatedMemory.getArrayMemory then we have: ${offset} + ${length} > ${this.data.length}`);\n\t\t\t}\n\t\t}\n\n\t\treturn {\n\t\t\tbufferPosition: this.bufferPosition,\n\t\t\tbufferByteOffset: this.bufferByteOffset + offset * this.data.BYTES_PER_ELEMENT,\n\t\t\tlength,\n\t\t};\n\t}\n\n\tfree() {\n\t\t// NOTE: From worker thread you can't pass the array, you have to pass an explicit address to free\n\t\tthis.buffer.free(this.data.byteOffset);\n\t}\n\n\tgetSharedMemory(): SharedAllocatedMemory {\n\t\treturn {\n\t\t\tbufferPosition: this.bufferPosition,\n\t\t\tbufferByteOffset: this.bufferByteOffset,\n\t\t\tlength: this.data.length,\n\t\t};\n\t}\n}\n\ninterface AllocatedMemoryConfig {\n\tdata: Uint32Array\n\tbuffer: MemoryBuffer\n}\n\ninterface SharedAllocatedMemory {\n\tbufferPosition: number\n\tbufferByteOffset: number\n\tlength?: number\n}\ninterface AllocatedMemoryPointer {\n\tbufferPosition: number\n\tbufferByteOffset: number\n}\nexport type { AllocatedMemoryPointer, SharedAllocatedMemory };"],"mappings":";;AAMA,IAAqBA,IAArB,MAAqBA,EAAgB;CACpC;CAEA;CACA,IAAI,mBAA2B;EAC9B,OAAO,KAAK,KAAK;CAClB;CACA,IAAI,UAAkB;EACrB,OAAOC,EAAc,KAAK,gBAAgB,KAAK,kBAAkB,KAAK,OAAO,YAAY;CAC1F;CACA;CACA;CAGA,IAAI,aAAqB;EACxB,OAAO,KAAK,OAAO,SAAS,KAAK,KAAK,UAAU,KAAK;CACtD;CAEA,YAAY,GAAoB,GAAgF;EAG/G,IAFA,KAAK,SAASC,GAEX,YAAYC,GAGd,AAFA,KAAK,OAAOA,EAAO,MACnB,KAAK,SAASA,EAAO,QACrB,KAAK,iBAAiB,KAAK,OAAO,QAAQ,QAAQA,EAAO,MAAM;OACzD;GAEN,AADA,KAAK,iBAAiBA,EAAO,gBAC7B,KAAK,SAASD,EAAO,QAAQC,EAAO;GAEpC,IAAMC,IAAS,YAAYD,IACxBA,EAAO,SACP,KAAK,OAAO,mBAAmBA,EAAO,gBAAgB;GACzD,KAAK,OAAO,IAAI,YAAY,KAAK,OAAO,KAAKA,EAAO,kBAAkBC,CAAM;EAC7E;CACD;CAEA,SAA+B,GAAgC,GAAgB,GAAmB;EAajG,OAAO,IAAIC,EAAK,KAAK,KAAK,QAAQ,KAAK,KAAK,aAAaC,IAASD,EAAK,mBAAmBD,CAAM;CACjG;CAIA,iBAAiB,GAAgB,GAAiC;EACjE,OAAO,IAAIJ,EAAgB,KAAK,QAAQ;GACvC,MAAM,IAAI,YAAY,KAAK,KAAK,QAAQ,KAAK,mBAAmBM,IAAS,YAAY,mBAAmBF,CAAM;GAC9G,QAAQ,KAAK;EACd,CAAC;CACF;CAEA,eAAe,GAAgB,GAAuC;EAOrE,OAAO;GACN,gBAAgB,KAAK;GACrB,kBAAkB,KAAK,mBAAmBE,IAAS,KAAK,KAAK;GAC7D;EACD;CACD;CAEA,OAAO;EAEN,KAAK,OAAO,KAAK,KAAK,KAAK,UAAU;CACtC;CAEA,kBAAyC;EACxC,OAAO;GACN,gBAAgB,KAAK;GACrB,kBAAkB,KAAK;GACvB,QAAQ,KAAK,KAAK;EACnB;CACD;AACD"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cached-item-list.js","names":[],"sources":["../src/cached-item-list.ts"],"sourcesContent":["import type { SharedAllocatedMemory } from './allocated-memory';\nimport type MemoryHeap from './memory-heap';\nimport SharedList, { type SharedListMemory } from './shared-list';\n\nexport default abstract class CachedItemList<T extends Item> implements Iterable<{ item: T, deleteCurrent: () => void }> {\n\tstatic readonly ALLOCATE_COUNT = SharedList.ALLOCATE_COUNT;\n\n\tprotected heap: MemoryHeap;\n\tprotected list: SharedList;\n\tprotected cache: Map<number, T> = new Map();\n\n\tconstructor(heap: MemoryHeap, config?: CachedListConfig | SharedListMemory) {\n\t\tif(config) {\n\t\t\tthis.list = new SharedList(heap, config);\n\t\t} else {\n\t\t\tthis.list = new SharedList(heap);\n\t\t}\n\t\tthis.heap = heap;\n\n\t\tthis.list.onDelete = (pointerData: Uint32Array) => {\n\t\t\tlet pointer = Atomics.load(pointerData, 0);\n\t\t\tif(pointer) {\n\t\t\t\tlet item = this.cache.get(pointer);\n\t\t\t\tif(!item) {\n\t\t\t\t\titem = this.initItem(pointer);\n\t\t\t\t}\n\n\t\t\t\tif(item) {\n\t\t\t\t\titem.free();\n\t\t\t\t\tthis.cache.delete(pointer);\n\t\t\t\t}\n\t\t\t}\n\t\t};\n\t}\n\n\tget length() {\n\t\treturn this.list.length;\n\t}\n\n\tclear() {\n\t\tthis.list.clear();\n\t\tthis.cache.clear();\n\t}\n\n\tinsert(item: T) {\n\t\tthis.list.insert(item.pointer);\n\t\tthis.cache.set(item.pointer, item);\n\t}\n\tdelete(item: T) {\n\t\tthis.cache.delete(item.pointer);\n\t\tlet deleted = this.list.deleteValue(item.pointer);\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\tgetByPointer(pointer: number): T | undefined {\n\t\tlet item = this.cache.get(pointer);\n\t\tif(!item) {\n\t\t\titem = this.initItem(pointer);\n\t\t\tif(item) {\n\t\t\t\tthis.cache.set(pointer, item);\n\t\t\t}\n\t\t}\n\n\t\treturn item;\n\t}\n\tprotected abstract initItem(pointer: number): T | undefined;\n\n\t*[Symbol.iterator]() {\n\t\tlet iterator = this.list[Symbol.iterator]();\n\n\t\tfor(let { data: pointerData, deleteCurrent } of iterator) {\n\t\t\tlet pointer = Atomics.load(pointerData, 0);\n\t\t\tif(!pointer) {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tlet item = this.cache.get(pointer);\n\t\t\tif(!item) {\n\t\t\t\titem = this.initItem(pointer);\n\t\t\t\tif(item) {\n\t\t\t\t\tthis.cache.set(pointer, item);\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif(item) {\n\t\t\t\tyield {\n\t\t\t\t\titem,\n\t\t\t\t\tdeleteCurrent,\n\t\t\t\t};\n\t\t\t}\n\t\t}\n\t}\n\tforEach(callback: (item: T) => void, filter?: (item: T) => boolean) {\n\t\tfor(let { item } of this) {\n\t\t\tif(!filter || filter(item)) {\n\t\t\t\tcallback(item);\n\t\t\t}\n\t\t}\n\t}\n\n\tfind(callback: (item: T) => boolean): T | undefined {\n\t\tfor(let { item } of this) {\n\t\t\tif(callback(item)) {\n\t\t\t\treturn item;\n\t\t\t}\n\t\t}\n\n\t\treturn undefined;\n\t}\n\tfilter(callback: (entity: T) => boolean): Array<T> {\n\t\tlet items = [];\n\t\tfor(let { item } of this) {\n\t\t\tif(callback(item)) {\n\t\t\t\titems.push(item);\n\t\t\t}\n\t\t}\n\n\t\treturn items;\n\t}\n\tmap<X>(callback: (item: T) => X): Array<X> {\n\t\tconst array: Array<X> = [];\n\t\tfor(let { item } of this) {\n\t\t\tarray.push(callback(item));\n\t\t}\n\n\t\treturn array;\n\t}\n\n\tgetSharedMemory() {\n\t\treturn this.list.getSharedMemory();\n\t}\n\n\tfree() {\n\t\tthis.list.free();\n\t\tthis.cache.clear();\n\t}\n}\n\nexport interface CachedListConfig {\n\tinitWithBlock: SharedAllocatedMemory\n}\n\ninterface Item {\n\tpointer: number\n\tfree: () => void\n}"],"mappings":";;AAIA,
|
|
1
|
+
{"version":3,"file":"cached-item-list.js","names":["SharedList","CachedItemList","config","heap","pointer","pointerData","item","deleted","iterator","filter","callback","items","array"],"sources":["../src/cached-item-list.ts"],"sourcesContent":["import type { SharedAllocatedMemory } from './allocated-memory';\nimport type MemoryHeap from './memory-heap';\nimport SharedList, { type SharedListMemory } from './shared-list';\n\nexport default abstract class CachedItemList<T extends Item> implements Iterable<{ item: T, deleteCurrent: () => void }> {\n\tstatic readonly ALLOCATE_COUNT = SharedList.ALLOCATE_COUNT;\n\n\tprotected heap: MemoryHeap;\n\tprotected list: SharedList;\n\tprotected cache: Map<number, T> = new Map();\n\n\tconstructor(heap: MemoryHeap, config?: CachedListConfig | SharedListMemory) {\n\t\tif(config) {\n\t\t\tthis.list = new SharedList(heap, config);\n\t\t} else {\n\t\t\tthis.list = new SharedList(heap);\n\t\t}\n\t\tthis.heap = heap;\n\n\t\tthis.list.onDelete = (pointerData: Uint32Array) => {\n\t\t\tlet pointer = Atomics.load(pointerData, 0);\n\t\t\tif(pointer) {\n\t\t\t\tlet item = this.cache.get(pointer);\n\t\t\t\tif(!item) {\n\t\t\t\t\titem = this.initItem(pointer);\n\t\t\t\t}\n\n\t\t\t\tif(item) {\n\t\t\t\t\titem.free();\n\t\t\t\t\tthis.cache.delete(pointer);\n\t\t\t\t}\n\t\t\t}\n\t\t};\n\t}\n\n\tget length() {\n\t\treturn this.list.length;\n\t}\n\n\tclear() {\n\t\tthis.list.clear();\n\t\tthis.cache.clear();\n\t}\n\n\tinsert(item: T) {\n\t\tthis.list.insert(item.pointer);\n\t\tthis.cache.set(item.pointer, item);\n\t}\n\tdelete(item: T) {\n\t\tthis.cache.delete(item.pointer);\n\t\tlet deleted = this.list.deleteValue(item.pointer);\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\tgetByPointer(pointer: number): T | undefined {\n\t\tlet item = this.cache.get(pointer);\n\t\tif(!item) {\n\t\t\titem = this.initItem(pointer);\n\t\t\tif(item) {\n\t\t\t\tthis.cache.set(pointer, item);\n\t\t\t}\n\t\t}\n\n\t\treturn item;\n\t}\n\tprotected abstract initItem(pointer: number): T | undefined;\n\n\t*[Symbol.iterator]() {\n\t\tlet iterator = this.list[Symbol.iterator]();\n\n\t\tfor(let { data: pointerData, deleteCurrent } of iterator) {\n\t\t\tlet pointer = Atomics.load(pointerData, 0);\n\t\t\tif(!pointer) {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tlet item = this.cache.get(pointer);\n\t\t\tif(!item) {\n\t\t\t\titem = this.initItem(pointer);\n\t\t\t\tif(item) {\n\t\t\t\t\tthis.cache.set(pointer, item);\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif(item) {\n\t\t\t\tyield {\n\t\t\t\t\titem,\n\t\t\t\t\tdeleteCurrent,\n\t\t\t\t};\n\t\t\t}\n\t\t}\n\t}\n\tforEach(callback: (item: T) => void, filter?: (item: T) => boolean) {\n\t\tfor(let { item } of this) {\n\t\t\tif(!filter || filter(item)) {\n\t\t\t\tcallback(item);\n\t\t\t}\n\t\t}\n\t}\n\n\tfind(callback: (item: T) => boolean): T | undefined {\n\t\tfor(let { item } of this) {\n\t\t\tif(callback(item)) {\n\t\t\t\treturn item;\n\t\t\t}\n\t\t}\n\n\t\treturn undefined;\n\t}\n\tfilter(callback: (entity: T) => boolean): Array<T> {\n\t\tlet items = [];\n\t\tfor(let { item } of this) {\n\t\t\tif(callback(item)) {\n\t\t\t\titems.push(item);\n\t\t\t}\n\t\t}\n\n\t\treturn items;\n\t}\n\tmap<X>(callback: (item: T) => X): Array<X> {\n\t\tconst array: Array<X> = [];\n\t\tfor(let { item } of this) {\n\t\t\tarray.push(callback(item));\n\t\t}\n\n\t\treturn array;\n\t}\n\n\tgetSharedMemory() {\n\t\treturn this.list.getSharedMemory();\n\t}\n\n\tfree() {\n\t\tthis.list.free();\n\t\tthis.cache.clear();\n\t}\n}\n\nexport interface CachedListConfig {\n\tinitWithBlock: SharedAllocatedMemory\n}\n\ninterface Item {\n\tpointer: number\n\tfree: () => void\n}"],"mappings":";;AAIA,IAA8BC,IAA9B,MAAyH;CACxH,OAAgB,iBAAiBD,EAAW;CAE5C;CACA;CACA,wBAAkC,IAAI,IAAI;CAE1C,YAAY,GAAkB,GAA8C;EAQ3E,AAPA,AAGC,KAAK,OAHHE,IACU,IAAIF,EAAWG,GAAMD,CAAM,IAE3B,IAAIF,EAAWG,CAAI,GAEhC,KAAK,OAAOA,GAEZ,KAAK,KAAK,YAAY,MAA6B;GAClD,IAAIC,IAAU,QAAQ,KAAKC,GAAa,CAAC;GACzC,IAAGD,GAAS;IACX,IAAIE,IAAO,KAAK,MAAM,IAAIF,CAAO;IAKjC,AAJA,AACCE,MAAO,KAAK,SAASF,CAAO,GAG1BE,MACFA,EAAK,KAAK,GACV,KAAK,MAAM,OAAOF,CAAO;GAE3B;EACD;CACD;CAEA,IAAI,SAAS;EACZ,OAAO,KAAK,KAAK;CAClB;CAEA,QAAQ;EAEP,AADA,KAAK,KAAK,MAAM,GAChB,KAAK,MAAM,MAAM;CAClB;CAEA,OAAO,GAAS;EAEf,AADA,KAAK,KAAK,OAAOE,EAAK,OAAO,GAC7B,KAAK,MAAM,IAAIA,EAAK,SAASA,CAAI;CAClC;CACA,OAAO,GAAS;EACf,KAAK,MAAM,OAAOA,EAAK,OAAO;EAC9B,IAAIC,IAAU,KAAK,KAAK,YAAYD,EAAK,OAAO;EAGhD,OADA,KAAK,KAAK,QAAQ,GACXC;CACR;CAEA,UAAU;EACT,KAAK,KAAK,QAAQ;CACnB;CAEA,aAAa,GAAgC;EAC5C,IAAID,IAAO,KAAK,MAAM,IAAIF,CAAO;EAQjC,OAPIE,MACH,IAAO,KAAK,SAASF,CAAO,GACzBE,KACF,KAAK,MAAM,IAAIF,GAASE,CAAI,IAIvBA;CACR;CAGA,EAAE,OAAO,YAAY;EACpB,IAAIE,IAAW,KAAK,KAAK,OAAO,SAAS,CAAC;EAE1C,KAAI,IAAI,EAAE,MAAMH,GAAa,sBAAmBG,GAAU;GACzD,IAAIJ,IAAU,QAAQ,KAAKC,GAAa,CAAC;GACzC,IAAG,CAACD,GACH;GAGD,IAAIE,IAAO,KAAK,MAAM,IAAIF,CAAO;GAQjC,AAPIE,MACH,IAAO,KAAK,SAASF,CAAO,GACzBE,KACF,KAAK,MAAM,IAAIF,GAASE,CAAI,IAI3BA,MACF,MAAM;IACL;IACA;GACD;EAEF;CACD;CACA,QAAQ,GAA6B,GAA+B;EACnE,KAAI,IAAI,EAAE,aAAU,MACnB,CAAG,CAACG,KAAUA,EAAOH,CAAI,MACxBI,EAASJ,CAAI;CAGhB;CAEA,KAAK,GAA+C;EACnD,KAAI,IAAI,EAAE,aAAU,MACnB,IAAGI,EAASJ,CAAI,GACf,OAAOA;CAKV;CACA,OAAO,GAA4C;EAClD,IAAIK,IAAQ,CAAC;EACb,KAAI,IAAI,EAAE,aAAU,MACnB,AAAGD,EAASJ,CAAI,KACfK,EAAM,KAAKL,CAAI;EAIjB,OAAOK;CACR;CACA,IAAO,GAAoC;EAC1C,IAAMC,IAAkB,CAAC;EACzB,KAAI,IAAI,EAAE,aAAU,MACnB,EAAM,KAAKF,EAASJ,CAAI,CAAC;EAG1B,OAAOM;CACR;CAEA,kBAAkB;EACjB,OAAO,KAAK,KAAK,gBAAgB;CAClC;CAEA,OAAO;EAEN,AADA,KAAK,KAAK,KAAK,GACf,KAAK,MAAM,MAAM;CAClB;AACD"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"constant-string.js","names":[],"sources":["../src/constant-string.ts"],"sourcesContent":["import AllocatedMemory from './allocated-memory';\nimport type { SharedAllocatedMemory } from './allocated-memory';\nimport type MemoryHeap from './memory-heap';\nimport { getPointer, loadRawPointer, storeRawPointer } from './utils/pointer';\n\nenum CHAR_TYPE {\n\tUNDEFINED,\n\tASCII,\n\tUTF16,\n}\nconst TYPED_ARRAY_MAP = {\n\t[CHAR_TYPE.ASCII]: Uint8Array,\n\t[CHAR_TYPE.UTF16]: Uint16Array,\n};\n\nconst POINTER_INDEX = 0;\nconst LENGTH_INDEX = 1;\nconst TYPE_INDEX = 2;\n\n// An immutable SharedString: the value is written once at construction and never changes\nexport default class ConstantString {\n\tstatic readonly ALLOCATE_COUNT = 3;\n\n\tprivate memory: MemoryHeap;\n\tprivate allocatedMemory: AllocatedMemory;\n\n\treadonly value: string;\n\n\tconstructor(memory: MemoryHeap, value: string | SharedAllocatedMemory) {\n\t\tthis.memory = memory;\n\n\t\tif(typeof value === 'string') {\n\t\t\tthis.allocatedMemory = this.memory.allocUI32(ConstantString.ALLOCATE_COUNT);\n\t\t\tthis.writeString(value);\n\t\t\tthis.value = value;\n\t\t} else {\n\t\t\t// A read-only view over a header another thread already wrote.\n\t\t\tthis.allocatedMemory = new AllocatedMemory(memory, value);\n\t\t\tthis.value = this.readString();\n\t\t}\n\t}\n\n\tprivate writeString(value: string) {\n\t\tconst stringMemory = this.createString(value);\n\n\t\tstoreRawPointer(this.allocatedMemory.data, POINTER_INDEX, stringMemory.pointer);\n\t\tAtomics.store(this.allocatedMemory.data, LENGTH_INDEX, value.length);\n\t\tAtomics.store(this.allocatedMemory.data, TYPE_INDEX, stringMemory.charType);\n\t}\n\n\tprivate readString(): string {\n\t\tlet pointer = loadRawPointer(this.allocatedMemory.data, POINTER_INDEX);\n\t\tif(pointer === 0) {\n\t\t\treturn '';\n\t\t}\n\t\tlet { bufferPosition, bufferByteOffset } = getPointer(pointer, this.memory.positionBits);\n\n\t\t// No lock: the value never changes after it is written once, so the memory it points at is never freed or\n\t\t// rewritten underneath us.\n\t\tlet charType = Atomics.load(this.allocatedMemory.data, TYPE_INDEX);\n\t\t// @ts-expect-error\n\t\tlet typedArray = TYPED_ARRAY_MAP[charType];\n\t\tlet bufferLength = Atomics.load(this.allocatedMemory.data, LENGTH_INDEX);\n\n\t\tlet data = new typedArray(this.memory.buffers[bufferPosition].buf, bufferByteOffset, bufferLength);\n\t\treturn fromCharCodes(data);\n\t}\n\tprivate createString(value: string) {\n\t\tif(value === '') {\n\t\t\treturn {\n\t\t\t\tpointer: 0,\n\t\t\t\tcharType: CHAR_TYPE.ASCII,\n\t\t\t};\n\t\t}\n\n\t\t// NOTE: Scan with a loop instead of Math.max(...charCodes) which blows the stack for large strings\n\t\tlet maxCharCode = 0;\n\t\tfor(let i = 0; i < value.length; i++) {\n\t\t\tlet charCode = value.charCodeAt(i);\n\t\t\tif(charCode > maxCharCode) {\n\t\t\t\tmaxCharCode = charCode;\n\t\t\t}\n\t\t}\n\t\tlet charType = maxCharCode > 255 ? CHAR_TYPE.UTF16 : CHAR_TYPE.ASCII;\n\n\t\tlet typedArray = TYPED_ARRAY_MAP[charType];\n\t\tlet allocatedMemory = this.memory.allocUI32(Math.ceil(value.length / (4 / typedArray.BYTES_PER_ELEMENT)));\n\t\tlet data = new typedArray(allocatedMemory.data.buffer as ArrayBuffer, allocatedMemory.data.byteOffset, value.length);\n\t\tfor(let i = 0; i < value.length; i++) {\n\t\t\tdata[i] = value.charCodeAt(i);\n\t\t}\n\n\t\treturn {\n\t\t\tpointer: allocatedMemory.pointer,\n\t\t\tcharType,\n\t\t};\n\t}\n\n\tgetSharedMemory(): SharedAllocatedMemory {\n\t\treturn this.allocatedMemory.getSharedMemory();\n\t}\n\n\tget pointer() {\n\t\treturn this.allocatedMemory.pointer;\n\t}\n\n\tfree() {\n\t\tlet pointer = loadRawPointer(this.allocatedMemory.data, POINTER_INDEX);\n\t\tif(pointer) {\n\t\t\tlet { bufferPosition, bufferByteOffset } = getPointer(pointer, this.memory.positionBits);\n\t\t\tthis.memory.buffers[bufferPosition].free(bufferByteOffset);\n\t\t}\n\t\tthis.allocatedMemory.free();\n\t}\n}\n\n// String.fromCharCode.apply throws a RangeError (Maximum call stack size exceeded) when handed too many\n// arguments at once, so decode large buffers in chunks to support arbitrarily long strings\nfunction fromCharCodes(data: Uint8Array | Uint16Array): string {\n\tconst CHUNK_SIZE = 8192;\n\tif(data.length <= CHUNK_SIZE) {\n\t\treturn String.fromCharCode.apply(null, data as unknown as Array<number>);\n\t}\n\n\tlet string = '';\n\tfor(let i = 0; i < data.length; i += CHUNK_SIZE) {\n\t\tstring += String.fromCharCode.apply(null, data.subarray(i, i + CHUNK_SIZE) as unknown as Array<number>);\n\t}\n\n\treturn string;\n}\n"],"mappings":";;;AAUA,
|
|
1
|
+
{"version":3,"file":"constant-string.js","names":["AllocatedMemory","TYPED_ARRAY_MAP","POINTER_INDEX","LENGTH_INDEX","TYPE_INDEX","ConstantString","memory","value","stringMemory","storeRawPointer","pointer","loadRawPointer","getPointer","typedArray","bufferLength","fromCharCodes","bufferPosition","bufferByteOffset","maxCharCode","i","charCode","charType","allocatedMemory","data","CHUNK_SIZE","string"],"sources":["../src/constant-string.ts"],"sourcesContent":["import AllocatedMemory from './allocated-memory';\nimport type { SharedAllocatedMemory } from './allocated-memory';\nimport type MemoryHeap from './memory-heap';\nimport { getPointer, loadRawPointer, storeRawPointer } from './utils/pointer';\n\nenum CHAR_TYPE {\n\tUNDEFINED,\n\tASCII,\n\tUTF16,\n}\nconst TYPED_ARRAY_MAP = {\n\t[CHAR_TYPE.ASCII]: Uint8Array,\n\t[CHAR_TYPE.UTF16]: Uint16Array,\n};\n\nconst POINTER_INDEX = 0;\nconst LENGTH_INDEX = 1;\nconst TYPE_INDEX = 2;\n\n// An immutable SharedString: the value is written once at construction and never changes\nexport default class ConstantString {\n\tstatic readonly ALLOCATE_COUNT = 3;\n\n\tprivate memory: MemoryHeap;\n\tprivate allocatedMemory: AllocatedMemory;\n\n\treadonly value: string;\n\n\tconstructor(memory: MemoryHeap, value: string | SharedAllocatedMemory) {\n\t\tthis.memory = memory;\n\n\t\tif(typeof value === 'string') {\n\t\t\tthis.allocatedMemory = this.memory.allocUI32(ConstantString.ALLOCATE_COUNT);\n\t\t\tthis.writeString(value);\n\t\t\tthis.value = value;\n\t\t} else {\n\t\t\t// A read-only view over a header another thread already wrote.\n\t\t\tthis.allocatedMemory = new AllocatedMemory(memory, value);\n\t\t\tthis.value = this.readString();\n\t\t}\n\t}\n\n\tprivate writeString(value: string) {\n\t\tconst stringMemory = this.createString(value);\n\n\t\tstoreRawPointer(this.allocatedMemory.data, POINTER_INDEX, stringMemory.pointer);\n\t\tAtomics.store(this.allocatedMemory.data, LENGTH_INDEX, value.length);\n\t\tAtomics.store(this.allocatedMemory.data, TYPE_INDEX, stringMemory.charType);\n\t}\n\n\tprivate readString(): string {\n\t\tlet pointer = loadRawPointer(this.allocatedMemory.data, POINTER_INDEX);\n\t\tif(pointer === 0) {\n\t\t\treturn '';\n\t\t}\n\t\tlet { bufferPosition, bufferByteOffset } = getPointer(pointer, this.memory.positionBits);\n\n\t\t// No lock: the value never changes after it is written once, so the memory it points at is never freed or\n\t\t// rewritten underneath us.\n\t\tlet charType = Atomics.load(this.allocatedMemory.data, TYPE_INDEX);\n\t\t// @ts-expect-error\n\t\tlet typedArray = TYPED_ARRAY_MAP[charType];\n\t\tlet bufferLength = Atomics.load(this.allocatedMemory.data, LENGTH_INDEX);\n\n\t\tlet data = new typedArray(this.memory.buffers[bufferPosition].buf, bufferByteOffset, bufferLength);\n\t\treturn fromCharCodes(data);\n\t}\n\tprivate createString(value: string) {\n\t\tif(value === '') {\n\t\t\treturn {\n\t\t\t\tpointer: 0,\n\t\t\t\tcharType: CHAR_TYPE.ASCII,\n\t\t\t};\n\t\t}\n\n\t\t// NOTE: Scan with a loop instead of Math.max(...charCodes) which blows the stack for large strings\n\t\tlet maxCharCode = 0;\n\t\tfor(let i = 0; i < value.length; i++) {\n\t\t\tlet charCode = value.charCodeAt(i);\n\t\t\tif(charCode > maxCharCode) {\n\t\t\t\tmaxCharCode = charCode;\n\t\t\t}\n\t\t}\n\t\tlet charType = maxCharCode > 255 ? CHAR_TYPE.UTF16 : CHAR_TYPE.ASCII;\n\n\t\tlet typedArray = TYPED_ARRAY_MAP[charType];\n\t\tlet allocatedMemory = this.memory.allocUI32(Math.ceil(value.length / (4 / typedArray.BYTES_PER_ELEMENT)));\n\t\tlet data = new typedArray(allocatedMemory.data.buffer as ArrayBuffer, allocatedMemory.data.byteOffset, value.length);\n\t\tfor(let i = 0; i < value.length; i++) {\n\t\t\tdata[i] = value.charCodeAt(i);\n\t\t}\n\n\t\treturn {\n\t\t\tpointer: allocatedMemory.pointer,\n\t\t\tcharType,\n\t\t};\n\t}\n\n\tgetSharedMemory(): SharedAllocatedMemory {\n\t\treturn this.allocatedMemory.getSharedMemory();\n\t}\n\n\tget pointer() {\n\t\treturn this.allocatedMemory.pointer;\n\t}\n\n\tfree() {\n\t\tlet pointer = loadRawPointer(this.allocatedMemory.data, POINTER_INDEX);\n\t\tif(pointer) {\n\t\t\tlet { bufferPosition, bufferByteOffset } = getPointer(pointer, this.memory.positionBits);\n\t\t\tthis.memory.buffers[bufferPosition].free(bufferByteOffset);\n\t\t}\n\t\tthis.allocatedMemory.free();\n\t}\n}\n\n// String.fromCharCode.apply throws a RangeError (Maximum call stack size exceeded) when handed too many\n// arguments at once, so decode large buffers in chunks to support arbitrarily long strings\nfunction fromCharCodes(data: Uint8Array | Uint16Array): string {\n\tconst CHUNK_SIZE = 8192;\n\tif(data.length <= CHUNK_SIZE) {\n\t\treturn String.fromCharCode.apply(null, data as unknown as Array<number>);\n\t}\n\n\tlet string = '';\n\tfor(let i = 0; i < data.length; i += CHUNK_SIZE) {\n\t\tstring += String.fromCharCode.apply(null, data.subarray(i, i + CHUNK_SIZE) as unknown as Array<number>);\n\t}\n\n\treturn string;\n}\n"],"mappings":";;;AAUA,IAAMC,IAAkB;CACJ,GAAA;CACA,GAAA;AACpB,GAEMC,IAAgB,GAChBC,IAAe,GACfC,IAAa,GAGEC,IAArB,MAAqBA,EAAe;CACnC,OAAgB,iBAAiB;CAEjC;CACA;CAEA;CAEA,YAAY,GAAoB,GAAuC;EAGtE,AAFA,KAAK,SAASC,GAEX,OAAOC,KAAU,YACnB,KAAK,kBAAkB,KAAK,OAAO,UAAUF,EAAe,cAAc,GAC1E,KAAK,YAAYE,CAAK,GACtB,KAAK,QAAQA,MAGb,KAAK,kBAAkB,IAAIP,EAAgBM,GAAQC,CAAK,GACxD,KAAK,QAAQ,KAAK,WAAW;CAE/B;CAEA,YAAoB,GAAe;EAClC,IAAMC,IAAe,KAAK,aAAaD,CAAK;EAI5C,AAFAE,EAAgB,KAAK,gBAAgB,MAAMP,GAAeM,EAAa,OAAO,GAC9E,QAAQ,MAAM,KAAK,gBAAgB,MAAML,GAAcI,EAAM,MAAM,GACnE,QAAQ,MAAM,KAAK,gBAAgB,MAAMH,GAAYI,EAAa,QAAQ;CAC3E;CAEA,aAA6B;EAC5B,IAAIE,IAAUC,EAAe,KAAK,gBAAgB,MAAMT,CAAa;EACrE,IAAGQ,MAAY,GACd,OAAO;EAER,IAAI,EAAE,mBAAgB,wBAAqBE,EAAWF,GAAS,KAAK,OAAO,YAAY,GAMnFG,IAAaZ,EAFF,QAAQ,KAAK,KAAK,gBAAgB,MAAMG,CAEtB,IAC7BU,IAAe,QAAQ,KAAK,KAAK,gBAAgB,MAAMX,CAAY;EAGvE,OAAOY,EAAc,IADNF,EAAW,KAAK,OAAO,QAAQG,EAAe,CAAC,KAAKC,GAAkBH,CAChE,CAAI;CAC1B;CACA,aAAqB,GAAe;EACnC,IAAGP,MAAU,IACZ,OAAO;GACN,SAAS;GACT,UAAA;EACD;EAID,IAAIW,IAAc;EAClB,KAAI,IAAIC,IAAI,GAAGA,IAAIZ,EAAM,QAAQ,KAAK;GACrC,IAAIa,IAAWb,EAAM,WAAWY,CAAC;GACjC,AAAGC,IAAWF,MACb,IAAcE;EAEhB;EACA,IAAIC,IAAWH,IAAc,MAAA,IAAA,GAEzBL,IAAaZ,EAAgBoB,IAC7BC,IAAkB,KAAK,OAAO,UAAU,KAAK,KAAKf,EAAM,UAAU,IAAIM,EAAW,kBAAkB,CAAC,GACpGU,IAAO,IAAIV,EAAWS,EAAgB,KAAK,QAAuBA,EAAgB,KAAK,YAAYf,EAAM,MAAM;EACnH,KAAI,IAAIY,IAAI,GAAGA,IAAIZ,EAAM,QAAQ,KAChC,EAAKY,KAAKZ,EAAM,WAAWY,CAAC;EAG7B,OAAO;GACN,SAASG,EAAgB;GACzB;EACD;CACD;CAEA,kBAAyC;EACxC,OAAO,KAAK,gBAAgB,gBAAgB;CAC7C;CAEA,IAAI,UAAU;EACb,OAAO,KAAK,gBAAgB;CAC7B;CAEA,OAAO;EACN,IAAIZ,IAAUC,EAAe,KAAK,gBAAgB,MAAMT,CAAa;EACrE,IAAGQ,GAAS;GACX,IAAI,EAAE,mBAAgB,wBAAqBE,EAAWF,GAAS,KAAK,OAAO,YAAY;GACvF,KAAK,OAAO,QAAQM,EAAe,CAAC,KAAKC,CAAgB;EAC1D;EACA,KAAK,gBAAgB,KAAK;CAC3B;AACD;AAIA,SAASF,EAAc,GAAwC;CAC9D,IAAMS,IAAa;CACnB,IAAGD,EAAK,UAAUC,GACjB,OAAO,OAAO,aAAa,MAAM,MAAMD,CAAgC;CAGxE,IAAIE,IAAS;CACb,KAAI,IAAIN,IAAI,GAAGA,IAAII,EAAK,QAAQ,KAAKC,GACpC,KAAU,OAAO,aAAa,MAAM,MAAMD,EAAK,SAASJ,GAAGA,IAAIK,CAAU,CAA6B;CAGvG,OAAOC;AACR"}
|
package/dist/local-pool.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"local-pool.js","names":[],"sources":["../src/local-pool.ts"],"sourcesContent":["import AllocatedMemory from './allocated-memory';\nimport type MemoryHeap from './memory-heap';\nimport { getPointer } from './utils/pointer';\nimport { getArrayTypeCode, getByteMultipler, type NumericArray, type NumericArrayIO, type TypedArrayConstructor } from './utils/array-type';\n\n// Array with stable indexes and maximum contiguous memory sizes (necessary to fit large data sets into max 1MB buffers)\n// Can only do insert/deletions from a single thread - useful if other threads are known to be read-only\nexport default class LocalPool<T extends NumericArray = Uint32Array> implements Iterable<T> {\n\tprivate memory: MemoryHeap;\n\tprivate pointers: Array<number> = [];\n\tprivate recycledIndexes: Array<number> = [];\n\tprivate totalLength = 0;\n\treadonly maxChunkSize: number = 100;\n\treadonly dataLength: number = 1;\n\treadonly byteMultipler: number = 1;\n\tprivate typeConstructor: TypedArrayConstructor<T>;\n\tprivate fullDataBlocks: { [key: number]: T } = {};\n\n\tget length(): number {\n\t\treturn this.totalLength - this.recycledIndexes.length;\n\t}\n\tget bufferLength(): number {\n\t\treturn this.maxChunkSize * this.pointers.length;\n\t}\n\n\tconstructor(memory: MemoryHeap, config: LocalPoolConfig<T>) {\n\t\tthis.memory = memory;\n\n\t\tconst dataLength = config?.dataLength ?? 1;\n\t\tconst maxLength = config?.maxChunkSize ?? 100;\n\t\tthis.dataLength = dataLength;\n\t\tthis.maxChunkSize = maxLength;\n\n\t\tthis.typeConstructor = config.type;\n\t\tthis.byteMultipler = getByteMultipler(getArrayTypeCode(config.type));\n\n\t\tlet firstArray = memory.allocUI32(maxLength * dataLength * this.byteMultipler);\n\t\tthis.pointers.push(firstArray.pointer);\n\t\tlet data: T = new this.typeConstructor(firstArray.data.buffer, firstArray.bufferByteOffset, this.dataLength * this.maxChunkSize);\n\t\tthis.fullDataBlocks[0] = data;\n\t}\n\n\tat(index: number): T {\n\t\tlet dataBlock = this.fullDataBlocks[Math.floor(index / this.maxChunkSize)];\n\t\treturn this.getDataBlock(dataBlock, index % this.maxChunkSize);\n\t}\n\tget(index: number, dataIndex = 0): T[number] {\n\t\tconst dataLength = this.dataLength;\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.fullDataBlocks[Math.floor(index / this.maxChunkSize)] as NumericArrayIO;\n\t\treturn dataBlock[(index % this.maxChunkSize) * dataLength + dataIndex];\n\t}\n\n\tpush(values: T[number] | Array<T[number]>): number {\n\t\tconst isSingleValue = typeof values !== 'object';\n\t\tif(!isSingleValue && values.length > this.dataLength) {\n\t\t\tthrow new Error(`Can't insert ${values.length} array into shared list of ${this.dataLength} dataLength`);\n\t\t}\n\n\t\tlet newIndex = this.recycledIndexes.pop();\n\t\tif(newIndex === undefined) {\n\t\t\tnewIndex = this.totalLength++;\n\t\t}\n\n\n\t\tlet pointerIndex = Math.floor(newIndex / this.maxChunkSize);\n\t\tif(pointerIndex >= this.pointers.length) {\n\t\t\tlet newArray = this.memory.allocUI32(this.maxChunkSize * this.dataLength * this.byteMultipler);\n\t\t\tthis.pointers.push(newArray.pointer);\n\t\t\tlet data: T = new this.typeConstructor(newArray.data.buffer, newArray.bufferByteOffset, this.dataLength * this.maxChunkSize);\n\t\t\tthis.fullDataBlocks[pointerIndex] = data;\n\t\t}\n\n\t\tlet dataBlock = this.fullDataBlocks[pointerIndex] as NumericArrayIO;\n\t\tlet blockIndex = newIndex % this.maxChunkSize;\n\t\tlet startIndex = this.dataLength * blockIndex;\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\tdeleteIndex(index: number) {\n\t\tthis.recycledIndexes.push(index);\n\t}\n\n\tclear() {\n\t\tthis.totalLength = 0;\n\t\tthis.recycledIndexes = [];\n\t}\n\n\t*[Symbol.iterator]() {\n\t\tconst recycledValues: { [key: number]: true } = {};\n\t\tfor(let value of this.recycledIndexes) {\n\t\t\trecycledValues[value] = true;\n\t\t}\n\n\t\tlet dataBlock = this.getFullDataBlock(0);\n\t\tlet dataBlockIndex = 0;\n\t\tfor(let i = 0; i < this.totalLength; i++) {\n\t\t\tif(!recycledValues[i]) {\n\t\t\t\tlet newDataBlockIndex = Math.floor(i / this.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 % this.maxChunkSize);\n\t\t\t}\n\t\t}\n\t}\n\n\tprivate getFullDataBlock(index: number) {\n\t\tlet pointerIndex = Math.floor(index / this.maxChunkSize);\n\t\tlet cachedDataBlock = this.fullDataBlocks[pointerIndex];\n\t\tif(cachedDataBlock) {\n\t\t\treturn cachedDataBlock;\n\t\t}\n\t\tif(pointerIndex >= this.pointers.length) {\n\t\t\tlet newArray = this.memory.allocUI32(this.maxChunkSize * this.dataLength * this.byteMultipler);\n\t\t\tthis.pointers.push(newArray.pointer);\n\t\t}\n\n\t\tlet array = new AllocatedMemory(this.memory, getPointer(this.pointers[pointerIndex], this.memory.positionBits));\n\t\tlet data: T = new this.typeConstructor(array.data.buffer, array.bufferByteOffset, this.dataLength * this.maxChunkSize);\n\t\tthis.fullDataBlocks[pointerIndex] = data;\n\t\treturn data;\n\t}\n\n\tprivate getDataBlock(rawData: T, index: number): T {\n\t\tconst start = index * this.dataLength;\n\t\treturn rawData.subarray(start, start + this.dataLength) as T;\n\t}\n\n\tfree() {\n\t\tfor(let pointer of this.pointers) {\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.pointers = [];\n\t\tthis.recycledIndexes = [];\n\t\tthis.totalLength = 0;\n\t\tthis.fullDataBlocks = {};\n\t}\n}\n\ninterface LocalPoolConfig<T extends NumericArray> {\n\tmaxChunkSize?: number\n\ttype: TypedArrayConstructor<T>\n\tdataLength?: number\n}\nexport type { LocalPoolConfig };"],"mappings":";;;;AAOA,
|
|
1
|
+
{"version":3,"file":"local-pool.js","names":["AllocatedMemory","LocalPool","memory","dataLength","config","maxLength","getByteMultipler","getArrayTypeCode","firstArray","data","dataBlock","index","dataIndex","isSingleValue","values","newIndex","pointerIndex","newArray","blockIndex","startIndex","i","recycledValues","value","dataBlockIndex","newDataBlockIndex","cachedDataBlock","array","getPointer","start","rawData","pointer"],"sources":["../src/local-pool.ts"],"sourcesContent":["import AllocatedMemory from './allocated-memory';\nimport type MemoryHeap from './memory-heap';\nimport { getPointer } from './utils/pointer';\nimport { getArrayTypeCode, getByteMultipler, type NumericArray, type NumericArrayIO, type TypedArrayConstructor } from './utils/array-type';\n\n// Array with stable indexes and maximum contiguous memory sizes (necessary to fit large data sets into max 1MB buffers)\n// Can only do insert/deletions from a single thread - useful if other threads are known to be read-only\nexport default class LocalPool<T extends NumericArray = Uint32Array> implements Iterable<T> {\n\tprivate memory: MemoryHeap;\n\tprivate pointers: Array<number> = [];\n\tprivate recycledIndexes: Array<number> = [];\n\tprivate totalLength = 0;\n\treadonly maxChunkSize: number = 100;\n\treadonly dataLength: number = 1;\n\treadonly byteMultipler: number = 1;\n\tprivate typeConstructor: TypedArrayConstructor<T>;\n\tprivate fullDataBlocks: { [key: number]: T } = {};\n\n\tget length(): number {\n\t\treturn this.totalLength - this.recycledIndexes.length;\n\t}\n\tget bufferLength(): number {\n\t\treturn this.maxChunkSize * this.pointers.length;\n\t}\n\n\tconstructor(memory: MemoryHeap, config: LocalPoolConfig<T>) {\n\t\tthis.memory = memory;\n\n\t\tconst dataLength = config?.dataLength ?? 1;\n\t\tconst maxLength = config?.maxChunkSize ?? 100;\n\t\tthis.dataLength = dataLength;\n\t\tthis.maxChunkSize = maxLength;\n\n\t\tthis.typeConstructor = config.type;\n\t\tthis.byteMultipler = getByteMultipler(getArrayTypeCode(config.type));\n\n\t\tlet firstArray = memory.allocUI32(maxLength * dataLength * this.byteMultipler);\n\t\tthis.pointers.push(firstArray.pointer);\n\t\tlet data: T = new this.typeConstructor(firstArray.data.buffer, firstArray.bufferByteOffset, this.dataLength * this.maxChunkSize);\n\t\tthis.fullDataBlocks[0] = data;\n\t}\n\n\tat(index: number): T {\n\t\tlet dataBlock = this.fullDataBlocks[Math.floor(index / this.maxChunkSize)];\n\t\treturn this.getDataBlock(dataBlock, index % this.maxChunkSize);\n\t}\n\tget(index: number, dataIndex = 0): T[number] {\n\t\tconst dataLength = this.dataLength;\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.fullDataBlocks[Math.floor(index / this.maxChunkSize)] as NumericArrayIO;\n\t\treturn dataBlock[(index % this.maxChunkSize) * dataLength + dataIndex];\n\t}\n\n\tpush(values: T[number] | Array<T[number]>): number {\n\t\tconst isSingleValue = typeof values !== 'object';\n\t\tif(!isSingleValue && values.length > this.dataLength) {\n\t\t\tthrow new Error(`Can't insert ${values.length} array into shared list of ${this.dataLength} dataLength`);\n\t\t}\n\n\t\tlet newIndex = this.recycledIndexes.pop();\n\t\tif(newIndex === undefined) {\n\t\t\tnewIndex = this.totalLength++;\n\t\t}\n\n\n\t\tlet pointerIndex = Math.floor(newIndex / this.maxChunkSize);\n\t\tif(pointerIndex >= this.pointers.length) {\n\t\t\tlet newArray = this.memory.allocUI32(this.maxChunkSize * this.dataLength * this.byteMultipler);\n\t\t\tthis.pointers.push(newArray.pointer);\n\t\t\tlet data: T = new this.typeConstructor(newArray.data.buffer, newArray.bufferByteOffset, this.dataLength * this.maxChunkSize);\n\t\t\tthis.fullDataBlocks[pointerIndex] = data;\n\t\t}\n\n\t\tlet dataBlock = this.fullDataBlocks[pointerIndex] as NumericArrayIO;\n\t\tlet blockIndex = newIndex % this.maxChunkSize;\n\t\tlet startIndex = this.dataLength * blockIndex;\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\tdeleteIndex(index: number) {\n\t\tthis.recycledIndexes.push(index);\n\t}\n\n\tclear() {\n\t\tthis.totalLength = 0;\n\t\tthis.recycledIndexes = [];\n\t}\n\n\t*[Symbol.iterator]() {\n\t\tconst recycledValues: { [key: number]: true } = {};\n\t\tfor(let value of this.recycledIndexes) {\n\t\t\trecycledValues[value] = true;\n\t\t}\n\n\t\tlet dataBlock = this.getFullDataBlock(0);\n\t\tlet dataBlockIndex = 0;\n\t\tfor(let i = 0; i < this.totalLength; i++) {\n\t\t\tif(!recycledValues[i]) {\n\t\t\t\tlet newDataBlockIndex = Math.floor(i / this.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 % this.maxChunkSize);\n\t\t\t}\n\t\t}\n\t}\n\n\tprivate getFullDataBlock(index: number) {\n\t\tlet pointerIndex = Math.floor(index / this.maxChunkSize);\n\t\tlet cachedDataBlock = this.fullDataBlocks[pointerIndex];\n\t\tif(cachedDataBlock) {\n\t\t\treturn cachedDataBlock;\n\t\t}\n\t\tif(pointerIndex >= this.pointers.length) {\n\t\t\tlet newArray = this.memory.allocUI32(this.maxChunkSize * this.dataLength * this.byteMultipler);\n\t\t\tthis.pointers.push(newArray.pointer);\n\t\t}\n\n\t\tlet array = new AllocatedMemory(this.memory, getPointer(this.pointers[pointerIndex], this.memory.positionBits));\n\t\tlet data: T = new this.typeConstructor(array.data.buffer, array.bufferByteOffset, this.dataLength * this.maxChunkSize);\n\t\tthis.fullDataBlocks[pointerIndex] = data;\n\t\treturn data;\n\t}\n\n\tprivate getDataBlock(rawData: T, index: number): T {\n\t\tconst start = index * this.dataLength;\n\t\treturn rawData.subarray(start, start + this.dataLength) as T;\n\t}\n\n\tfree() {\n\t\tfor(let pointer of this.pointers) {\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.pointers = [];\n\t\tthis.recycledIndexes = [];\n\t\tthis.totalLength = 0;\n\t\tthis.fullDataBlocks = {};\n\t}\n}\n\ninterface LocalPoolConfig<T extends NumericArray> {\n\tmaxChunkSize?: number\n\ttype: TypedArrayConstructor<T>\n\tdataLength?: number\n}\nexport type { LocalPoolConfig };"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"read-write-lock.js","names":[],"sources":["../../src/lock/read-write-lock.ts"],"sourcesContent":["const WRITE_LOCKED = -1;\n\nfunction canWait(): boolean {\n\treturn typeof self !== 'undefined' && 'WorkerGlobalScope' in self;\n}\n\nexport function readLock(data: Int32Array, index: number = 0) {\n\t// Pending writers block new readers so a steady stream of queries cannot starve a resize.\n\twhile(true) {\n\t\tlet writers = data[index + 1];\n\t\tif(writers !== 0) {\n\t\t\tif(canWait()) {\n\t\t\t\tAtomics.wait(data, index + 1, writers);\n\t\t\t}\n\t\t\tcontinue;\n\t\t}\n\t\tlet readers = data[index];\n\t\tif(readers !== WRITE_LOCKED && Atomics.compareExchange(data, index, readers, readers + 1) === readers) {\n\t\t\treturn;\n\t\t}\n\t\tif(canWait() && readers === WRITE_LOCKED) {\n\t\t\tAtomics.wait(data, index, WRITE_LOCKED);\n\t\t}\n\t}\n}\nexport function writeLock(data: Int32Array, index: number = 0) {\n\tAtomics.add(data, index + 1, 1);\n\twhile(Atomics.compareExchange(data, index, 0, WRITE_LOCKED) !== 0) {\n\t\tlet state = data[index];\n\t\tif(canWait() && state !== 0) {\n\t\t\tAtomics.wait(data, index, state);\n\t\t}\n\t}\n\tAtomics.sub(data, index + 1, 1);\n}\n// Non-blocking write acquire: succeeds only when there is no active reader and no writer. Unlike writeLock it never\n// registers a pending writer, so a failed attempt leaves readers completely untouched - the caller just backs off.\nexport function tryWriteLock(data: Int32Array, index: number = 0): boolean {\n\treturn Atomics.compareExchange(data, index, 0, WRITE_LOCKED) === 0;\n}\n\nexport function readUnlock(data: Int32Array, index: number = 0) {\n\tif(Atomics.sub(data, index, 1) === 1) {\n\t\tAtomics.notify(data, index);\n\t}\n}\nexport function writeUnlock(data: Int32Array, index: number = 0) {\n\tif(Atomics.compareExchange(data, index, WRITE_LOCKED, 0) !== WRITE_LOCKED) {\n\t\tconsole.warn('We are unlocking when it was not write locked!');\n\t}\n\tAtomics.notify(data, index);\n\tAtomics.notify(data, index + 1);\n}\n\nexport const READ_WRITE_LOCK_ALLOCATE_COUNT = 2;\n"],"mappings":"AAEA,
|
|
1
|
+
{"version":3,"file":"read-write-lock.js","names":["canWait","readLock","writers","data","index","readers","writeLock","state","tryWriteLock","readUnlock","writeUnlock","READ_WRITE_LOCK_ALLOCATE_COUNT"],"sources":["../../src/lock/read-write-lock.ts"],"sourcesContent":["const WRITE_LOCKED = -1;\n\nfunction canWait(): boolean {\n\treturn typeof self !== 'undefined' && 'WorkerGlobalScope' in self;\n}\n\nexport function readLock(data: Int32Array, index: number = 0) {\n\t// Pending writers block new readers so a steady stream of queries cannot starve a resize.\n\twhile(true) {\n\t\tlet writers = data[index + 1];\n\t\tif(writers !== 0) {\n\t\t\tif(canWait()) {\n\t\t\t\tAtomics.wait(data, index + 1, writers);\n\t\t\t}\n\t\t\tcontinue;\n\t\t}\n\t\tlet readers = data[index];\n\t\tif(readers !== WRITE_LOCKED && Atomics.compareExchange(data, index, readers, readers + 1) === readers) {\n\t\t\treturn;\n\t\t}\n\t\tif(canWait() && readers === WRITE_LOCKED) {\n\t\t\tAtomics.wait(data, index, WRITE_LOCKED);\n\t\t}\n\t}\n}\nexport function writeLock(data: Int32Array, index: number = 0) {\n\tAtomics.add(data, index + 1, 1);\n\twhile(Atomics.compareExchange(data, index, 0, WRITE_LOCKED) !== 0) {\n\t\tlet state = data[index];\n\t\tif(canWait() && state !== 0) {\n\t\t\tAtomics.wait(data, index, state);\n\t\t}\n\t}\n\tAtomics.sub(data, index + 1, 1);\n}\n// Non-blocking write acquire: succeeds only when there is no active reader and no writer. Unlike writeLock it never\n// registers a pending writer, so a failed attempt leaves readers completely untouched - the caller just backs off.\nexport function tryWriteLock(data: Int32Array, index: number = 0): boolean {\n\treturn Atomics.compareExchange(data, index, 0, WRITE_LOCKED) === 0;\n}\n\nexport function readUnlock(data: Int32Array, index: number = 0) {\n\tif(Atomics.sub(data, index, 1) === 1) {\n\t\tAtomics.notify(data, index);\n\t}\n}\nexport function writeUnlock(data: Int32Array, index: number = 0) {\n\tif(Atomics.compareExchange(data, index, WRITE_LOCKED, 0) !== WRITE_LOCKED) {\n\t\tconsole.warn('We are unlocking when it was not write locked!');\n\t}\n\tAtomics.notify(data, index);\n\tAtomics.notify(data, index + 1);\n}\n\nexport const READ_WRITE_LOCK_ALLOCATE_COUNT = 2;\n"],"mappings":"AAEA,SAASA,IAAmB;CAC3B,OAAO,OAAO,OAAS,OAAe,uBAAuB;AAC9D;AAEA,SAAgBC,EAAS,GAAkB,IAAgB,GAAG;CAE7D,SAAY;EACX,IAAIC,IAAUC,EAAKC,IAAQ;EAC3B,IAAGF,MAAY,GAAG;GACjB,AAAGF,EAAQ,KACV,QAAQ,KAAKG,GAAMC,IAAQ,GAAGF,CAAO;GAEtC;EACD;EACA,IAAIG,IAAUF,EAAKC;EACnB,IAAGC,MAAY,MAAgB,QAAQ,gBAAgBF,GAAMC,GAAOC,GAASA,IAAU,CAAC,MAAMA,GAC7F;EAED,AAAGL,EAAQ,KAAKK,MAAY,MAC3B,QAAQ,KAAKF,GAAMC,GAAO,EAAY;CAExC;AACD;AACA,SAAgBE,EAAU,GAAkB,IAAgB,GAAG;CAE9D,KADA,QAAQ,IAAIH,GAAMC,IAAQ,GAAG,CAAC,GACxB,QAAQ,gBAAgBD,GAAMC,GAAO,GAAG,EAAY,MAAM,IAAG;EAClE,IAAIG,IAAQJ,EAAKC;EACjB,AAAGJ,EAAQ,KAAKO,MAAU,KACzB,QAAQ,KAAKJ,GAAMC,GAAOG,CAAK;CAEjC;CACA,QAAQ,IAAIJ,GAAMC,IAAQ,GAAG,CAAC;AAC/B;AAGA,SAAgBI,EAAa,GAAkB,IAAgB,GAAY;CAC1E,OAAO,QAAQ,gBAAgBL,GAAMC,GAAO,GAAG,EAAY,MAAM;AAClE;AAEA,SAAgBK,EAAW,GAAkB,IAAgB,GAAG;CAC/D,AAAG,QAAQ,IAAIN,GAAMC,GAAO,CAAC,MAAM,KAClC,QAAQ,OAAOD,GAAMC,CAAK;AAE5B;AACA,SAAgBM,EAAY,GAAkB,IAAgB,GAAG;CAKhE,AAJG,QAAQ,gBAAgBP,GAAMC,GAAO,IAAc,CAAC,MAAM,MAC5D,QAAQ,KAAK,gDAAgD,GAE9D,QAAQ,OAAOD,GAAMC,CAAK,GAC1B,QAAQ,OAAOD,GAAMC,IAAQ,CAAC;AAC/B;AAEA,IAAaO,IAAiC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"simple-lock.js","names":[],"sources":["../../src/lock/simple-lock.ts"],"sourcesContent":["// Three-state futex mutex (Drepper / Lars T Hansen): 0 = unlocked, 1 = locked with no waiters, 2 = locked and contended.\n// Tracking contention lets unlock skip Atomics.notify entirely on the common uncontended path (notify is expensive even\n// with zero waiters), while still waking a waiter whenever one parked itself by moving the state to CONTENDED.\nconst UNLOCKED = 0;\nconst LOCKED = 1;\nconst CONTENDED = 2;\n\nexport function lock(data: Int32Array, index: number = 0) {\n\tlet c = Atomics.compareExchange(data, index, UNLOCKED, LOCKED);\n\tif(c !== UNLOCKED) {\n\t\t// Mark the lock contended so the current holder knows to notify us on release\n\t\tif(c !== CONTENDED) {\n\t\t\tc = Atomics.exchange(data, index, CONTENDED);\n\t\t}\n\t\tlet isWorker = typeof self !== 'undefined' && 'WorkerGlobalScope' in self;\n\t\twhile(c !== UNLOCKED) {\n\t\t\tif(isWorker) {\n\t\t\t\tAtomics.wait(data, index, CONTENDED);\n\t\t\t}\n\t\t\t// On the main thread Atomics.wait throws, so this loop degrades to a spin re-acquire\n\t\t\tc = Atomics.exchange(data, index, CONTENDED);\n\t\t}\n\t}\n}\nexport function unlock(data: Int32Array, index: number = 0) {\n\t// If the lock was contended a waiter may be parked, so publish the release and wake exactly one of them\n\tif(Atomics.exchange(data, index, UNLOCKED) === CONTENDED) {\n\t\tAtomics.notify(data, index, 1);\n\t}\n}\n\nexport const SIMPLE_LOCK_ALLOCATE_COUNT = 1;\n"],"mappings":"AAOA,
|
|
1
|
+
{"version":3,"file":"simple-lock.js","names":["lock","c","data","index","isWorker","unlock","SIMPLE_LOCK_ALLOCATE_COUNT"],"sources":["../../src/lock/simple-lock.ts"],"sourcesContent":["// Three-state futex mutex (Drepper / Lars T Hansen): 0 = unlocked, 1 = locked with no waiters, 2 = locked and contended.\n// Tracking contention lets unlock skip Atomics.notify entirely on the common uncontended path (notify is expensive even\n// with zero waiters), while still waking a waiter whenever one parked itself by moving the state to CONTENDED.\nconst UNLOCKED = 0;\nconst LOCKED = 1;\nconst CONTENDED = 2;\n\nexport function lock(data: Int32Array, index: number = 0) {\n\tlet c = Atomics.compareExchange(data, index, UNLOCKED, LOCKED);\n\tif(c !== UNLOCKED) {\n\t\t// Mark the lock contended so the current holder knows to notify us on release\n\t\tif(c !== CONTENDED) {\n\t\t\tc = Atomics.exchange(data, index, CONTENDED);\n\t\t}\n\t\tlet isWorker = typeof self !== 'undefined' && 'WorkerGlobalScope' in self;\n\t\twhile(c !== UNLOCKED) {\n\t\t\tif(isWorker) {\n\t\t\t\tAtomics.wait(data, index, CONTENDED);\n\t\t\t}\n\t\t\t// On the main thread Atomics.wait throws, so this loop degrades to a spin re-acquire\n\t\t\tc = Atomics.exchange(data, index, CONTENDED);\n\t\t}\n\t}\n}\nexport function unlock(data: Int32Array, index: number = 0) {\n\t// If the lock was contended a waiter may be parked, so publish the release and wake exactly one of them\n\tif(Atomics.exchange(data, index, UNLOCKED) === CONTENDED) {\n\t\tAtomics.notify(data, index, 1);\n\t}\n}\n\nexport const SIMPLE_LOCK_ALLOCATE_COUNT = 1;\n"],"mappings":"AAOA,SAAgBA,EAAK,GAAkB,IAAgB,GAAG;CACzD,IAAIC,IAAI,QAAQ,gBAAgBC,GAAMC,GAAO,GAAU,CAAM;CAC7D,IAAGF,MAAM,GAAU;EAElB,AAAGA,MAAM,MACR,IAAI,QAAQ,SAASC,GAAMC,GAAO,CAAS;EAE5C,IAAIC,IAAW,OAAO,OAAS,OAAe,uBAAuB;EACrE,OAAMH,MAAM,IAKX,AAJGG,KACF,QAAQ,KAAKF,GAAMC,GAAO,CAAS,GAGpC,IAAI,QAAQ,SAASD,GAAMC,GAAO,CAAS;CAE7C;AACD;AACA,SAAgBE,EAAO,GAAkB,IAAgB,GAAG;CAE3D,AAAG,QAAQ,SAASH,GAAMC,GAAO,CAAQ,MAAM,KAC9C,QAAQ,OAAOD,GAAMC,GAAO,CAAC;AAE/B;AAEA,IAAaG,IAA6B"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"memory-buffer.js","names":[],"sources":["../src/memory-buffer.ts"],"sourcesContent":["// Just disabling this rule instead of re-writing for now to keep asa close to original in case we want to pull updates later\n// Doing `block && block.fill(fill)` instead of `if(block) { block.fill(fill); }` is valid code even if it is hard to parse\n/* eslint-disable @typescript-eslint/no-unused-expressions */\n\nimport type { Pow2 } from './interfaces/pow2';\nimport type { TypedArray } from './interfaces/typed-array';\nimport { lock, unlock } from './lock/simple-lock';\nimport { typedArray } from './utils/typedarray';\n\nconst STATE_FREE = 0;\nconst STATE_USED = 1;\nconst STATE_TOP = 2;\nconst STATE_END = 3;\nconst STATE_ALIGN = 4;\nconst STATE_FLAGS = 5;\nconst STATE_MIN_SPLIT = 6;\n// Highest top ever reached. callock can skip re-zeroing it\nconst STATE_HIGH_WATER = 7;\n\nconst MASK_COMPACT = 1;\nconst MASK_SPLIT = 2;\n\nconst SIZEOF = {\n\tu8: 1,\n\tu8c: 1,\n\ti8: 1,\n\tu16: 2,\n\ti16: 2,\n\tu32: 4,\n\ti32: 4,\n\ti64: 8,\n\tu64: 8,\n\tf32: 4,\n\tf64: 8,\n};\n\nconst SIZEOF_STATE = 9 * 4;\n\nconst MEM_BLOCK_SIZE = 0;\nconst MEM_BLOCK_NEXT = 1;\n// The used list is doubly linked so free() can unlink a block in O(1) instead of walking to find its predecessor.\n// MEM_BLOCK_STATE marks live blocks so free() rejects a double-free (which would otherwise splice free-list pointers\n// into the used list) without that walk. Word 3 also pads the header to 16 bytes, keeping data addresses aligned exactly\n// as the original 8-byte header did.\nconst MEM_BLOCK_PREV = 2;\nconst MEM_BLOCK_STATE = 3;\n\nconst SIZEOF_MEM_BLOCK = 4 * 4;\n\nconst BLOCK_FREE = 0;\nconst BLOCK_USED = 1;\n\n// Copied from https://github.com/thi-ng/umbrella/blob/develop/packages/malloc/src/pool.ts\n// Changes: Atomic.load/store on state, simple lock on malloc/free\n// Added bytesFor/lengthOf to be able to grab expected length of a block\nexport default class MemoryBuffer {\n\tbuf: ArrayBufferLike;\n\n\tprotected readonly start: number;\n\tprotected u8: Uint8Array;\n\tprotected u32: Uint32Array;\n\tprotected state: Uint32Array;\n\tprotected lock: Int32Array;\n\t// Whether the most recent malloc came from never-before-allocated (already-zeroed) memory. Per-instance/per-thread and\n\t// read by calloc immediately after malloc under the same call, so no locking is needed.\n\tprivate lastMallocFresh = false;\n\n\tconstructor(opts: Partial<MemoryBufferConfig> = {}) {\n\t\tthis.buf = opts.buf ? opts.buf : new ArrayBuffer(opts.size || 0x1000);\n\t\tthis.start = opts.start != null ? align(Math.max(opts.start, 0), 4) : 0;\n\t\tthis.u8 = new Uint8Array(this.buf);\n\t\tthis.u32 = new Uint32Array(this.buf);\n\t\tthis.state = new Uint32Array(this.buf, this.start, SIZEOF_STATE / 4);\n\t\tthis.lock = new Int32Array(this.buf, this.start + this.state.byteLength - 4, 1);\n\n\t\tif(!opts.skipInitialization) {\n\t\t\tconst _align = opts.align || 8;\n\t\t\tif(_align < 8) {\n\t\t\t\tthrow new Error(`invalid alignment: ${_align}, must be a pow2 and >= 8`);\n\t\t\t}\n\t\t\tconst top = this.initialTop(_align);\n\t\t\tconst resolvedEnd =\n\t\t\t\topts.end != null\n\t\t\t\t\t? Math.min(opts.end, this.buf.byteLength)\n\t\t\t\t\t: this.buf.byteLength;\n\n\t\t\tif(top >= resolvedEnd) {\n\t\t\t\tthrow new Error(\n\t\t\t\t\t`insufficient address range (0x${this.start.toString(\n\t\t\t\t\t\t16,\n\t\t\t\t\t)} - 0x${resolvedEnd.toString(16)})`,\n\t\t\t\t);\n\t\t\t}\n\n\t\t\tthis.align = _align;\n\t\t\tthis.doCompact = opts.compact !== false;\n\t\t\tthis.doSplit = opts.split !== false;\n\t\t\tthis.minSplit = opts.minSplit || SIZEOF_MEM_BLOCK * 2;\n\t\t\tthis.end = resolvedEnd;\n\t\t\tthis.top = top;\n\t\t\tthis.highWater = top;\n\t\t\tthis._free = 0;\n\t\t\tthis._used = 0;\n\t\t}\n\t}\n\n\tstats(): Readonly<MemoryBufferStats> {\n\t\tconst listStats = (block: number) => {\n\t\t\tlet count = 0;\n\t\t\tlet size = 0;\n\t\t\twhile(block) {\n\t\t\t\tcount++;\n\t\t\t\tsize += this.blockSize(block);\n\t\t\t\tblock = this.blockNext(block);\n\n\t\t\t\tif(block > this.end) {\n\t\t\t\t\tconsole.error(`Trying to get stats for block past end of buffer: ${block} > ${this.end}`);\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn { count, size };\n\t\t};\n\t\tconst free = listStats(this._free);\n\t\treturn {\n\t\t\tfree,\n\t\t\tused: listStats(this._used),\n\t\t\ttop: this.top,\n\t\t\tavailable: this.end - this.top + free.size,\n\t\t\ttotal: this.buf.byteLength,\n\t\t};\n\t}\n\n\tcallocAs<T extends Type>(type: T, num: number, fill = 0) {\n\t\tconst block = this.mallocAs(type, num);\n\t\t// Fresh bump memory is already zero from the backing buffer; only fill for a non-zero value or reused (dirty) memory.\n\t\tblock && (fill !== 0 || !this.lastMallocFresh) && block.fill(fill);\n\t\treturn block;\n\t}\n\n\tmallocAs<T extends Type>(type: T, num: number) {\n\t\tconst addr = this.malloc(num * SIZEOF[type]);\n\t\treturn addr ? typedArray(type, this.buf, addr, num) : undefined;\n\t}\n\n\tcalloc(bytes: number, fill = 0) {\n\t\tconst addr = this.malloc(bytes);\n\t\t// Fresh bump memory is already zero from the backing buffer; only fill for a non-zero value or reused (dirty) memory.\n\t\taddr && (fill !== 0 || !this.lastMallocFresh) && this.u8.fill(fill, addr, addr + bytes);\n\t\treturn addr;\n\t}\n\n\tmalloc(bytes: number) {\n\t\tif(bytes <= 0) {\n\t\t\treturn 0;\n\t\t}\n\t\tlock(this.lock);\n\t\tconst paddedSize = align(bytes + SIZEOF_MEM_BLOCK, this.align);\n\t\tconst end = this.end;\n\t\t// Without compaction top never retreats, so bump memory is always pristine and no high-water tracking is needed. With\n\t\t// compaction top can move back below the high-water mark, so a bump there reuses dirty memory and must be zeroed.\n\t\tconst doCompact = this.doCompact;\n\t\tconst highWater = doCompact ? this.highWater : 0;\n\t\tlet top = this.top;\n\t\tlet block = this._free;\n\t\tlet prev = 0;\n\t\twhile(block) {\n\t\t\tconst blockSize = this.blockSize(block);\n\t\t\tconst isTop = block + blockSize >= top;\n\t\t\tif(isTop || blockSize >= paddedSize) {\n\t\t\t\tlet result = this.mallocTop(\n\t\t\t\t\tblock,\n\t\t\t\t\tprev,\n\t\t\t\t\tblockSize,\n\t\t\t\t\tpaddedSize,\n\t\t\t\t\tisTop,\n\t\t\t\t);\n\n\t\t\t\t// A free-list block was previously allocated (below the high-water mark), so its memory is dirty.\n\t\t\t\tthis.lastMallocFresh = result !== 0 && doCompact && block >= highWater;\n\t\t\t\tif(doCompact && this.top > highWater) {\n\t\t\t\t\tthis.highWater = this.top;\n\t\t\t\t}\n\t\t\t\tunlock(this.lock);\n\t\t\t\treturn result;\n\t\t\t}\n\t\t\tprev = block;\n\t\t\tblock = this.blockNext(block);\n\t\t}\n\t\tblock = top;\n\t\ttop = block + paddedSize;\n\t\tif(top <= end) {\n\t\t\tthis.setBlockSize(block, paddedSize);\n\t\t\tthis.pushUsed(block);\n\t\t\tthis.top = top;\n\t\t\t// Bump allocation only ever advances into untouched memory, so it is fresh whenever it is at/above the mark.\n\t\t\tthis.lastMallocFresh = !doCompact || block >= highWater;\n\t\t\tif(doCompact && top > highWater) {\n\t\t\t\tthis.highWater = top;\n\t\t\t}\n\t\t\tlet result = blockDataAddress(block);\n\t\t\tunlock(this.lock);\n\n\t\t\treturn result;\n\t\t}\n\t\tthis.lastMallocFresh = false;\n\t\tunlock(this.lock);\n\t\treturn 0;\n\t}\n\n\tprivate mallocTop(\n\t\tblock: number,\n\t\tprev: number,\n\t\tblockSize: number,\n\t\tpaddedSize: number,\n\t\tisTop: boolean,\n\t) {\n\t\tif(isTop && block + paddedSize > this.end) return 0;\n\t\tif(prev) {\n\t\t\tthis.unlinkBlock(prev, block);\n\t\t} else {\n\t\t\tthis._free = this.blockNext(block);\n\t\t}\n\t\tthis.pushUsed(block);\n\t\tif(isTop) {\n\t\t\t// Without splitting, shrinking would expose a new block header inside stale views of the old allocation.\n\t\t\tif(this.doSplit || paddedSize >= blockSize) {\n\t\t\t\tthis.top = block + this.setBlockSize(block, paddedSize);\n\t\t\t}\n\t\t} else if(this.doSplit) {\n\t\t\tconst excess = blockSize - paddedSize;\n\t\t\texcess >= this.minSplit &&\n\t\t\tthis.splitBlock(block, paddedSize, excess);\n\t\t}\n\t\treturn blockDataAddress(block);\n\t}\n\n\tbytesFor(ptrOrArray: number | TypedArray): number | undefined {\n\t\tlet addr: number;\n\t\tif(typeof ptrOrArray !== 'number') {\n\t\t\tif(ptrOrArray.buffer !== this.buf) {\n\t\t\t\treturn undefined;\n\t\t\t}\n\t\t\taddr = ptrOrArray.byteOffset;\n\t\t} else {\n\t\t\taddr = ptrOrArray;\n\t\t}\n\n\t\taddr = blockSelfAddress(addr);\n\t\tlet block = this._used;\n\t\twhile(block) {\n\t\t\tif(block === addr) {\n\t\t\t\treturn this.blockSize(addr) - SIZEOF_MEM_BLOCK;\n\t\t\t}\n\t\t\tblock = this.blockNext(block);\n\t\t}\n\n\t\treturn undefined;\n\t}\n\tlengthOf(ptrOrArray: number | TypedArray): number | undefined {\n\t\tlet bytes = this.bytesFor(ptrOrArray);\n\t\tif(bytes) {\n\t\t\treturn bytes / this.u32.BYTES_PER_ELEMENT;\n\t\t} else {\n\t\t\treturn undefined;\n\t\t}\n\t}\n\tallocationLengthAt(dataAddress: number): number {\n\t\treturn (this.blockSize(blockSelfAddress(dataAddress)) - SIZEOF_MEM_BLOCK) / this.u32.BYTES_PER_ELEMENT;\n\t}\n\n\tfree(ptrOrArray: number | TypedArray) {\n\t\tlet addr: number;\n\t\tif(typeof ptrOrArray !== 'number') {\n\t\t\tif(ptrOrArray.buffer !== this.buf) {\n\t\t\t\treturn false;\n\t\t\t}\n\t\t\taddr = ptrOrArray.byteOffset;\n\t\t} else {\n\t\t\taddr = ptrOrArray;\n\t\t}\n\t\tlock(this.lock);\n\t\taddr = blockSelfAddress(addr);\n\t\t// Reject anything not currently live (double-free or a stray pointer) without walking the used list.\n\t\tif(this.blockState(addr) !== BLOCK_USED) {\n\t\t\tunlock(this.lock);\n\t\t\treturn false;\n\t\t}\n\t\tconst next = this.blockNext(addr);\n\t\tconst prev = this.blockPrev(addr);\n\t\tif(prev) {\n\t\t\tthis.setBlockNext(prev, next);\n\t\t} else {\n\t\t\tthis._used = next;\n\t\t}\n\t\tif(next) {\n\t\t\tthis.setBlockPrev(next, prev);\n\t\t}\n\t\tthis.setBlockState(addr, BLOCK_FREE);\n\t\tthis.insert(addr);\n\t\tthis.doCompact && this.compact();\n\n\t\tunlock(this.lock);\n\t\treturn true;\n\t}\n\n\trelease() {\n\t\tdelete (<any> this).u8;\n\t\tdelete (<any> this).u32;\n\t\tdelete (<any> this).state;\n\t\tdelete (<any> this).buf;\n\t\treturn true;\n\t}\n\n\tprotected get align() {\n\t\treturn <Pow2> this.state[STATE_ALIGN];\n\t}\n\n\tprotected set align(x: Pow2) {\n\t\tthis.state[STATE_ALIGN] = x;\n\t}\n\n\tget end() {\n\t\treturn this.state[STATE_END];\n\t}\n\n\tprotected set end(x: number) {\n\t\tthis.state[STATE_END] = x;\n\t}\n\n\tget top() {\n\t\treturn Atomics.load(this.state, STATE_TOP);\n\t}\n\n\tprotected set top(x: number) {\n\t\tAtomics.store(this.state, STATE_TOP, x);\n\t}\n\n\tprotected get highWater() {\n\t\treturn Atomics.load(this.state, STATE_HIGH_WATER);\n\t}\n\n\tprotected set highWater(x: number) {\n\t\tAtomics.store(this.state, STATE_HIGH_WATER, x);\n\t}\n\n\t// No live allocations. O(1) (the head of the used list, not a walk like stats()), so it is cheap to poll each\n\t// frame when checking whether a spare buffer is still available.\n\tget isEmpty(): boolean {\n\t\treturn this._used === 0;\n\t}\n\n\tprotected get _free() {\n\t\treturn Atomics.load(this.state, STATE_FREE);\n\t}\n\n\tprotected set _free(block: number) {\n\t\tAtomics.store(this.state, STATE_FREE, block);\n\t}\n\n\tprotected get _used() {\n\t\treturn Atomics.load(this.state, STATE_USED);\n\t}\n\n\tprotected set _used(block: number) {\n\t\tAtomics.store(this.state, STATE_USED, block);\n\t}\n\n\tprotected get doCompact() {\n\t\treturn !!(this.state[STATE_FLAGS] & MASK_COMPACT);\n\t}\n\n\tprotected set doCompact(flag: boolean) {\n\t\tflag\n\t\t\t? (this.state[STATE_FLAGS] |= 1 << (MASK_COMPACT - 1))\n\t\t\t: (this.state[STATE_FLAGS] &= ~MASK_COMPACT);\n\t}\n\n\tprotected get doSplit() {\n\t\treturn !!(this.state[STATE_FLAGS] & MASK_SPLIT);\n\t}\n\n\tprotected set doSplit(flag: boolean) {\n\t\tflag\n\t\t\t? (this.state[STATE_FLAGS] |= 1 << (MASK_SPLIT - 1))\n\t\t\t: (this.state[STATE_FLAGS] &= ~MASK_SPLIT);\n\t}\n\n\tprotected get minSplit() {\n\t\treturn this.state[STATE_MIN_SPLIT];\n\t}\n\n\tprotected set minSplit(x: number) {\n\t\tif(x <= SIZEOF_MEM_BLOCK) {\n\t\t\tthrow new Error(`illegal min split threshold: ${x}, require at least ${\n\t\t\t\tSIZEOF_MEM_BLOCK + 1\n\t\t\t}`);\n\t\t}\n\t\tthis.state[STATE_MIN_SPLIT] = x;\n\t}\n\n\tprotected blockSize(block: number) {\n\t\treturn Atomics.load(this.u32, (block >> 2) + MEM_BLOCK_SIZE);\n\t}\n\n\t/**\n\t * Sets & returns given block size.\n\t *\n\t * @param block -\n\t * @param size -\n\t */\n\tprotected setBlockSize(block: number, size: number) {\n\t\tAtomics.store(this.u32, (block >> 2) + MEM_BLOCK_SIZE, size);\n\t\treturn size;\n\t}\n\n\tprotected blockNext(block: number) {\n\t\treturn Atomics.load(this.u32, (block >> 2) + MEM_BLOCK_NEXT);\n\t}\n\n\t/**\n\t * Sets block next pointer to `next`. Use zero to indicate list end.\n\t *\n\t * @param block -\n\t */\n\tprotected setBlockNext(block: number, next: number) {\n\t\tAtomics.store(this.u32, (block >> 2) + MEM_BLOCK_NEXT, next);\n\t}\n\n\tprotected blockPrev(block: number) {\n\t\treturn Atomics.load(this.u32, (block >> 2) + MEM_BLOCK_PREV);\n\t}\n\n\tprotected setBlockPrev(block: number, prev: number) {\n\t\tAtomics.store(this.u32, (block >> 2) + MEM_BLOCK_PREV, prev);\n\t}\n\n\tprotected blockState(block: number) {\n\t\treturn Atomics.load(this.u32, (block >> 2) + MEM_BLOCK_STATE);\n\t}\n\n\tprotected setBlockState(block: number, state: number) {\n\t\tAtomics.store(this.u32, (block >> 2) + MEM_BLOCK_STATE, state);\n\t}\n\n\t// Prepend a block onto the doubly-linked used list, keeping the back-links free() relies on for O(1) removal.\n\tprotected pushUsed(block: number) {\n\t\tconst head = this._used;\n\t\tthis.setBlockNext(block, head);\n\t\tthis.setBlockPrev(block, 0);\n\t\tthis.setBlockState(block, BLOCK_USED);\n\t\tif(head) {\n\t\t\tthis.setBlockPrev(head, block);\n\t\t}\n\t\tthis._used = block;\n\t}\n\n\t/**\n\t * Initializes block header with given `size` and `next` pointer. Returns `block`.\n\t *\n\t * @param block -\n\t * @param size -\n\t * @param next -\n\t */\n\t// Only used for free-list blocks (split excess); the used list is built via pushUsed which also sets prev/state.\n\tprotected initBlock(block: number, size: number, next: number) {\n\t\tconst idx = block >>> 2;\n\t\tAtomics.store(this.u32, idx + MEM_BLOCK_SIZE, size);\n\t\tAtomics.store(this.u32, idx + MEM_BLOCK_NEXT, next);\n\t\tAtomics.store(this.u32, idx + MEM_BLOCK_STATE, BLOCK_FREE);\n\t\treturn block;\n\t}\n\n\tprotected unlinkBlock(prev: number, block: number) {\n\t\tthis.setBlockNext(prev, this.blockNext(block));\n\t}\n\n\tprotected splitBlock(block: number, blockSize: number, excess: number) {\n\t\tthis.insert(\n\t\t\tthis.initBlock(\n\t\t\t\tblock + this.setBlockSize(block, blockSize),\n\t\t\t\texcess,\n\t\t\t\t0,\n\t\t\t),\n\t\t);\n\t\tthis.doCompact && this.compact();\n\t}\n\n\tprotected initialTop(_align = this.align) {\n\t\treturn (\n\t\t\talign(this.start + SIZEOF_STATE + SIZEOF_MEM_BLOCK, _align) -\n\t\t\tSIZEOF_MEM_BLOCK\n\t\t);\n\t}\n\n\t/**\n\t * Traverses free list and attempts to recursively merge blocks\n\t * occupying consecutive memory regions. Returns true if any blocks\n\t * have been merged. Only called if `compact` option is enabled.\n\t */\n\tprotected compact() {\n\t\tlet block = this._free;\n\t\tlet prev = 0;\n\t\tlet scan = 0;\n\t\tlet scanPrev: number;\n\t\tlet res = false;\n\t\twhile(block) {\n\t\t\tscanPrev = block;\n\t\t\tscan = this.blockNext(block);\n\t\t\twhile(scan && scanPrev + this.blockSize(scanPrev) === scan) {\n\t\t\t\t// console.log(\"merge:\", scan.addr, scan.size);\n\t\t\t\tscanPrev = scan;\n\t\t\t\tscan = this.blockNext(scan);\n\t\t\t}\n\t\t\tif(scanPrev !== block) {\n\t\t\t\tconst newSize = scanPrev - block + this.blockSize(scanPrev);\n\t\t\t\t// console.log(\"merged size:\", newSize);\n\t\t\t\tthis.setBlockSize(block, newSize);\n\t\t\t\tconst next = this.blockNext(scanPrev);\n\t\t\t\tlet tmp = this.blockNext(block);\n\t\t\t\twhile(tmp && tmp !== next) {\n\t\t\t\t\t// console.log(\"release:\", tmp.addr);\n\t\t\t\t\tconst tn = this.blockNext(tmp);\n\t\t\t\t\tthis.setBlockNext(tmp, 0);\n\t\t\t\t\ttmp = tn;\n\t\t\t\t}\n\t\t\t\tthis.setBlockNext(block, next);\n\t\t\t\tres = true;\n\t\t\t}\n\t\t\t// re-adjust top if poss\n\t\t\tif(block + this.blockSize(block) >= this.top) {\n\t\t\t\tthis.top = block;\n\t\t\t\tprev\n\t\t\t\t\t? this.unlinkBlock(prev, block)\n\t\t\t\t\t: (this._free = this.blockNext(block));\n\t\t\t}\n\t\t\tprev = block;\n\t\t\tblock = this.blockNext(block);\n\t\t}\n\t\treturn res;\n\t}\n\n\t/**\n\t * Inserts given block into list of free blocks, sorted by address.\n\t *\n\t * @param block -\n\t */\n\tprotected insert(block: number) {\n\t\t// Address ordering only exists so compact() can merge neighbours; without compaction a prepend keeps free O(1).\n\t\tif(!this.doCompact) {\n\t\t\tthis.setBlockNext(block, this._free);\n\t\t\tthis._free = block;\n\t\t\treturn;\n\t\t}\n\n\t\tlet ptr = this._free;\n\t\tlet prev = 0;\n\t\twhile(ptr) {\n\t\t\tif(block <= ptr) break;\n\t\t\tprev = ptr;\n\t\t\tptr = this.blockNext(ptr);\n\t\t}\n\t\tif(prev) {\n\t\t\tthis.setBlockNext(prev, block);\n\t\t} else {\n\t\t\tthis._free = block;\n\t\t}\n\t\tthis.setBlockNext(block, ptr);\n\t}\n}\n\n/**\n * Returns a block's data address, based on given alignment.\n *\n * @param blockAddress -\n */\nfunction blockDataAddress(blockAddress: number) {\n\treturn blockAddress > 0 ? blockAddress + SIZEOF_MEM_BLOCK : 0;\n}\n\n/**\n * Returns block start address for given data address and alignment.\n *\n * @param dataAddress -\n */\nfunction blockSelfAddress(dataAddress: number) {\n\treturn dataAddress > 0 ? dataAddress - SIZEOF_MEM_BLOCK : 0;\n}\n\nfunction align(addr: number, size: number) {\n\tsize--;\n\treturn addr + size & ~size;\n}\n\n// Bytes reserved before usable heap space (allocator state) and the per-allocation block header. Exported so callers can\n// size an allocation against what a single buffer can actually hold.\n// Byte offset of the first allocation's data in a freshly initialized buffer (default align). The heap's state block is\n// always the first allocation, so a clone can resolve it without guessing.\nconst FIRST_BLOCK_DATA_BYTE_OFFSET = align(SIZEOF_STATE + SIZEOF_MEM_BLOCK, 8);\n\nexport { SIZEOF_STATE, SIZEOF_MEM_BLOCK, FIRST_BLOCK_DATA_BYTE_OFFSET };\n\ntype Type = 'u8' | 'u8c' | 'i8' | 'u16' | 'i16' | 'u32' | 'i32' | 'f32' | 'f64';\n\ninterface MemoryBufferConfig {\n\t/**\n\t * Backing ArrayBuffer (or SharedArrayBuffer). If not given, a new\n\t * one will be created with given `size`.\n\t */\n\tbuf: ArrayBufferLike\n\t/**\n\t * Byte size for newly created ArrayBuffers (if `buf` is not given).\n\t *\n\t * @defaultValue 0x1000 (4KB)\n\t */\n\tsize: number\n\t/**\n\t * Anchor index (byte address) inside the array buffer. The MemPool\n\t * stores its internal state from the given address and heap space\n\t * starts at least 32 bytes later (depending on chosen `align`\n\t * value). Unlike allocator state variables, `start`` cannot be\n\t * saved inside the array buffer itself. If the ArrayBuffer is\n\t * passed to other consumers they must use the same start value.\n\t * MUST be multiple of 4.\n\t *\n\t * @defaultValue 0\n\t */\n\tstart: number\n\t/**\n\t * Byte address (+1) of the end of the memory region managed by the\n\t * {@link MemPool}.\n\t *\n\t * @defaultValue end of the backing ArrayBuffer\n\t */\n\tend: number\n\t/**\n\t * Number of bytes to align memory blocks to. MUST be a power of 2\n\t * and >= 8. Use 16 if the pool is being used for allocating memory\n\t * used in SIMD operations.\n\t *\n\t * @defaultValue 8\n\t */\n\talign: Pow2\n\t/**\n\t * Flag to configure memory block compaction. If true,\n\t * adjoining free blocks (in terms of address space) will be merged\n\t * to minimize fragementation.\n\t *\n\t * @defaultValue true\n\t */\n\tcompact: boolean\n\t/**\n\t * Flag to configure memory block splitting. If true, and when the\n\t * allocator is re-using a previously freed block larger than the\n\t * requested size, the block will be split to minimize wasted/unused\n\t * memory. The splitting behavior can further customized via the\n\t * `minSplit` option.\n\t *\n\t * @defaultValue true\n\t */\n\tsplit: boolean\n\t/**\n\t * Only used if `split` behavior is enabled. Defines min number of\n\t * excess bytes available in a block for memory block splitting to\n\t * occur.\n\t *\n\t * @defaultValue 16, MUST be > 8\n\t */\n\tminSplit: number\n\t/**\n\t * Only needed when sharing the underlying ArrayBuffer. If true, the\n\t * {@link MemPool} constructor will NOT initialize its internal state and\n\t * assume the underlying ArrayBuffer has already been initialized by\n\t * another {@link MemPool} instance. If this option is used, `buf` MUST be\n\t * given.\n\t *\n\t * @defaultValue false\n\t */\n\tskipInitialization: boolean\n}\ninterface MemoryBufferStats {\n\t/**\n\t * Free block stats.\n\t */\n\tfree: { count: number, size: number }\n\t/**\n\t * Used block stats.\n\t */\n\tused: { count: number, size: number }\n\t/**\n\t * Current top address.\n\t */\n\ttop: number\n\t/**\n\t * Bytes available\n\t */\n\tavailable: number\n\t/**\n\t * Total pool size.\n\t */\n\ttotal: number\n}"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"memory-buffer.js","names":["STATE_FREE","STATE_USED","STATE_TOP","STATE_END","STATE_ALIGN","STATE_FLAGS","STATE_MIN_SPLIT","STATE_HIGH_WATER","MASK_COMPACT","MASK_SPLIT","SIZEOF","SIZEOF_STATE","MEM_BLOCK_SIZE","MEM_BLOCK_NEXT","MEM_BLOCK_PREV","MEM_BLOCK_STATE","SIZEOF_MEM_BLOCK","BLOCK_FREE","BLOCK_USED","MemoryBuffer","opts","align","_align","top","resolvedEnd","listStats","count","size","block","free","type","num","fill","addr","typedArray","bytes","paddedSize","end","doCompact","highWater","prev","blockSize","isTop","result","unlock","blockDataAddress","excess","ptrOrArray","blockSelfAddress","dataAddress","lock","next","x","state","head","idx","scan","scanPrev","res","newSize","tmp","tn","ptr","blockAddress","FIRST_BLOCK_DATA_BYTE_OFFSET"],"sources":["../src/memory-buffer.ts"],"sourcesContent":["// Just disabling this rule instead of re-writing for now to keep asa close to original in case we want to pull updates later\n// Doing `block && block.fill(fill)` instead of `if(block) { block.fill(fill); }` is valid code even if it is hard to parse\n/* eslint-disable @typescript-eslint/no-unused-expressions */\n\nimport type { Pow2 } from './interfaces/pow2';\nimport type { TypedArray } from './interfaces/typed-array';\nimport { lock, unlock } from './lock/simple-lock';\nimport { typedArray } from './utils/typedarray';\n\nconst STATE_FREE = 0;\nconst STATE_USED = 1;\nconst STATE_TOP = 2;\nconst STATE_END = 3;\nconst STATE_ALIGN = 4;\nconst STATE_FLAGS = 5;\nconst STATE_MIN_SPLIT = 6;\n// Highest top ever reached. callock can skip re-zeroing it\nconst STATE_HIGH_WATER = 7;\n\nconst MASK_COMPACT = 1;\nconst MASK_SPLIT = 2;\n\nconst SIZEOF = {\n\tu8: 1,\n\tu8c: 1,\n\ti8: 1,\n\tu16: 2,\n\ti16: 2,\n\tu32: 4,\n\ti32: 4,\n\ti64: 8,\n\tu64: 8,\n\tf32: 4,\n\tf64: 8,\n};\n\nconst SIZEOF_STATE = 9 * 4;\n\nconst MEM_BLOCK_SIZE = 0;\nconst MEM_BLOCK_NEXT = 1;\n// The used list is doubly linked so free() can unlink a block in O(1) instead of walking to find its predecessor.\n// MEM_BLOCK_STATE marks live blocks so free() rejects a double-free (which would otherwise splice free-list pointers\n// into the used list) without that walk. Word 3 also pads the header to 16 bytes, keeping data addresses aligned exactly\n// as the original 8-byte header did.\nconst MEM_BLOCK_PREV = 2;\nconst MEM_BLOCK_STATE = 3;\n\nconst SIZEOF_MEM_BLOCK = 4 * 4;\n\nconst BLOCK_FREE = 0;\nconst BLOCK_USED = 1;\n\n// Copied from https://github.com/thi-ng/umbrella/blob/develop/packages/malloc/src/pool.ts\n// Changes: Atomic.load/store on state, simple lock on malloc/free\n// Added bytesFor/lengthOf to be able to grab expected length of a block\nexport default class MemoryBuffer {\n\tbuf: ArrayBufferLike;\n\n\tprotected readonly start: number;\n\tprotected u8: Uint8Array;\n\tprotected u32: Uint32Array;\n\tprotected state: Uint32Array;\n\tprotected lock: Int32Array;\n\t// Whether the most recent malloc came from never-before-allocated (already-zeroed) memory. Per-instance/per-thread and\n\t// read by calloc immediately after malloc under the same call, so no locking is needed.\n\tprivate lastMallocFresh = false;\n\n\tconstructor(opts: Partial<MemoryBufferConfig> = {}) {\n\t\tthis.buf = opts.buf ? opts.buf : new ArrayBuffer(opts.size || 0x1000);\n\t\tthis.start = opts.start != null ? align(Math.max(opts.start, 0), 4) : 0;\n\t\tthis.u8 = new Uint8Array(this.buf);\n\t\tthis.u32 = new Uint32Array(this.buf);\n\t\tthis.state = new Uint32Array(this.buf, this.start, SIZEOF_STATE / 4);\n\t\tthis.lock = new Int32Array(this.buf, this.start + this.state.byteLength - 4, 1);\n\n\t\tif(!opts.skipInitialization) {\n\t\t\tconst _align = opts.align || 8;\n\t\t\tif(_align < 8) {\n\t\t\t\tthrow new Error(`invalid alignment: ${_align}, must be a pow2 and >= 8`);\n\t\t\t}\n\t\t\tconst top = this.initialTop(_align);\n\t\t\tconst resolvedEnd =\n\t\t\t\topts.end != null\n\t\t\t\t\t? Math.min(opts.end, this.buf.byteLength)\n\t\t\t\t\t: this.buf.byteLength;\n\n\t\t\tif(top >= resolvedEnd) {\n\t\t\t\tthrow new Error(\n\t\t\t\t\t`insufficient address range (0x${this.start.toString(\n\t\t\t\t\t\t16,\n\t\t\t\t\t)} - 0x${resolvedEnd.toString(16)})`,\n\t\t\t\t);\n\t\t\t}\n\n\t\t\tthis.align = _align;\n\t\t\tthis.doCompact = opts.compact !== false;\n\t\t\tthis.doSplit = opts.split !== false;\n\t\t\tthis.minSplit = opts.minSplit || SIZEOF_MEM_BLOCK * 2;\n\t\t\tthis.end = resolvedEnd;\n\t\t\tthis.top = top;\n\t\t\tthis.highWater = top;\n\t\t\tthis._free = 0;\n\t\t\tthis._used = 0;\n\t\t}\n\t}\n\n\tstats(): Readonly<MemoryBufferStats> {\n\t\tconst listStats = (block: number) => {\n\t\t\tlet count = 0;\n\t\t\tlet size = 0;\n\t\t\twhile(block) {\n\t\t\t\tcount++;\n\t\t\t\tsize += this.blockSize(block);\n\t\t\t\tblock = this.blockNext(block);\n\n\t\t\t\tif(block > this.end) {\n\t\t\t\t\tconsole.error(`Trying to get stats for block past end of buffer: ${block} > ${this.end}`);\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn { count, size };\n\t\t};\n\t\tconst free = listStats(this._free);\n\t\treturn {\n\t\t\tfree,\n\t\t\tused: listStats(this._used),\n\t\t\ttop: this.top,\n\t\t\tavailable: this.end - this.top + free.size,\n\t\t\ttotal: this.buf.byteLength,\n\t\t};\n\t}\n\n\tcallocAs<T extends Type>(type: T, num: number, fill = 0) {\n\t\tconst block = this.mallocAs(type, num);\n\t\t// Fresh bump memory is already zero from the backing buffer; only fill for a non-zero value or reused (dirty) memory.\n\t\tblock && (fill !== 0 || !this.lastMallocFresh) && block.fill(fill);\n\t\treturn block;\n\t}\n\n\tmallocAs<T extends Type>(type: T, num: number) {\n\t\tconst addr = this.malloc(num * SIZEOF[type]);\n\t\treturn addr ? typedArray(type, this.buf, addr, num) : undefined;\n\t}\n\n\tcalloc(bytes: number, fill = 0) {\n\t\tconst addr = this.malloc(bytes);\n\t\t// Fresh bump memory is already zero from the backing buffer; only fill for a non-zero value or reused (dirty) memory.\n\t\taddr && (fill !== 0 || !this.lastMallocFresh) && this.u8.fill(fill, addr, addr + bytes);\n\t\treturn addr;\n\t}\n\n\tmalloc(bytes: number) {\n\t\tif(bytes <= 0) {\n\t\t\treturn 0;\n\t\t}\n\t\tlock(this.lock);\n\t\tconst paddedSize = align(bytes + SIZEOF_MEM_BLOCK, this.align);\n\t\tconst end = this.end;\n\t\t// Without compaction top never retreats, so bump memory is always pristine and no high-water tracking is needed. With\n\t\t// compaction top can move back below the high-water mark, so a bump there reuses dirty memory and must be zeroed.\n\t\tconst doCompact = this.doCompact;\n\t\tconst highWater = doCompact ? this.highWater : 0;\n\t\tlet top = this.top;\n\t\tlet block = this._free;\n\t\tlet prev = 0;\n\t\twhile(block) {\n\t\t\tconst blockSize = this.blockSize(block);\n\t\t\tconst isTop = block + blockSize >= top;\n\t\t\tif(isTop || blockSize >= paddedSize) {\n\t\t\t\tlet result = this.mallocTop(\n\t\t\t\t\tblock,\n\t\t\t\t\tprev,\n\t\t\t\t\tblockSize,\n\t\t\t\t\tpaddedSize,\n\t\t\t\t\tisTop,\n\t\t\t\t);\n\n\t\t\t\t// A free-list block was previously allocated (below the high-water mark), so its memory is dirty.\n\t\t\t\tthis.lastMallocFresh = result !== 0 && doCompact && block >= highWater;\n\t\t\t\tif(doCompact && this.top > highWater) {\n\t\t\t\t\tthis.highWater = this.top;\n\t\t\t\t}\n\t\t\t\tunlock(this.lock);\n\t\t\t\treturn result;\n\t\t\t}\n\t\t\tprev = block;\n\t\t\tblock = this.blockNext(block);\n\t\t}\n\t\tblock = top;\n\t\ttop = block + paddedSize;\n\t\tif(top <= end) {\n\t\t\tthis.setBlockSize(block, paddedSize);\n\t\t\tthis.pushUsed(block);\n\t\t\tthis.top = top;\n\t\t\t// Bump allocation only ever advances into untouched memory, so it is fresh whenever it is at/above the mark.\n\t\t\tthis.lastMallocFresh = !doCompact || block >= highWater;\n\t\t\tif(doCompact && top > highWater) {\n\t\t\t\tthis.highWater = top;\n\t\t\t}\n\t\t\tlet result = blockDataAddress(block);\n\t\t\tunlock(this.lock);\n\n\t\t\treturn result;\n\t\t}\n\t\tthis.lastMallocFresh = false;\n\t\tunlock(this.lock);\n\t\treturn 0;\n\t}\n\n\tprivate mallocTop(\n\t\tblock: number,\n\t\tprev: number,\n\t\tblockSize: number,\n\t\tpaddedSize: number,\n\t\tisTop: boolean,\n\t) {\n\t\tif(isTop && block + paddedSize > this.end) {\n\t\t\treturn 0;\n\t\t}\n\t\tif(prev) {\n\t\t\tthis.unlinkBlock(prev, block);\n\t\t} else {\n\t\t\tthis._free = this.blockNext(block);\n\t\t}\n\t\tthis.pushUsed(block);\n\t\tif(isTop) {\n\t\t\t// Without splitting, shrinking would expose a new block header inside stale views of the old allocation.\n\t\t\tif(this.doSplit || paddedSize >= blockSize) {\n\t\t\t\tthis.top = block + this.setBlockSize(block, paddedSize);\n\t\t\t}\n\t\t} else if(this.doSplit) {\n\t\t\tconst excess = blockSize - paddedSize;\n\t\t\texcess >= this.minSplit &&\n\t\t\tthis.splitBlock(block, paddedSize, excess);\n\t\t}\n\t\treturn blockDataAddress(block);\n\t}\n\n\tbytesFor(ptrOrArray: number | TypedArray): number | undefined {\n\t\tlet addr: number;\n\t\tif(typeof ptrOrArray !== 'number') {\n\t\t\tif(ptrOrArray.buffer !== this.buf) {\n\t\t\t\treturn undefined;\n\t\t\t}\n\t\t\taddr = ptrOrArray.byteOffset;\n\t\t} else {\n\t\t\taddr = ptrOrArray;\n\t\t}\n\n\t\taddr = blockSelfAddress(addr);\n\t\tlet block = this._used;\n\t\twhile(block) {\n\t\t\tif(block === addr) {\n\t\t\t\treturn this.blockSize(addr) - SIZEOF_MEM_BLOCK;\n\t\t\t}\n\t\t\tblock = this.blockNext(block);\n\t\t}\n\n\t\treturn undefined;\n\t}\n\tlengthOf(ptrOrArray: number | TypedArray): number | undefined {\n\t\tlet bytes = this.bytesFor(ptrOrArray);\n\t\tif(bytes) {\n\t\t\treturn bytes / this.u32.BYTES_PER_ELEMENT;\n\t\t} else {\n\t\t\treturn undefined;\n\t\t}\n\t}\n\tallocationLengthAt(dataAddress: number): number {\n\t\treturn (this.blockSize(blockSelfAddress(dataAddress)) - SIZEOF_MEM_BLOCK) / this.u32.BYTES_PER_ELEMENT;\n\t}\n\n\tfree(ptrOrArray: number | TypedArray) {\n\t\tlet addr: number;\n\t\tif(typeof ptrOrArray !== 'number') {\n\t\t\tif(ptrOrArray.buffer !== this.buf) {\n\t\t\t\treturn false;\n\t\t\t}\n\t\t\taddr = ptrOrArray.byteOffset;\n\t\t} else {\n\t\t\taddr = ptrOrArray;\n\t\t}\n\t\tlock(this.lock);\n\t\taddr = blockSelfAddress(addr);\n\t\t// Reject anything not currently live (double-free or a stray pointer) without walking the used list.\n\t\tif(this.blockState(addr) !== BLOCK_USED) {\n\t\t\tunlock(this.lock);\n\t\t\treturn false;\n\t\t}\n\t\tconst next = this.blockNext(addr);\n\t\tconst prev = this.blockPrev(addr);\n\t\tif(prev) {\n\t\t\tthis.setBlockNext(prev, next);\n\t\t} else {\n\t\t\tthis._used = next;\n\t\t}\n\t\tif(next) {\n\t\t\tthis.setBlockPrev(next, prev);\n\t\t}\n\t\tthis.setBlockState(addr, BLOCK_FREE);\n\t\tthis.insert(addr);\n\t\tthis.doCompact && this.compact();\n\n\t\tunlock(this.lock);\n\t\treturn true;\n\t}\n\n\trelease() {\n\t\tdelete (<any> this).u8;\n\t\tdelete (<any> this).u32;\n\t\tdelete (<any> this).state;\n\t\tdelete (<any> this).buf;\n\t\treturn true;\n\t}\n\n\tprotected get align() {\n\t\treturn <Pow2> this.state[STATE_ALIGN];\n\t}\n\n\tprotected set align(x: Pow2) {\n\t\tthis.state[STATE_ALIGN] = x;\n\t}\n\n\tget end() {\n\t\treturn this.state[STATE_END];\n\t}\n\n\tprotected set end(x: number) {\n\t\tthis.state[STATE_END] = x;\n\t}\n\n\tget top() {\n\t\treturn Atomics.load(this.state, STATE_TOP);\n\t}\n\n\tprotected set top(x: number) {\n\t\tAtomics.store(this.state, STATE_TOP, x);\n\t}\n\n\tprotected get highWater() {\n\t\treturn Atomics.load(this.state, STATE_HIGH_WATER);\n\t}\n\n\tprotected set highWater(x: number) {\n\t\tAtomics.store(this.state, STATE_HIGH_WATER, x);\n\t}\n\n\t// No live allocations. O(1) (the head of the used list, not a walk like stats()), so it is cheap to poll each\n\t// frame when checking whether a spare buffer is still available.\n\tget isEmpty(): boolean {\n\t\treturn this._used === 0;\n\t}\n\n\tprotected get _free() {\n\t\treturn Atomics.load(this.state, STATE_FREE);\n\t}\n\n\tprotected set _free(block: number) {\n\t\tAtomics.store(this.state, STATE_FREE, block);\n\t}\n\n\tprotected get _used() {\n\t\treturn Atomics.load(this.state, STATE_USED);\n\t}\n\n\tprotected set _used(block: number) {\n\t\tAtomics.store(this.state, STATE_USED, block);\n\t}\n\n\tprotected get doCompact() {\n\t\treturn !!(this.state[STATE_FLAGS] & MASK_COMPACT);\n\t}\n\n\tprotected set doCompact(flag: boolean) {\n\t\tflag\n\t\t\t? (this.state[STATE_FLAGS] |= 1 << (MASK_COMPACT - 1))\n\t\t\t: (this.state[STATE_FLAGS] &= ~MASK_COMPACT);\n\t}\n\n\tprotected get doSplit() {\n\t\treturn !!(this.state[STATE_FLAGS] & MASK_SPLIT);\n\t}\n\n\tprotected set doSplit(flag: boolean) {\n\t\tflag\n\t\t\t? (this.state[STATE_FLAGS] |= 1 << (MASK_SPLIT - 1))\n\t\t\t: (this.state[STATE_FLAGS] &= ~MASK_SPLIT);\n\t}\n\n\tprotected get minSplit() {\n\t\treturn this.state[STATE_MIN_SPLIT];\n\t}\n\n\tprotected set minSplit(x: number) {\n\t\tif(x <= SIZEOF_MEM_BLOCK) {\n\t\t\tthrow new Error(`illegal min split threshold: ${x}, require at least ${\n\t\t\t\tSIZEOF_MEM_BLOCK + 1\n\t\t\t}`);\n\t\t}\n\t\tthis.state[STATE_MIN_SPLIT] = x;\n\t}\n\n\tprotected blockSize(block: number) {\n\t\treturn Atomics.load(this.u32, (block >> 2) + MEM_BLOCK_SIZE);\n\t}\n\n\t/**\n\t * Sets & returns given block size.\n\t *\n\t * @param block -\n\t * @param size -\n\t */\n\tprotected setBlockSize(block: number, size: number) {\n\t\tAtomics.store(this.u32, (block >> 2) + MEM_BLOCK_SIZE, size);\n\t\treturn size;\n\t}\n\n\tprotected blockNext(block: number) {\n\t\treturn Atomics.load(this.u32, (block >> 2) + MEM_BLOCK_NEXT);\n\t}\n\n\t/**\n\t * Sets block next pointer to `next`. Use zero to indicate list end.\n\t *\n\t * @param block -\n\t */\n\tprotected setBlockNext(block: number, next: number) {\n\t\tAtomics.store(this.u32, (block >> 2) + MEM_BLOCK_NEXT, next);\n\t}\n\n\tprotected blockPrev(block: number) {\n\t\treturn Atomics.load(this.u32, (block >> 2) + MEM_BLOCK_PREV);\n\t}\n\n\tprotected setBlockPrev(block: number, prev: number) {\n\t\tAtomics.store(this.u32, (block >> 2) + MEM_BLOCK_PREV, prev);\n\t}\n\n\tprotected blockState(block: number) {\n\t\treturn Atomics.load(this.u32, (block >> 2) + MEM_BLOCK_STATE);\n\t}\n\n\tprotected setBlockState(block: number, state: number) {\n\t\tAtomics.store(this.u32, (block >> 2) + MEM_BLOCK_STATE, state);\n\t}\n\n\t// Prepend a block onto the doubly-linked used list, keeping the back-links free() relies on for O(1) removal.\n\tprotected pushUsed(block: number) {\n\t\tconst head = this._used;\n\t\tthis.setBlockNext(block, head);\n\t\tthis.setBlockPrev(block, 0);\n\t\tthis.setBlockState(block, BLOCK_USED);\n\t\tif(head) {\n\t\t\tthis.setBlockPrev(head, block);\n\t\t}\n\t\tthis._used = block;\n\t}\n\n\t/**\n\t * Initializes block header with given `size` and `next` pointer. Returns `block`.\n\t *\n\t * @param block -\n\t * @param size -\n\t * @param next -\n\t */\n\t// Only used for free-list blocks (split excess); the used list is built via pushUsed which also sets prev/state.\n\tprotected initBlock(block: number, size: number, next: number) {\n\t\tconst idx = block >>> 2;\n\t\tAtomics.store(this.u32, idx + MEM_BLOCK_SIZE, size);\n\t\tAtomics.store(this.u32, idx + MEM_BLOCK_NEXT, next);\n\t\tAtomics.store(this.u32, idx + MEM_BLOCK_STATE, BLOCK_FREE);\n\t\treturn block;\n\t}\n\n\tprotected unlinkBlock(prev: number, block: number) {\n\t\tthis.setBlockNext(prev, this.blockNext(block));\n\t}\n\n\tprotected splitBlock(block: number, blockSize: number, excess: number) {\n\t\tthis.insert(\n\t\t\tthis.initBlock(\n\t\t\t\tblock + this.setBlockSize(block, blockSize),\n\t\t\t\texcess,\n\t\t\t\t0,\n\t\t\t),\n\t\t);\n\t\tthis.doCompact && this.compact();\n\t}\n\n\tprotected initialTop(_align = this.align) {\n\t\treturn (\n\t\t\talign(this.start + SIZEOF_STATE + SIZEOF_MEM_BLOCK, _align) -\n\t\t\tSIZEOF_MEM_BLOCK\n\t\t);\n\t}\n\n\t/**\n\t * Traverses free list and attempts to recursively merge blocks\n\t * occupying consecutive memory regions. Returns true if any blocks\n\t * have been merged. Only called if `compact` option is enabled.\n\t */\n\tprotected compact() {\n\t\tlet block = this._free;\n\t\tlet prev = 0;\n\t\tlet scan = 0;\n\t\tlet scanPrev: number;\n\t\tlet res = false;\n\t\twhile(block) {\n\t\t\tscanPrev = block;\n\t\t\tscan = this.blockNext(block);\n\t\t\twhile(scan && scanPrev + this.blockSize(scanPrev) === scan) {\n\t\t\t\t// console.log(\"merge:\", scan.addr, scan.size);\n\t\t\t\tscanPrev = scan;\n\t\t\t\tscan = this.blockNext(scan);\n\t\t\t}\n\t\t\tif(scanPrev !== block) {\n\t\t\t\tconst newSize = scanPrev - block + this.blockSize(scanPrev);\n\t\t\t\t// console.log(\"merged size:\", newSize);\n\t\t\t\tthis.setBlockSize(block, newSize);\n\t\t\t\tconst next = this.blockNext(scanPrev);\n\t\t\t\tlet tmp = this.blockNext(block);\n\t\t\t\twhile(tmp && tmp !== next) {\n\t\t\t\t\t// console.log(\"release:\", tmp.addr);\n\t\t\t\t\tconst tn = this.blockNext(tmp);\n\t\t\t\t\tthis.setBlockNext(tmp, 0);\n\t\t\t\t\ttmp = tn;\n\t\t\t\t}\n\t\t\t\tthis.setBlockNext(block, next);\n\t\t\t\tres = true;\n\t\t\t}\n\t\t\t// re-adjust top if poss\n\t\t\tif(block + this.blockSize(block) >= this.top) {\n\t\t\t\tthis.top = block;\n\t\t\t\tprev\n\t\t\t\t\t? this.unlinkBlock(prev, block)\n\t\t\t\t\t: (this._free = this.blockNext(block));\n\t\t\t}\n\t\t\tprev = block;\n\t\t\tblock = this.blockNext(block);\n\t\t}\n\t\treturn res;\n\t}\n\n\t/**\n\t * Inserts given block into list of free blocks, sorted by address.\n\t *\n\t * @param block -\n\t */\n\tprotected insert(block: number) {\n\t\t// Address ordering only exists so compact() can merge neighbours; without compaction a prepend keeps free O(1).\n\t\tif(!this.doCompact) {\n\t\t\tthis.setBlockNext(block, this._free);\n\t\t\tthis._free = block;\n\t\t\treturn;\n\t\t}\n\n\t\tlet ptr = this._free;\n\t\tlet prev = 0;\n\t\twhile(ptr) {\n\t\t\tif(block <= ptr) {\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tprev = ptr;\n\t\t\tptr = this.blockNext(ptr);\n\t\t}\n\t\tif(prev) {\n\t\t\tthis.setBlockNext(prev, block);\n\t\t} else {\n\t\t\tthis._free = block;\n\t\t}\n\t\tthis.setBlockNext(block, ptr);\n\t}\n}\n\n/**\n * Returns a block's data address, based on given alignment.\n *\n * @param blockAddress -\n */\nfunction blockDataAddress(blockAddress: number) {\n\treturn blockAddress > 0 ? blockAddress + SIZEOF_MEM_BLOCK : 0;\n}\n\n/**\n * Returns block start address for given data address and alignment.\n *\n * @param dataAddress -\n */\nfunction blockSelfAddress(dataAddress: number) {\n\treturn dataAddress > 0 ? dataAddress - SIZEOF_MEM_BLOCK : 0;\n}\n\nfunction align(addr: number, size: number) {\n\tsize--;\n\treturn addr + size & ~size;\n}\n\n// Bytes reserved before usable heap space (allocator state) and the per-allocation block header. Exported so callers can\n// size an allocation against what a single buffer can actually hold.\n// Byte offset of the first allocation's data in a freshly initialized buffer (default align). The heap's state block is\n// always the first allocation, so a clone can resolve it without guessing.\nconst FIRST_BLOCK_DATA_BYTE_OFFSET = align(SIZEOF_STATE + SIZEOF_MEM_BLOCK, 8);\n\nexport { SIZEOF_STATE, SIZEOF_MEM_BLOCK, FIRST_BLOCK_DATA_BYTE_OFFSET };\n\ntype Type = 'u8' | 'u8c' | 'i8' | 'u16' | 'i16' | 'u32' | 'i32' | 'f32' | 'f64';\n\ninterface MemoryBufferConfig {\n\t/**\n\t * Backing ArrayBuffer (or SharedArrayBuffer). If not given, a new\n\t * one will be created with given `size`.\n\t */\n\tbuf: ArrayBufferLike\n\t/**\n\t * Byte size for newly created ArrayBuffers (if `buf` is not given).\n\t *\n\t * @defaultValue 0x1000 (4KB)\n\t */\n\tsize: number\n\t/**\n\t * Anchor index (byte address) inside the array buffer. The MemPool\n\t * stores its internal state from the given address and heap space\n\t * starts at least 32 bytes later (depending on chosen `align`\n\t * value). Unlike allocator state variables, `start`` cannot be\n\t * saved inside the array buffer itself. If the ArrayBuffer is\n\t * passed to other consumers they must use the same start value.\n\t * MUST be multiple of 4.\n\t *\n\t * @defaultValue 0\n\t */\n\tstart: number\n\t/**\n\t * Byte address (+1) of the end of the memory region managed by the\n\t * {@link MemPool}.\n\t *\n\t * @defaultValue end of the backing ArrayBuffer\n\t */\n\tend: number\n\t/**\n\t * Number of bytes to align memory blocks to. MUST be a power of 2\n\t * and >= 8. Use 16 if the pool is being used for allocating memory\n\t * used in SIMD operations.\n\t *\n\t * @defaultValue 8\n\t */\n\talign: Pow2\n\t/**\n\t * Flag to configure memory block compaction. If true,\n\t * adjoining free blocks (in terms of address space) will be merged\n\t * to minimize fragementation.\n\t *\n\t * @defaultValue true\n\t */\n\tcompact: boolean\n\t/**\n\t * Flag to configure memory block splitting. If true, and when the\n\t * allocator is re-using a previously freed block larger than the\n\t * requested size, the block will be split to minimize wasted/unused\n\t * memory. The splitting behavior can further customized via the\n\t * `minSplit` option.\n\t *\n\t * @defaultValue true\n\t */\n\tsplit: boolean\n\t/**\n\t * Only used if `split` behavior is enabled. Defines min number of\n\t * excess bytes available in a block for memory block splitting to\n\t * occur.\n\t *\n\t * @defaultValue 16, MUST be > 8\n\t */\n\tminSplit: number\n\t/**\n\t * Only needed when sharing the underlying ArrayBuffer. If true, the\n\t * {@link MemPool} constructor will NOT initialize its internal state and\n\t * assume the underlying ArrayBuffer has already been initialized by\n\t * another {@link MemPool} instance. If this option is used, `buf` MUST be\n\t * given.\n\t *\n\t * @defaultValue false\n\t */\n\tskipInitialization: boolean\n}\ninterface MemoryBufferStats {\n\t/**\n\t * Free block stats.\n\t */\n\tfree: { count: number, size: number }\n\t/**\n\t * Used block stats.\n\t */\n\tused: { count: number, size: number }\n\t/**\n\t * Current top address.\n\t */\n\ttop: number\n\t/**\n\t * Bytes available\n\t */\n\tavailable: number\n\t/**\n\t * Total pool size.\n\t */\n\ttotal: number\n}"],"mappings":"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"}
|
package/dist/memory-heap.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"memory-heap.js","names":[],"sources":["../src/memory-heap.ts"],"sourcesContent":["import AllocatedMemory, { type AllocatedMemoryPointer, type SharedAllocatedMemory } from './allocated-memory';\nimport { MAX_BYTE_OFFSET_LENGTH, positionBitsForBufferSize } from './utils/pointer';\nimport MemoryBuffer, { FIRST_BLOCK_DATA_BYTE_OFFSET, SIZEOF_MEM_BLOCK, SIZEOF_STATE } from './memory-buffer';\n\nconst DEFAULT_BUFFER_SIZE = MAX_BYTE_OFFSET_LENGTH;\nconst MAX_BUFFER_SIZE = Math.pow(2, 31);\nconst BUFFER_SIZE_INDEX = 0;\nconst BUFFER_COUNT_INDEX = 1;\nconst BUFFER_AUTO_GROW_INDEX = 2;\nconst BUFFER_POSITION_BITS_INDEX = 3;\nconst HEAP_STATE_COUNT = 4;\nexport default class MemoryHeap {\n\tbuffers: Array<MemoryBuffer>;\n\tprivate onGrowBufferHandlers: Array<OnGrowBuffer> = [];\n\tisClone: boolean;\n\tprivate memory: AllocatedMemory;\n\n\t// Per-heap (per-thread) hint of the last buffer we allocated into\n\tprivate allocCursor = 0;\n\n\tpositionBits: number;\n\t// Max buffer position (count) and max byte offset the split can address\n\tget maxBuffers() {\n\t\treturn Math.pow(2, this.positionBits);\n\t}\n\n\tget bufferSize() {\n\t\treturn this.memory.data[BUFFER_SIZE_INDEX];\n\t}\n\n\t// Largest u32 count a single fresh buffer can hand out in one allocation: the buffer minus the allocator state and the\n\t// block header. A structure that grows in segments must keep each segment within this or the allocation can never fit.\n\tget maxAllocationLength() {\n\t\treturn Math.floor((this.bufferSize - SIZEOF_STATE - SIZEOF_MEM_BLOCK) / Uint32Array.BYTES_PER_ELEMENT);\n\t}\n\n\tconstructor(config?: MemoryHeapConfig | MemoryHeapMemory) {\n\t\tif(config && 'buffers' in config) {\n\t\t\tthis.buffers = config.buffers.map(buffer => {\n\t\t\t\treturn new MemoryBuffer({\n\t\t\t\t\tbuf: buffer,\n\t\t\t\t\tskipInitialization: true,\n\t\t\t\t});\n\t\t\t});\n\n\t\t\t// The heap state block is always the first allocation, so its data sits at this fixed, header-derived offset.\n\t\t\tthis.memory = new AllocatedMemory(this, {\n\t\t\t\tbufferPosition: 0,\n\t\t\t\tbufferByteOffset: FIRST_BLOCK_DATA_BYTE_OFFSET,\n\t\t\t} satisfies AllocatedMemoryPointer);\n\t\t\tthis.positionBits = this.memory.data[BUFFER_POSITION_BITS_INDEX];\n\t\t\tthis.isClone = true;\n\t\t} else {\n\t\t\tif(!('SharedArrayBuffer' in globalThis)) {\n\t\t\t\tconsole.warn('SharedArrayBuffer is not working: falling back to ArrayBuffer');\n\t\t\t}\n\n\t\t\tconst bufferSize = config?.bufferSize ?? DEFAULT_BUFFER_SIZE;\n\t\t\tif(bufferSize > MAX_BUFFER_SIZE) {\n\t\t\t\tthrow new Error(`Buffer size ${bufferSize} is greater than max ${MAX_BUFFER_SIZE} that we can reference with pointers`);\n\t\t\t}\n\t\t\tthis.positionBits = positionBitsForBufferSize(bufferSize);\n\n\t\t\tlet startBuffer = this.createBuffer(bufferSize);\n\t\t\tthis.buffers = [\n\t\t\t\tstartBuffer,\n\t\t\t];\n\t\t\tconst data = startBuffer.callocAs('u32', HEAP_STATE_COUNT);\n\t\t\tif(data) {\n\t\t\t\tthis.memory = new AllocatedMemory(this, {\n\t\t\t\t\tbufferPosition: 0,\n\t\t\t\t\tbufferByteOffset: data.byteOffset,\n\t\t\t\t});\n\t\t\t} else {\n\t\t\t\tthrow new Error('Failed to initialize first byte from buffer');\n\t\t\t}\n\t\t\tthis.memory.data[BUFFER_SIZE_INDEX] = bufferSize;\n\t\t\tthis.memory.data[BUFFER_AUTO_GROW_INDEX] = config?.autoGrowSize ?? 100;\n\t\t\tthis.memory.data[BUFFER_POSITION_BITS_INDEX] = this.positionBits;\n\t\t\tthis.isClone = false;\n\n\t\t\tfor(let i = 1; i < (config?.initialBuffers ?? 1); i++) {\n\t\t\t\tthis.buffers.push(this.createBuffer(bufferSize));\n\t\t\t}\n\t\t\tthis.memory.data[BUFFER_COUNT_INDEX] = this.buffers.length;\n\t\t}\n\t}\n\n\taddSharedBuffer(data: GrowBufferData) {\n\t\tthis.buffers[data.bufferPosition] = new MemoryBuffer({\n\t\t\tbuf: data.buffer,\n\t\t\tskipInitialization: true,\n\t\t});\n\t}\n\n\tprivate growBuffer() {\n\t\tconst buffer = this.createBuffer();\n\t\tlet nextBufferPosition = Atomics.add(this.memory.data, BUFFER_COUNT_INDEX, 1);\n\t\t// Setting index set by internal Atomic count so we can create new buffers from multiple threads and keep position consistent\n\t\tthis.buffers[nextBufferPosition] = buffer;\n\t\tthis.onGrowBufferHandlers.forEach(handler => handler({\n\t\t\tbufferPosition: nextBufferPosition,\n\t\t\tbuffer: buffer.buf as SharedArrayBuffer,\n\t\t}));\n\n\t\treturn buffer;\n\t}\n\tprivate createBuffer(bufferSize?: number): MemoryBuffer {\n\t\tconst usedBufferSize = bufferSize ?? this.bufferSize;\n\t\tlet buf: ArrayBuffer | SharedArrayBuffer;\n\t\tif('SharedArrayBuffer' in globalThis) {\n\t\t\tbuf = new SharedArrayBuffer(usedBufferSize);\n\t\t} else {\n\t\t\tbuf = new ArrayBuffer(usedBufferSize);\n\t\t}\n\n\t\treturn new MemoryBuffer({\n\t\t\tbuf,\n\n\t\t\t// We can't use this unless we can 100% guarantee that every thread will stop using memory the instant it is freed\n\t\t\t// ex: Allocate 16 bytes. Thread A frees that allocation and then allocates 12 bytes and 4 bytes, but Thread B is mid-execution\n\t\t\t// on the old allocation can changes the internal state of the 4-byte allocation breaking everything\n\t\t\t// After the internal state is wrong MemoryBuffer will loose track of which blocks are where and how big they are\n\t\t\tcompact: false,\n\t\t\tsplit: false,\n\t\t});\n\t}\n\n\taddOnGrowBufferHandlers(handler: OnGrowBuffer) {\n\t\tthis.onGrowBufferHandlers.push(handler);\n\t}\n\n\t// Guarantees at least one completely-empty buffer exists and (via the grow handlers) has been fanned out to every\n\t// thread. Call this on the owning thread before dispatching work that allocates on another thread (a worker): that\n\t// allocation then lands in a buffer everyone already holds instead of forcing growBuffer on the worker, which would\n\t// create a buffer only the worker can see and leave every other thread unable to resolve pointers into it. Returns\n\t// true if it had to grow one.\n\tensureSpareBuffer(): boolean {\n\t\tfor(let i = 0; i < this.buffers.length; i++) {\n\t\t\tconst buffer = this.buffers[i];\n\t\t\tif(buffer && buffer.isEmpty) {\n\t\t\t\treturn false;\n\t\t\t}\n\t\t}\n\n\t\tthis.growBuffer();\n\t\treturn true;\n\t}\n\n\tallocUI32(count: number): AllocatedMemory {\n\t\tcount = Math.ceil(count);\n\t\tconst bufferCount = this.buffers.length;\n\t\tfor(let n = 0; n < bufferCount; n++) {\n\t\t\tconst i = (this.allocCursor + n) % bufferCount;\n\t\t\tconst buffer = this.buffers[i];\n\t\t\t// Should just mean we haven't synced this buffer from another thread yet\n\t\t\tif(!buffer) {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\t// Should be fine to initialize all values as 0s since unsigned/signed ints and floats all store 0 as all 0s\n\t\t\tconst data = buffer.callocAs('u32', count);\n\t\t\tif(data) {\n\t\t\t\tthis.allocCursor = i;\n\n\t\t\t\t// Auto grow when nearly full when we need buffer to already be sync'd between threads BEFORE we try to use it\n\t\t\t\tif(\n\t\t\t\t\ti === (this.buffers.length - 1)\n\t\t\t\t\t&& Atomics.load(this.memory.data, BUFFER_COUNT_INDEX) === this.buffers.length\n\t\t\t\t\t&& this.memory.data[BUFFER_AUTO_GROW_INDEX] < 100\n\t\t\t\t\t&& this.memory.data[BUFFER_AUTO_GROW_INDEX] > 0\n\t\t\t\t) {\n\t\t\t\t\tconst percentFull = buffer.top / buffer.end;\n\t\t\t\t\tif(percentFull > (this.memory.data[BUFFER_AUTO_GROW_INDEX] / 100)) {\n\t\t\t\t\t\tthis.growBuffer();\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\treturn new AllocatedMemory(this, {\n\t\t\t\t\tdata,\n\t\t\t\t\tbuffer,\n\t\t\t\t});\n\t\t\t}\n\t\t}\n\n\t\tif(this.buffers.length >= this.maxBuffers) {\n\t\t\tthrow new Error(`Can't initialize a new buffer since it would have a position greater than the max of ${this.maxBuffers}`);\n\t\t}\n\n\t\t// If we get here we need to grow another buffer to continue allocating new memory\n\t\tlet buffer = this.growBuffer();\n\t\tconst data = buffer.callocAs('u32', count);\n\t\tif(data) {\n\t\t\tthis.allocCursor = this.buffers.indexOf(buffer);\n\t\t\treturn new AllocatedMemory(this, {\n\t\t\t\tdata,\n\t\t\t\tbuffer,\n\t\t\t});\n\t\t} else {\n\t\t\tthrow new Error(`Unable to allocate ${count} numbers even after adding a new buffer`);\n\t\t}\n\t}\n\n\tgetSharedAlloc(shared: SharedAllocatedMemory): AllocatedMemory | undefined {\n\t\t// Should just mean it hasn't synced to this thread yet\n\t\tif(this.buffers[shared.bufferPosition] === undefined) {\n\t\t\treturn undefined;\n\t\t}\n\n\t\treturn new AllocatedMemory(this, shared);\n\t}\n\n\tget currentUsed() {\n\t\treturn this.totalAllocated - this.buffers.reduce((total, memPool) => total + memPool.stats().available, 0);\n\t}\n\tget totalAllocated() {\n\t\treturn this.buffers[0].buf.byteLength * this.buffers.length;\n\t}\n\n\tgetSharedMemory(): MemoryHeapMemory {\n\t\treturn {\n\t\t\tbuffers: this.buffers.map(buffer => buffer.buf as SharedArrayBuffer),\n\t\t};\n\t}\n}\n\ntype OnGrowBuffer = (newBuffer: GrowBufferData) => void;\ninterface GrowBufferData {\n\tbufferPosition: number\n\tbuffer: SharedArrayBuffer\n}\n\ninterface MemoryHeapConfig {\n\tbufferSize?: number\n\tinitialBuffers?: number\n\tautoGrowSize?: number\n}\ninterface MemoryHeapMemory {\n\tbuffers: Array<SharedArrayBuffer>\n}\n\nexport type { MemoryHeapConfig, MemoryHeapMemory, GrowBufferData };\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"memory-heap.js","names":["AllocatedMemory","MemoryBuffer","DEFAULT_BUFFER_SIZE","MAX_BYTE_OFFSET_LENGTH","MAX_BUFFER_SIZE","BUFFER_SIZE_INDEX","BUFFER_COUNT_INDEX","BUFFER_AUTO_GROW_INDEX","BUFFER_POSITION_BITS_INDEX","HEAP_STATE_COUNT","MemoryHeap","config","buffer","FIRST_BLOCK_DATA_BYTE_OFFSET","bufferSize","positionBitsForBufferSize","startBuffer","data","i","nextBufferPosition","handler","usedBufferSize","buf","count","bufferCount","n","shared","total","memPool"],"sources":["../src/memory-heap.ts"],"sourcesContent":["import AllocatedMemory, { type AllocatedMemoryPointer, type SharedAllocatedMemory } from './allocated-memory';\nimport { MAX_BYTE_OFFSET_LENGTH, positionBitsForBufferSize } from './utils/pointer';\nimport MemoryBuffer, { FIRST_BLOCK_DATA_BYTE_OFFSET, SIZEOF_MEM_BLOCK, SIZEOF_STATE } from './memory-buffer';\n\nconst DEFAULT_BUFFER_SIZE = MAX_BYTE_OFFSET_LENGTH;\nconst MAX_BUFFER_SIZE = Math.pow(2, 31);\nconst BUFFER_SIZE_INDEX = 0;\nconst BUFFER_COUNT_INDEX = 1;\nconst BUFFER_AUTO_GROW_INDEX = 2;\nconst BUFFER_POSITION_BITS_INDEX = 3;\nconst HEAP_STATE_COUNT = 4;\nexport default class MemoryHeap {\n\tbuffers: Array<MemoryBuffer>;\n\tprivate onGrowBufferHandlers: Array<OnGrowBuffer> = [];\n\tisClone: boolean;\n\tprivate memory: AllocatedMemory;\n\n\t// Per-heap (per-thread) hint of the last buffer we allocated into\n\tprivate allocCursor = 0;\n\n\tpositionBits: number;\n\t// Max buffer position (count) and max byte offset the split can address\n\tget maxBuffers() {\n\t\treturn Math.pow(2, this.positionBits);\n\t}\n\n\tget bufferSize() {\n\t\treturn this.memory.data[BUFFER_SIZE_INDEX];\n\t}\n\n\t// Largest u32 count a single fresh buffer can hand out in one allocation: the buffer minus the allocator state and the\n\t// block header. A structure that grows in segments must keep each segment within this or the allocation can never fit.\n\tget maxAllocationLength() {\n\t\treturn Math.floor((this.bufferSize - SIZEOF_STATE - SIZEOF_MEM_BLOCK) / Uint32Array.BYTES_PER_ELEMENT);\n\t}\n\n\tconstructor(config?: MemoryHeapConfig | MemoryHeapMemory) {\n\t\tif(config && 'buffers' in config) {\n\t\t\tthis.buffers = config.buffers.map(buffer => {\n\t\t\t\treturn new MemoryBuffer({\n\t\t\t\t\tbuf: buffer,\n\t\t\t\t\tskipInitialization: true,\n\t\t\t\t});\n\t\t\t});\n\n\t\t\t// The heap state block is always the first allocation, so its data sits at this fixed, header-derived offset.\n\t\t\tthis.memory = new AllocatedMemory(this, {\n\t\t\t\tbufferPosition: 0,\n\t\t\t\tbufferByteOffset: FIRST_BLOCK_DATA_BYTE_OFFSET,\n\t\t\t} satisfies AllocatedMemoryPointer);\n\t\t\tthis.positionBits = this.memory.data[BUFFER_POSITION_BITS_INDEX];\n\t\t\tthis.isClone = true;\n\t\t} else {\n\t\t\tif(!('SharedArrayBuffer' in globalThis)) {\n\t\t\t\tconsole.warn('SharedArrayBuffer is not working: falling back to ArrayBuffer');\n\t\t\t}\n\n\t\t\tconst bufferSize = config?.bufferSize ?? DEFAULT_BUFFER_SIZE;\n\t\t\tif(bufferSize > MAX_BUFFER_SIZE) {\n\t\t\t\tthrow new Error(`Buffer size ${bufferSize} is greater than max ${MAX_BUFFER_SIZE} that we can reference with pointers`);\n\t\t\t}\n\t\t\tthis.positionBits = positionBitsForBufferSize(bufferSize);\n\n\t\t\tlet startBuffer = this.createBuffer(bufferSize);\n\t\t\tthis.buffers = [\n\t\t\t\tstartBuffer,\n\t\t\t];\n\t\t\tconst data = startBuffer.callocAs('u32', HEAP_STATE_COUNT);\n\t\t\tif(data) {\n\t\t\t\tthis.memory = new AllocatedMemory(this, {\n\t\t\t\t\tbufferPosition: 0,\n\t\t\t\t\tbufferByteOffset: data.byteOffset,\n\t\t\t\t});\n\t\t\t} else {\n\t\t\t\tthrow new Error('Failed to initialize first byte from buffer');\n\t\t\t}\n\t\t\tthis.memory.data[BUFFER_SIZE_INDEX] = bufferSize;\n\t\t\tthis.memory.data[BUFFER_AUTO_GROW_INDEX] = config?.autoGrowSize ?? 100;\n\t\t\tthis.memory.data[BUFFER_POSITION_BITS_INDEX] = this.positionBits;\n\t\t\tthis.isClone = false;\n\n\t\t\tfor(let i = 1; i < (config?.initialBuffers ?? 1); i++) {\n\t\t\t\tthis.buffers.push(this.createBuffer(bufferSize));\n\t\t\t}\n\t\t\tthis.memory.data[BUFFER_COUNT_INDEX] = this.buffers.length;\n\t\t}\n\t}\n\n\taddSharedBuffer(data: GrowBufferData) {\n\t\tthis.buffers[data.bufferPosition] = new MemoryBuffer({\n\t\t\tbuf: data.buffer,\n\t\t\tskipInitialization: true,\n\t\t});\n\t}\n\n\tprivate growBuffer() {\n\t\tconst buffer = this.createBuffer();\n\t\tlet nextBufferPosition = Atomics.add(this.memory.data, BUFFER_COUNT_INDEX, 1);\n\t\t// Setting index set by internal Atomic count so we can create new buffers from multiple threads and keep position consistent\n\t\tthis.buffers[nextBufferPosition] = buffer;\n\t\tthis.onGrowBufferHandlers.forEach(handler => handler({\n\t\t\tbufferPosition: nextBufferPosition,\n\t\t\tbuffer: buffer.buf as SharedArrayBuffer,\n\t\t}));\n\n\t\treturn buffer;\n\t}\n\tprivate createBuffer(bufferSize?: number): MemoryBuffer {\n\t\tconst usedBufferSize = bufferSize ?? this.bufferSize;\n\t\tlet buf: ArrayBuffer | SharedArrayBuffer;\n\t\tif('SharedArrayBuffer' in globalThis) {\n\t\t\tbuf = new SharedArrayBuffer(usedBufferSize);\n\t\t} else {\n\t\t\tbuf = new ArrayBuffer(usedBufferSize);\n\t\t}\n\n\t\treturn new MemoryBuffer({\n\t\t\tbuf,\n\n\t\t\t// We can't use this unless we can 100% guarantee that every thread will stop using memory the instant it is freed\n\t\t\t// ex: Allocate 16 bytes. Thread A frees that allocation and then allocates 12 bytes and 4 bytes, but Thread B is mid-execution\n\t\t\t// on the old allocation can changes the internal state of the 4-byte allocation breaking everything\n\t\t\t// After the internal state is wrong MemoryBuffer will loose track of which blocks are where and how big they are\n\t\t\tcompact: false,\n\t\t\tsplit: false,\n\t\t});\n\t}\n\n\taddOnGrowBufferHandlers(handler: OnGrowBuffer) {\n\t\tthis.onGrowBufferHandlers.push(handler);\n\t}\n\n\t// Guarantees at least one completely-empty buffer exists and (via the grow handlers) has been fanned out to every\n\t// thread. Call this on the owning thread before dispatching work that allocates on another thread (a worker): that\n\t// allocation then lands in a buffer everyone already holds instead of forcing growBuffer on the worker, which would\n\t// create a buffer only the worker can see and leave every other thread unable to resolve pointers into it. Returns\n\t// true if it had to grow one.\n\tensureSpareBuffer(): boolean {\n\t\tfor(let i = 0; i < this.buffers.length; i++) {\n\t\t\tconst buffer = this.buffers[i];\n\t\t\tif(buffer && buffer.isEmpty) {\n\t\t\t\treturn false;\n\t\t\t}\n\t\t}\n\n\t\tthis.growBuffer();\n\t\treturn true;\n\t}\n\n\tallocUI32(count: number): AllocatedMemory {\n\t\tcount = Math.ceil(count);\n\t\tconst bufferCount = this.buffers.length;\n\t\tfor(let n = 0; n < bufferCount; n++) {\n\t\t\tconst i = (this.allocCursor + n) % bufferCount;\n\t\t\tconst buffer = this.buffers[i];\n\t\t\t// Should just mean we haven't synced this buffer from another thread yet\n\t\t\tif(!buffer) {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\t// Should be fine to initialize all values as 0s since unsigned/signed ints and floats all store 0 as all 0s\n\t\t\tconst data = buffer.callocAs('u32', count);\n\t\t\tif(data) {\n\t\t\t\tthis.allocCursor = i;\n\n\t\t\t\t// Auto grow when nearly full when we need buffer to already be sync'd between threads BEFORE we try to use it\n\t\t\t\tif(\n\t\t\t\t\ti === (this.buffers.length - 1)\n\t\t\t\t\t&& Atomics.load(this.memory.data, BUFFER_COUNT_INDEX) === this.buffers.length\n\t\t\t\t\t&& this.memory.data[BUFFER_AUTO_GROW_INDEX] < 100\n\t\t\t\t\t&& this.memory.data[BUFFER_AUTO_GROW_INDEX] > 0\n\t\t\t\t) {\n\t\t\t\t\tconst percentFull = buffer.top / buffer.end;\n\t\t\t\t\tif(percentFull > (this.memory.data[BUFFER_AUTO_GROW_INDEX] / 100)) {\n\t\t\t\t\t\tthis.growBuffer();\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\treturn new AllocatedMemory(this, {\n\t\t\t\t\tdata,\n\t\t\t\t\tbuffer,\n\t\t\t\t});\n\t\t\t}\n\t\t}\n\n\t\tif(this.buffers.length >= this.maxBuffers) {\n\t\t\tthrow new Error(`Can't initialize a new buffer since it would have a position greater than the max of ${this.maxBuffers}`);\n\t\t}\n\n\t\t// If we get here we need to grow another buffer to continue allocating new memory\n\t\tlet buffer = this.growBuffer();\n\t\tconst data = buffer.callocAs('u32', count);\n\t\tif(data) {\n\t\t\tthis.allocCursor = this.buffers.indexOf(buffer);\n\t\t\treturn new AllocatedMemory(this, {\n\t\t\t\tdata,\n\t\t\t\tbuffer,\n\t\t\t});\n\t\t} else {\n\t\t\tthrow new Error(`Unable to allocate ${count} numbers even after adding a new buffer`);\n\t\t}\n\t}\n\n\tgetSharedAlloc(shared: SharedAllocatedMemory): AllocatedMemory | undefined {\n\t\t// Should just mean it hasn't synced to this thread yet\n\t\tif(this.buffers[shared.bufferPosition] === undefined) {\n\t\t\treturn undefined;\n\t\t}\n\n\t\treturn new AllocatedMemory(this, shared);\n\t}\n\n\tget currentUsed() {\n\t\treturn this.totalAllocated - this.buffers.reduce((total, memPool) => total + memPool.stats().available, 0);\n\t}\n\tget totalAllocated() {\n\t\treturn this.buffers[0].buf.byteLength * this.buffers.length;\n\t}\n\n\tgetSharedMemory(): MemoryHeapMemory {\n\t\treturn {\n\t\t\tbuffers: this.buffers.map(buffer => buffer.buf as SharedArrayBuffer),\n\t\t};\n\t}\n}\n\ntype OnGrowBuffer = (newBuffer: GrowBufferData) => void;\ninterface GrowBufferData {\n\tbufferPosition: number\n\tbuffer: SharedArrayBuffer\n}\n\ninterface MemoryHeapConfig {\n\tbufferSize?: number\n\tinitialBuffers?: number\n\tautoGrowSize?: number\n}\ninterface MemoryHeapMemory {\n\tbuffers: Array<SharedArrayBuffer>\n}\n\nexport type { MemoryHeapConfig, MemoryHeapMemory, GrowBufferData };\n"],"mappings":";;;;AAIA,IAAME,IAAsBC,GACtBC,IAA2B,KAAG,IAC9BC,IAAoB,GACpBC,IAAqB,GACrBC,IAAyB,GACzBC,IAA6B,GAC7BC,IAAmB,GACJC,IAArB,MAAgC;CAC/B;CACA,uBAAoD,CAAC;CACrD;CACA;CAGA,cAAsB;CAEtB;CAEA,IAAI,aAAa;EAChB,OAAgB,KAAG,KAAK;CACzB;CAEA,IAAI,aAAa;EAChB,OAAO,KAAK,OAAO,KAAKL;CACzB;CAIA,IAAI,sBAAsB;EACzB,OAAO,KAAK,OAAO,KAAK,aAAA,KAAA,MAAgD,YAAY,iBAAiB;CACtG;CAEA,YAAY,GAA8C;EACzD,IAAGM,KAAU,aAAaA,GAczB,AAbA,KAAK,UAAUA,EAAO,QAAQ,KAAI,MAC1B,IAAIV,EAAa;GACvB,KAAKW;GACL,oBAAoB;EACrB,CAAC,CACD,GAGD,KAAK,SAAS,IAAIZ,EAAgB,MAAM;GACvC,gBAAgB;GAChB,kBAAkBa;EACnB,CAAkC,GAClC,KAAK,eAAe,KAAK,OAAO,KAAKL,IACrC,KAAK,UAAU;OACT;GACN,AAAK,uBAAuB,cAC3B,QAAQ,KAAK,+DAA+D;GAG7E,IAAMM,IAAaH,GAAQ,cAAcT;GACzC,IAAGY,IAAaV,GACf,MAAU,MAAM,eAAeU,EAAW,uBAAuBV,EAAgB,qCAAqC;GAEvH,KAAK,eAAeW,EAA0BD,CAAU;GAExD,IAAIE,IAAc,KAAK,aAAaF,CAAU;GAC9C,KAAK,UAAU,CACdE,CACD;GACA,IAAMC,IAAOD,EAAY,SAAS,OAAOP,CAAgB;GACzD,IAAGQ,GACF,KAAK,SAAS,IAAIjB,EAAgB,MAAM;IACvC,gBAAgB;IAChB,kBAAkBiB,EAAK;GACxB,CAAC;QAED,MAAU,MAAM,6CAA6C;GAK9D,AAHA,KAAK,OAAO,KAAKZ,KAAqBS,GACtC,KAAK,OAAO,KAAKP,KAA0BI,GAAQ,gBAAgB,KACnE,KAAK,OAAO,KAAKH,KAA8B,KAAK,cACpD,KAAK,UAAU;GAEf,KAAI,IAAIU,IAAI,GAAGA,KAAKP,GAAQ,kBAAkB,IAAI,KACjD,KAAK,QAAQ,KAAK,KAAK,aAAaG,CAAU,CAAC;GAEhD,KAAK,OAAO,KAAKR,KAAsB,KAAK,QAAQ;EACrD;CACD;CAEA,gBAAgB,GAAsB;EACrC,KAAK,QAAQW,EAAK,kBAAkB,IAAIhB,EAAa;GACpD,KAAKgB,EAAK;GACV,oBAAoB;EACrB,CAAC;CACF;CAEA,aAAqB;EACpB,IAAML,IAAS,KAAK,aAAa,GAC7BO,IAAqB,QAAQ,IAAI,KAAK,OAAO,MAAMb,GAAoB,CAAC;EAQ5E,OANA,KAAK,QAAQa,KAAsBP,GACnC,KAAK,qBAAqB,SAAQ,MAAWQ,EAAQ;GACpD,gBAAgBD;GAChB,QAAQP,EAAO;EAChB,CAAC,CAAC,GAEKA;CACR;CACA,aAAqB,GAAmC;EACvD,IAAMS,IAAiBP,KAAc,KAAK,YACtCQ;EAOJ,OANA,AAGCA,IAHE,uBAAuB,aACnB,IAAI,kBAAkBD,CAAc,IAEpC,IAAI,YAAYA,CAAc,GAG9B,IAAIpB,EAAa;GACvB;GAMA,SAAS;GACT,OAAO;EACR,CAAC;CACF;CAEA,wBAAwB,GAAuB;EAC9C,KAAK,qBAAqB,KAAKmB,CAAO;CACvC;CAOA,oBAA6B;EAC5B,KAAI,IAAIF,IAAI,GAAGA,IAAI,KAAK,QAAQ,QAAQ,KAAK;GAC5C,IAAMN,IAAS,KAAK,QAAQM;GAC5B,IAAGN,KAAUA,EAAO,SACnB,OAAO;EAET;EAGA,OADA,KAAK,WAAW,GACT;CACR;CAEA,UAAU,GAAgC;EACzC,IAAQ,KAAK,KAAKW,CAAK;EACvB,IAAMC,IAAc,KAAK,QAAQ;EACjC,KAAI,IAAIC,IAAI,GAAGA,IAAID,GAAa,KAAK;GACpC,IAAM,KAAK,KAAK,cAAcC,KAAKD,GAC7BZ,IAAS,KAAK,QAAQ;GAE5B,IAAG,CAACA,GACH;GAID,IAAMK,IAAOL,EAAO,SAAS,OAAOW,CAAK;GACzC,IAAGN,GAgBF,OAfA,KAAK,cAAc,GAIlB,MAAO,KAAK,QAAQ,SAAS,KAC1B,QAAQ,KAAK,KAAK,OAAO,MAAMX,CAAkB,MAAM,KAAK,QAAQ,UACpE,KAAK,OAAO,KAAKC,KAA0B,OAC3C,KAAK,OAAO,KAAKA,KAA0B,KAE1BK,EAAO,MAAMA,EAAO,MACtB,KAAK,OAAO,KAAKL,KAA0B,OAC5D,KAAK,WAAW,GAIX,IAAIP,EAAgB,MAAM;IAChC;IACA;GACD,CAAC;EAEH;EAEA,IAAG,KAAK,QAAQ,UAAU,KAAK,YAC9B,MAAU,MAAM,wFAAwF,KAAK,YAAY;EAI1H,IAAIY,IAAS,KAAK,WAAW,GACvBK,IAAOL,EAAO,SAAS,OAAOW,CAAK;EACzC,IAAGN,GAEF,OADA,KAAK,cAAc,KAAK,QAAQ,QAAQL,CAAM,GACvC,IAAIZ,EAAgB,MAAM;GAChC;GACA;EACD,CAAC;EAED,MAAU,MAAM,sBAAsBuB,EAAM,wCAAwC;CAEtF;CAEA,eAAe,GAA4D;EAE1E,IAAG,KAAK,QAAQG,EAAO,oBAAoB,KAAA,GAI3C,OAAO,IAAI1B,EAAgB,MAAM0B,CAAM;CACxC;CAEA,IAAI,cAAc;EACjB,OAAO,KAAK,iBAAiB,KAAK,QAAQ,QAAQ,GAAO,MAAYC,IAAQC,EAAQ,MAAM,CAAC,CAAC,WAAW,CAAC;CAC1G;CACA,IAAI,iBAAiB;EACpB,OAAO,KAAK,QAAQ,EAAE,CAAC,IAAI,aAAa,KAAK,QAAQ;CACtD;CAEA,kBAAoC;EACnC,OAAO,EACN,SAAS,KAAK,QAAQ,KAAI,MAAUhB,EAAO,GAAwB,EACpE;CACD;AACD"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"serialize-object.js","names":[],"sources":["../src/serialize-object.ts"],"sourcesContent":["import AllocatedMemory from './allocated-memory';\nimport type MemoryHeap from './memory-heap';\nimport { getPointer } from './utils/pointer';\n\nenum VALUE_TYPE {\n\tUNDEFINED,\n\tNUMBER,\n\tSTRING,\n}\n\n// Be able to serialize a simple object into memory and re-create the object in another thread\n// This does NOT support updating objects across threads\nconst MAGIC_NUMBER = 52361700;\nexport function serializeObjectToMemory<T extends object>(heap: MemoryHeap, object: T): AllocatedMemory {\n\tconst data: Array<number> = [];\n\tconst keys = Object.keys(object);\n\tdata.push(MAGIC_NUMBER);\n\tdata.push(keys.length);\n\n\t// Create index first so we can quickly construct index\n\tlet keyIndexes: { [key:string]:number } = {};\n\tkeys.forEach(key => {\n\t\tdata.push(key.length);\n\t\taddCharCodes(data, key);\n\n\t\tkeyIndexes[key] = data.length;\n\t\tdata.push(0);\n\t});\n\n\tkeys.forEach(key => {\n\t\tlet keyIndex = keyIndexes[key];\n\t\t// @ts-expect-error\n\t\tlet value = object[key];\n\t\tif(Number.isFinite(value)) {\n\t\t\tdata[keyIndex] = data.length;\n\t\t\tdata.push(VALUE_TYPE.NUMBER);\n\t\t\tdata.push(value);\n\t\t} else if(typeof value === 'string') {\n\t\t\tdata[keyIndex] = data.length;\n\t\t\tdata.push(VALUE_TYPE.STRING);\n\t\t\tdata.push(value.length);\n\t\t\taddCharCodes(data, value);\n\t\t}\n\t});\n\t\n\tlet memory = heap.allocUI32(data.length);\n\tlet sharedData = memory.getArray(Float32Array, 0, data.length);\n\tsharedData.set(data);\n\n\treturn memory;\n}\n\nexport function createObjectFromPointer<T extends object>(heap: MemoryHeap, pointer: number) {\n\tlet memory = new AllocatedMemory(heap, getPointer(pointer, heap.positionBits));\n\treturn createObjectFromMemory<T>(memory);\n}\nexport function createObjectFromMemory<T extends object>(memory: AllocatedMemory): T {\n\tlet sharedData = new Float32Array(memory.data.buffer, memory.bufferByteOffset);\n\tif(sharedData[0] !== MAGIC_NUMBER) {\n\t\tthrow new Error('Trying to create object from invalid memory location');\n\t}\n\n\tconst sharedObject: { [key:string]:any } = {};\n\tconst keyCount = sharedData[1];\n\tlet keyStartIndex = 2;\n\tfor(let i = 0; i < keyCount; i++) {\n\t\tlet keyLength = sharedData[keyStartIndex];\n\t\tlet keyData = new Float32Array(memory.data.buffer, memory.bufferByteOffset + (keyStartIndex + 1) * sharedData.BYTES_PER_ELEMENT, keyLength);\n\t\tlet key = String.fromCharCode.apply(null, [...keyData]);\n\t\tlet valueIndex = sharedData[keyStartIndex + keyLength + 1];\n\t\tlet valueType = sharedData[valueIndex];\n\t\tswitch(valueType) {\n\t\t\tcase VALUE_TYPE.NUMBER: {\n\t\t\t\tsharedObject[key] = sharedData[valueIndex + 1];\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tcase VALUE_TYPE.STRING: {\n\t\t\t\tlet length = sharedData[valueIndex + 1];\n\t\t\t\tlet valueData = new Float32Array(memory.data.buffer, memory.bufferByteOffset + (valueIndex + 2) * sharedData.BYTES_PER_ELEMENT, length);\n\t\t\t\tsharedObject[key] = String.fromCharCode.apply(null, [...valueData]);\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\n\t\tkeyStartIndex += 2 + keyLength;\n\t}\n\n\treturn sharedObject as T;\n}\n\nfunction addCharCodes(data: Array<number>, value: string) {\n\tfor(let i = 0; i < value.length; i++) {\n\t\tdata.push(value.charCodeAt(i));\n\t}\n}"],"mappings":";;;AAYA,
|
|
1
|
+
{"version":3,"file":"serialize-object.js","names":["AllocatedMemory","MAGIC_NUMBER","serializeObjectToMemory","data","keys","object","keyIndexes","key","addCharCodes","keyIndex","value","memory","heap","createObjectFromPointer","createObjectFromMemory","getPointer","pointer","sharedData","sharedObject","keyCount","keyStartIndex","i","keyLength","keyData","valueIndex","length","valueData"],"sources":["../src/serialize-object.ts"],"sourcesContent":["import AllocatedMemory from './allocated-memory';\nimport type MemoryHeap from './memory-heap';\nimport { getPointer } from './utils/pointer';\n\nenum VALUE_TYPE {\n\tUNDEFINED,\n\tNUMBER,\n\tSTRING,\n}\n\n// Be able to serialize a simple object into memory and re-create the object in another thread\n// This does NOT support updating objects across threads\nconst MAGIC_NUMBER = 52361700;\nexport function serializeObjectToMemory<T extends object>(heap: MemoryHeap, object: T): AllocatedMemory {\n\tconst data: Array<number> = [];\n\tconst keys = Object.keys(object);\n\tdata.push(MAGIC_NUMBER);\n\tdata.push(keys.length);\n\n\t// Create index first so we can quickly construct index\n\tlet keyIndexes: { [key:string]:number } = {};\n\tkeys.forEach(key => {\n\t\tdata.push(key.length);\n\t\taddCharCodes(data, key);\n\n\t\tkeyIndexes[key] = data.length;\n\t\tdata.push(0);\n\t});\n\n\tkeys.forEach(key => {\n\t\tlet keyIndex = keyIndexes[key];\n\t\t// @ts-expect-error\n\t\tlet value = object[key];\n\t\tif(Number.isFinite(value)) {\n\t\t\tdata[keyIndex] = data.length;\n\t\t\tdata.push(VALUE_TYPE.NUMBER);\n\t\t\tdata.push(value);\n\t\t} else if(typeof value === 'string') {\n\t\t\tdata[keyIndex] = data.length;\n\t\t\tdata.push(VALUE_TYPE.STRING);\n\t\t\tdata.push(value.length);\n\t\t\taddCharCodes(data, value);\n\t\t}\n\t});\n\t\n\tlet memory = heap.allocUI32(data.length);\n\tlet sharedData = memory.getArray(Float32Array, 0, data.length);\n\tsharedData.set(data);\n\n\treturn memory;\n}\n\nexport function createObjectFromPointer<T extends object>(heap: MemoryHeap, pointer: number) {\n\tlet memory = new AllocatedMemory(heap, getPointer(pointer, heap.positionBits));\n\treturn createObjectFromMemory<T>(memory);\n}\nexport function createObjectFromMemory<T extends object>(memory: AllocatedMemory): T {\n\tlet sharedData = new Float32Array(memory.data.buffer, memory.bufferByteOffset);\n\tif(sharedData[0] !== MAGIC_NUMBER) {\n\t\tthrow new Error('Trying to create object from invalid memory location');\n\t}\n\n\tconst sharedObject: { [key:string]:any } = {};\n\tconst keyCount = sharedData[1];\n\tlet keyStartIndex = 2;\n\tfor(let i = 0; i < keyCount; i++) {\n\t\tlet keyLength = sharedData[keyStartIndex];\n\t\tlet keyData = new Float32Array(memory.data.buffer, memory.bufferByteOffset + (keyStartIndex + 1) * sharedData.BYTES_PER_ELEMENT, keyLength);\n\t\tlet key = String.fromCharCode.apply(null, [...keyData]);\n\t\tlet valueIndex = sharedData[keyStartIndex + keyLength + 1];\n\t\tlet valueType = sharedData[valueIndex];\n\t\tswitch(valueType) {\n\t\t\tcase VALUE_TYPE.NUMBER: {\n\t\t\t\tsharedObject[key] = sharedData[valueIndex + 1];\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tcase VALUE_TYPE.STRING: {\n\t\t\t\tlet length = sharedData[valueIndex + 1];\n\t\t\t\tlet valueData = new Float32Array(memory.data.buffer, memory.bufferByteOffset + (valueIndex + 2) * sharedData.BYTES_PER_ELEMENT, length);\n\t\t\t\tsharedObject[key] = String.fromCharCode.apply(null, [...valueData]);\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\n\t\tkeyStartIndex += 2 + keyLength;\n\t}\n\n\treturn sharedObject as T;\n}\n\nfunction addCharCodes(data: Array<number>, value: string) {\n\tfor(let i = 0; i < value.length; i++) {\n\t\tdata.push(value.charCodeAt(i));\n\t}\n}"],"mappings":";;;AAYA,IAAMC,IAAe;AACrB,SAAgBC,EAA0C,GAAkB,GAA4B;CACvG,IAAMC,IAAsB,CAAC,GACvBC,IAAO,OAAO,KAAKC,CAAM;CAE/B,AADAF,EAAK,KAAKF,CAAY,GACtBE,EAAK,KAAKC,EAAK,MAAM;CAGrB,IAAIE,IAAsC,CAAC;CAS3C,AARAF,EAAK,SAAQ,MAAO;EAKnB,AAJAD,EAAK,KAAKI,EAAI,MAAM,GACpBC,EAAaL,GAAMI,CAAG,GAEtB,EAAWA,KAAOJ,EAAK,QACvBA,EAAK,KAAK,CAAC;CACZ,CAAC,GAEDC,EAAK,SAAQ,MAAO;EACnB,IAAIK,IAAWH,EAAWC,IAEtBG,IAAQL,EAAOE;EACnB,AAAG,OAAO,SAASG,CAAK,KACvB,EAAKD,KAAYN,EAAK,QACtBA,EAAK,KAAA,CAAsB,GAC3BA,EAAK,KAAKO,CAAK,KACN,OAAOA,KAAU,aAC1B,EAAKD,KAAYN,EAAK,QACtBA,EAAK,KAAA,CAAsB,GAC3BA,EAAK,KAAKO,EAAM,MAAM,GACtBF,EAAaL,GAAMO,CAAK;CAE1B,CAAC;CAED,IAAIC,IAASC,EAAK,UAAUT,EAAK,MAAM;CAIvC,OAFAQ,EADwB,SAAS,cAAc,GAAGR,EAAK,MACvD,CAAA,CAAW,IAAIA,CAAI,GAEZQ;AACR;AAEA,SAAgBE,EAA0C,GAAkB,GAAiB;CAE5F,OAAOC,EAA0B,IADhBd,EAAgBY,GAAMG,EAAWC,GAASJ,EAAK,YAAY,CAC3C,CAAM;AACxC;AACA,SAAgBE,EAAyC,GAA4B;CACpF,IAAIG,IAAa,IAAI,aAAaN,EAAO,KAAK,QAAQA,EAAO,gBAAgB;CAC7E,IAAGM,EAAW,OAAOhB,GACpB,MAAU,MAAM,sDAAsD;CAGvE,IAAMiB,IAAqC,CAAC,GACtCC,IAAWF,EAAW,IACxBG,IAAgB;CACpB,KAAI,IAAIC,IAAI,GAAGA,IAAIF,GAAU,KAAK;EACjC,IAAIG,IAAYL,EAAWG,IACvBG,IAAU,IAAI,aAAaZ,EAAO,KAAK,QAAQA,EAAO,oBAAoBS,IAAgB,KAAKH,EAAW,mBAAmBK,CAAS,GACtIf,IAAM,OAAO,aAAa,MAAM,MAAM,CAAC,GAAGgB,CAAO,CAAC,GAClDC,IAAaP,EAAWG,IAAgBE,IAAY;EAExD,QADgBL,EAAWO,IAC3B;GACC,KAAA;IACC,EAAajB,KAAOU,EAAWO,IAAa;IAC5C;GAED,KAAA,GAAwB;IACvB,IAAIC,IAASR,EAAWO,IAAa,IACjCE,IAAY,IAAI,aAAaf,EAAO,KAAK,QAAQA,EAAO,oBAAoBa,IAAa,KAAKP,EAAW,mBAAmBQ,CAAM;IACtI,EAAalB,KAAO,OAAO,aAAa,MAAM,MAAM,CAAC,GAAGmB,CAAS,CAAC;IAClE;GACD;EACD;EAEA,KAAiB,IAAIJ;CACtB;CAEA,OAAOJ;AACR;AAEA,SAASV,EAAa,GAAqB,GAAe;CACzD,KAAI,IAAIa,IAAI,GAAGA,IAAIX,EAAM,QAAQ,KAChC,EAAK,KAAKA,EAAM,WAAWW,CAAC,CAAC;AAE/B"}
|
package/dist/shared-list.js.map
CHANGED
|
@@ -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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"}
|