UNPKG

@thi.ng/vector-pools

Version:

Data structures for managing & working with strided, memory mapped vectors

344 lines (343 loc) 9.77 kB
import { sizeOf, typedArray } from "@thi.ng/api/typedarray"; import { align } from "@thi.ng/binary/align"; import { isNumber } from "@thi.ng/checks/is-number"; import { assert } from "@thi.ng/errors/assert"; import { MemPool } from "@thi.ng/malloc/pool"; import { range } from "@thi.ng/transducers/range"; import { zeroes } from "@thi.ng/vectors/setn"; import { LOGGER } from "./api.js"; class AttribPool { attribs; order; specs; pool; addr; capacity; byteStride; maxAttribSize; resizable; constructor(opts) { opts = { resizable: true, ...opts }; this.pool = !(opts.mem instanceof MemPool) ? new MemPool(opts.mem) : opts.mem; this.capacity = opts.num; this.resizable = !!opts.resizable; this.specs = {}; this.attribs = {}; this.order = []; this.byteStride = 1; this.maxAttribSize = 1; opts.attribs && this.addAttribs(opts.attribs, true); } bytes() { return new Uint8Array( this.pool.buf, this.addr, this.capacity * this.byteStride ); } release(releasePool = true) { if (releasePool && this.pool) { this.pool.release(); } delete this.pool; delete this.attribs; delete this.specs; return true; } addAttribs(specs, alloc = false) { const [newStride, maxSize] = this.computeStride(specs); this.maxAttribSize = maxSize; if (newStride != this.byteStride) { this.realign(newStride); } this.validateSpecs(specs, newStride); this.byteStride = newStride; if (alloc) { const addr = this.pool.malloc(this.capacity * this.byteStride); assert(addr > 0, `out of memory`); this.addr = addr; } this.initDefaults(specs); this.setAttribs(specs); } attribValue(id, i) { const spec = this.specs[id]; __ensureSpec(spec, id); if (i >= this.capacity) return; i *= spec.stride; return spec.size > 1 ? this.attribs[id].subarray(i, i + spec.size) : this.attribs[id][i]; } *attribValues(id) { const spec = this.specs[id]; __ensureSpec(spec, id); const buf = this.attribs[id]; const stride = spec.stride; const size = spec.size; if (size > 1) { for (let i = 0, j = 0, n = this.capacity; i < n; i++, j += stride) { yield buf.subarray(j, j + size); } } else { for (let i = 0, n = this.capacity; i < n; i++) { yield buf[i * stride]; } } } attribArray(id) { const spec = this.specs[id]; __ensureSpec(spec, id); const n = this.capacity; const size = spec.size; const stride = spec.stride; const src = this.attribs[id]; const dest = typedArray(spec.type, n * size); if (size > 1) { for (let i = 0, j = 0; i < n; i++, j += stride) { dest.set(src.subarray(j, j + size), i * size); } } else { for (let i = 0; i < n; i++) { dest[i] = src[i * stride]; } } return dest; } setAttribValue(id, index, v) { const spec = this.specs[id]; this.ensure(index + 1); const isNum = isNumber(v); __ensureAttrib(spec, id, isNum); const buf = this.attribs[id]; index *= spec.stride; if (!isNum) { __ensureValueSize(v, spec.size); buf.set(v, index); } else { buf[index] = v; } return this; } setAttribValues(id, vals, index = 0) { const v = vals[0]; const spec = this.specs[id]; const isNum = isNumber(v); __ensureAttrib(spec, id, isNum); const n = vals.length; this.ensure(index + n); const stride = spec.stride; const buf = this.attribs[id]; if (!isNum) { __ensureValueSize(v, spec.size); for (let i = 0, j = index * stride; i < n; i++, j += stride) { buf.set(vals[i], j); } } else { for (let i = 0, j = index * stride; i < n; i++, j += stride) { buf[j] = vals[i]; } } } setAttribs(specs) { for (let id in specs) { const spec = specs[id]; spec.data && this.setAttribValues(id, spec.data, spec.index || 0); } } removeAttrib(id) { if (!this.attribs[id]) return false; delete this.attribs[id]; delete this.specs[id]; this.updateOrder(); const [stride, size] = this.computeStride(this.specs, false); this.maxAttribSize = size; this.realign(stride); } ensure(newCapacity, fill = false) { if (newCapacity <= this.capacity) return; assert(this.resizable, `pool resizing disabled`); const newAddr = this.pool.realloc( this.addr, newCapacity * this.byteStride ); assert(newAddr > 0, `out of memory`); for (let id in this.specs) { const a = this.specs[id]; const buf = typedArray( a.type, this.pool.buf, newAddr + (a.byteOffset || 0), (newCapacity - 1) * a.stride + a.size ); buf.set(this.attribs[id]); this.attribs[id] = buf; } if (fill) { this.setDefaults(this.specs, this.capacity, newCapacity); } this.addr = newAddr; this.capacity = newCapacity; } computeStride(specs, inclExisting = true) { let maxStride = inclExisting ? this.byteStride : 1; let maxSize = inclExisting ? this.maxAttribSize : 1; for (let id in specs) { const a = specs[id]; const size = sizeOf(a.type); maxSize = Math.max(maxSize, size); maxStride = Math.max(maxStride, a.byteOffset + a.size * size); } return [align(maxStride, maxSize), maxSize]; } validateSpecs(specs, stride = this.byteStride) { for (let id in specs) { assert(!this.attribs[id], `attrib: ${id} already exists`); const a = specs[id]; assert(a.size > 0, `attrib ${id}: illegal or missing size`); const size = sizeOf(a.type); a.default == null && (a.default = a.size > 1 ? zeroes(a.size) : 0); const isNum = isNumber(a.default); assert( () => !isNum && a.size === a.default.length || isNum && a.size === 1, `attrib ${id}: incompatible default value, expected size ${a.size}` ); assert( a.byteOffset % size === 0, `attrib ${id}: invalid offset, expected multiple of ${size}` ); a.stride = stride / size; this.specs[id] = a; } this.updateOrder(); } updateOrder() { this.order = Object.keys(this.specs).sort( (a, b) => this.specs[a].byteOffset - this.specs[b].byteOffset ); } initDefaults(specs, start = 0, end = this.capacity) { for (let id in specs) { const a = specs[id]; this.attribs[id] = typedArray( a.type, this.pool.buf, this.addr + (a.byteOffset || 0), (this.capacity - 1) * a.stride + a.size ); } this.setDefaults(specs, start, end); } setDefaults(specs, start = 0, end = this.capacity) { for (let id in specs) { const a = specs[id]; if (a.default == null) continue; const buf = this.attribs[id]; const s = a.stride; const v = a.default; if (a.size === 1) { for (let i = start; i < end; i++) { buf[i * s] = v; } } else { for (let i = start; i < end; i++) { buf.set(v, i * s); } } } } realign(newByteStride) { if (this.order.length === 0 || newByteStride === this.byteStride) return; LOGGER.info(`realigning ${this.byteStride} -> ${newByteStride}...`); const grow = newByteStride > this.byteStride; let newAddr = this.addr; if (grow) { assert(this.resizable, `pool growth disabled`); newAddr = this.pool.realloc( this.addr, this.capacity * newByteStride ); assert(newAddr > 0, `out of memory`); } else if (!this.resizable) { return; } const sameBlock = newAddr === this.addr; const num = this.capacity - 1; const { attribs, specs } = this; const order = grow ? [...this.order].reverse() : this.order; const newAttribs = __resizeAttribs( specs, this.pool.buf, newAddr, newByteStride, num ); for (let i of newByteStride < this.byteStride ? range(num + 1) : range(num, -1, -1)) { __moveAttribs( order, specs, attribs, newAttribs, i, sameBlock, grow ); } this.addr = newAddr; this.byteStride = newByteStride; for (let id in newAttribs) { const a = newAttribs[id]; attribs[id] = a[0]; specs[id].stride = a[1]; } } } const __resizeAttribs = (specs, buf, dest, stride, num) => { const newAttribs = {}; for (let id in specs) { const a = specs[id]; const dStride = stride / sizeOf(a.type); newAttribs[id] = [ typedArray( a.type, buf, dest + a.byteOffset, num * dStride + a.size ), dStride ]; } return newAttribs; }; const __moveAttribs = (order, specs, attribs, newAttribs, i, sameBlock, grow) => { for (let id of order) { const a = specs[id]; const sStride = a.stride; const src = attribs[id]; const [dest, dStride] = newAttribs[id]; if (a.size === 1) { dest[i * dStride] = src[i * sStride]; } else { const saddr = i * sStride; const daddr = i * dStride; sameBlock ? (grow ? dest : src).copyWithin(daddr, saddr, saddr + a.size) : dest.set(src.subarray(saddr, saddr + a.size), daddr); } } }; const __ensureSpec = (spec, id) => assert(!!spec, `invalid attrib: ${id}`); const __ensureAttrib = (spec, id, isNum) => { __ensureSpec(spec, id); assert( () => !isNum && spec.size > 1 || isNum && spec.size === 1, `incompatible value for attrib: ${id}` ); }; const __ensureValueSize = (v, size) => assert( v.length <= size, `wrong attrib val size, expected ${size}, got ${v.length}` ); export { AttribPool };