@thi.ng/vector-pools
Version:
Data structures for managing & working with strided, memory mapped vectors
344 lines (343 loc) • 9.77 kB
JavaScript
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
};