From 019c0af35e155731f795158174eb585417227547 Mon Sep 17 00:00:00 2001 From: Karsten Schmidt Date: Fri, 30 Nov 2018 01:51:59 +0000 Subject: [PATCH] feat(vector-pools): add AttribPool, refactor lists --- packages/vector-pools/src/alist.ts | 46 ++-- packages/vector-pools/src/api.ts | 18 +- packages/vector-pools/src/array-list.ts | 22 +- packages/vector-pools/src/attrib-pool.ts | 293 +++++++++++++++++++++++ packages/vector-pools/src/index.ts | 1 + packages/vector-pools/src/linked-list.ts | 14 +- 6 files changed, 359 insertions(+), 35 deletions(-) create mode 100644 packages/vector-pools/src/attrib-pool.ts diff --git a/packages/vector-pools/src/alist.ts b/packages/vector-pools/src/alist.ts index 82f9c281b1..295ae0a064 100644 --- a/packages/vector-pools/src/alist.ts +++ b/packages/vector-pools/src/alist.ts @@ -1,8 +1,8 @@ -import { IVector, Vec } from "@thi.ng/vectors3/api"; -import { wrap } from "./wrap"; +import { StridedVec, Vec } from "@thi.ng/vectors3/api"; import { VecFactory } from "./api"; +import { wrap } from "./wrap"; -export abstract class AVecList> { +export abstract class AVecList { buffer: Vec; factory: VecFactory; @@ -14,17 +14,27 @@ export abstract class AVecList> { estride: number; cstride: number; - _free: T[]; + freeIDs: number[]; + /** + * + * @param buffer + * @param capacity + * @param size + * @param start + * @param cstride + * @param estride + * @param factory + */ constructor( buffer: Vec, capacity: number, size: number, - factory: VecFactory = wrap, + start = 0, cstride = 1, estride = size, - start = 0) { - + factory: VecFactory = wrap, + ) { this.buffer = buffer || new Float32Array(size * capacity); this.size = size; this.factory = factory; @@ -32,7 +42,7 @@ export abstract class AVecList> { this.estride = estride; this.start = this.curr = start; this.capacity = capacity; - this._free = []; + this.freeIDs = []; } abstract [Symbol.iterator](): IterableIterator; @@ -45,6 +55,10 @@ export abstract class AVecList> { abstract remove(v: T): boolean; + abstract has(v: T): boolean; + + abstract nth(n: number): T; + indices(res: Vec = [], i = 0, local = true) { const start = this.start; const estride = this.estride; @@ -61,15 +75,15 @@ export abstract class AVecList> { } protected alloc() { - let v: T; - if (this._free.length > 0) { - v = this._free.pop(); + let idx: number; + if (this.freeIDs.length > 0) { + idx = this.freeIDs.pop(); + } else if (this.length >= this.capacity) { + return; } else { - if (this.length < this.capacity) { - v = this.factory(this.buffer, this.size, this.curr, this.cstride); - this.curr += this.estride; - } + idx = this.curr; + this.curr += this.estride; } - return v; + return this.factory(this.buffer, this.size, idx, this.cstride); } } diff --git a/packages/vector-pools/src/api.ts b/packages/vector-pools/src/api.ts index 24a43c120b..f4b97f16a3 100644 --- a/packages/vector-pools/src/api.ts +++ b/packages/vector-pools/src/api.ts @@ -1,21 +1,29 @@ import { IRelease, TypedArray } from "@thi.ng/api/api"; import { Type } from "@thi.ng/malloc/api"; -import { IVector, Vec } from "@thi.ng/vectors3/api"; +import { StridedVec, Vec, ReadonlyVec } from "@thi.ng/vectors3/api"; + +export interface AttribSpec { + type: Type; + size: number; + default: number | ReadonlyVec; + byteOffset: number; + stride?: number; +} export interface IVecPool extends IRelease { malloc(size: number, type?: Type): TypedArray; - mallocWrapped(size: number, stride?: number, type?: Type): IVector; + mallocWrapped(size: number, stride?: number, type?: Type): StridedVec; - mallocArray(num: number, size: number, cstride?: number, estride?: number, type?: Type): IVector[]; + mallocArray(num: number, size: number, cstride?: number, estride?: number, type?: Type): StridedVec[]; - free(vec: IVector | TypedArray): boolean; + free(vec: StridedVec | TypedArray): boolean; freeAll(); } export type VecFactory = - (buf: Vec, size: number, index: number, stride: number) => IVector; + (buf: Vec, size: number, index: number, stride: number) => StridedVec; export { Type }; diff --git a/packages/vector-pools/src/array-list.ts b/packages/vector-pools/src/array-list.ts index 9c24347fe8..e602058643 100644 --- a/packages/vector-pools/src/array-list.ts +++ b/packages/vector-pools/src/array-list.ts @@ -1,8 +1,8 @@ -import { IVector, Vec } from "@thi.ng/vectors3/api"; +import { StridedVec, Vec } from "@thi.ng/vectors3/api"; import { AVecList } from "./alist"; import { VecFactory } from "./api"; -export class VecArrayList> extends AVecList { +export class VecArrayList extends AVecList { items: T[]; @@ -19,12 +19,12 @@ export class VecArrayList> extends AVecList { buffer: Vec, capacity: number, size: number, - factory?: VecFactory, + start = 0, cstride = 1, estride = size, - start = 0) { - - super(buffer, capacity, size, factory, cstride, estride, start); + factory?: VecFactory, + ) { + super(buffer, capacity, size, cstride, estride, start, factory); this.items = []; } @@ -56,9 +56,17 @@ export class VecArrayList> extends AVecList { remove(v: T) { const idx = this.items.indexOf(v); if (idx >= 0) { - this._free.push(v); + this.freeIDs.push(v.i); this.items.splice(idx, 1); return true; } } + + has(v: T) { + return this.items.indexOf(v) >= 0; + } + + nth(n: number) { + return this.items[n]; + } } diff --git a/packages/vector-pools/src/attrib-pool.ts b/packages/vector-pools/src/attrib-pool.ts new file mode 100644 index 0000000000..7c79a2abf6 --- /dev/null +++ b/packages/vector-pools/src/attrib-pool.ts @@ -0,0 +1,293 @@ +import { IObjectOf, IRelease, TypedArray } from "@thi.ng/api/api"; +import { assert } from "@thi.ng/api/assert"; +import { align } from "@thi.ng/binary/align"; +import { Pow2 } from "@thi.ng/binary/api"; +import { SIZEOF, MemPoolOpts } from "@thi.ng/malloc/api"; +import { MemPool } from "@thi.ng/malloc/pool"; +import { wrap } from "@thi.ng/malloc/wrap"; +import { range } from "@thi.ng/transducers/iter/range"; +import { ReadonlyVec, Vec } from "@thi.ng/vectors3/api"; +import { AttribSpec } from "./api"; + +/* + * WASM mem : 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 ... + * typedarr : 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 ... offset = 4 (bytes) + * pos (f32) : X X X X X Y Y Y Y X ... offset = 0 (bytes), size = 2 (f32) + * uv (f32) : U U U U V V V V ... offset = 8 (bytes), size = 2 (f32) + * col (u16) : R R G G B B A A ... offset = 16 (bytes), size = 4 (u16) + * + * global stride: 16 + */ +export class AttribPool implements + IRelease { + + attribs: IObjectOf; + order: string[]; + specs: IObjectOf; + pool: MemPool; + addr: number; + capacity: number; + + byteStride: number; + maxAttribSize: number; + + constructor( + pool: number | ArrayBuffer | MemPool, + capacity: number, + specs: IObjectOf, + poolOpts?: MemPoolOpts + ) { + this.pool = !(pool instanceof MemPool) ? + new MemPool(pool, poolOpts) : + pool; + this.capacity = capacity; + this.specs = {}; + this.attribs = {}; + this.order = []; + this.byteStride = 1; + this.maxAttribSize = 1; + this.initAttribs(specs, true); + } + + release(releasePool = true) { + if (releasePool && this.pool) { + this.pool.release(); + } + delete this.pool; + delete this.attribs; + delete this.specs; + return true; + } + + initAttribs(specs: IObjectOf, alloc = false) { + const [newStride, maxSize] = this.computeStride(specs); + this.maxAttribSize = maxSize; + if (newStride != this.byteStride) { + this.realignAttribs(newStride); + } + this.validateAttribs(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); + } + + getAttrib(id: string) { + return this.attribs[id]; + } + + getAttribVal(id: string, i: number): number | Vec { + if (i >= this.capacity) return; + const spec = this.specs[id]; + assert(!!spec, `invalid attrib: ${id}`); + i *= spec.stride; + return spec.size > 1 ? + this.attribs[id].subarray(i, i + spec.size) : + this.attribs[id][i]; + } + + *attribValues(id: string) { + const buf = this.attribs[id]; + assert(!!buf, `invalid attrib: ${id}`); + const spec = this.specs[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]; + } + } + } + + setAttribVal(id: string, index: number, v: number | ReadonlyVec) { + const spec = this.specs[id]; + assert(!!spec, `invalid attrib: ${id}`); + this.ensure(index); + const buf = this.attribs[id]; + index *= spec.stride; + const isNum = typeof v === "number"; + assert( + () => (!isNum && spec.size > 1) || (isNum && spec.size === 1), + `incompatible value for attrib: ${id}` + ); + if (!isNum) { + assert( + (v).length <= spec.size, + `wrong attrib val size, expected ${spec.size}, got ${(v).length}` + ); + buf.set(v, index); + } else { + buf[index] = v; + } + return this; + } + + removeAttrib(id: string) { + 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.realignAttribs(stride); + } + + ensure(newCapacity: number, fill = false) { + if (newCapacity < this.capacity) return; + // TODO add realloc() + const newAddr = this.pool.malloc(newCapacity * this.byteStride); + assert(newAddr > 0, `out of memory`); + for (let id in this.specs) { + const a = this.specs[id]; + const buf = wrap( + 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) { + // TODO fill remainder with default values? + this.setDefaults(this.specs, this.capacity, newCapacity); + } + this.pool.free(this.addr); + this.addr = newAddr; + this.capacity = newCapacity; + } + + protected computeStride(specs: IObjectOf, 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]; + } + + protected validateAttribs(specs: IObjectOf, stride = this.byteStride) { + for (let id in specs) { + assert(!this.attribs[id], `attrib: ${id} already exists`); + const a = specs[id]; + const size = SIZEOF[a.type]; + const isNum = typeof a.default === "number"; + assert( + () => (!isNum && a.size === (a.default).length) || (isNum && a.size === 1), + `incompatible default value for attrib: ${id}, expected size ${a.size}` + ); + assert( + a.byteOffset % size === 0, + `invalid offset for attrib: ${id}, expected multiple of ${size}` + ); + a.stride = stride / size; + this.specs[id] = a; + } + this.updateOrder(); + } + + protected updateOrder() { + this.order = Object.keys(this.specs).sort((a, b) => this.specs[a].byteOffset - this.specs[b].byteOffset); + } + + protected initDefaults(specs: IObjectOf, start = 0, end = this.capacity) { + for (let id in specs) { + const a = specs[id]; + this.attribs[id] = wrap( + a.type, + this.pool.buf, + this.addr + (a.byteOffset || 0), + (this.capacity - 1) * a.stride + a.size + ); + } + this.setDefaults(specs, start, end); + } + + protected setDefaults(specs: IObjectOf, start = 0, end = this.capacity) { + for (let id in specs) { + const buf = this.attribs[id]; + const a = specs[id]; + const s = a.stride; + const v = a.default; + if (typeof v === "number") { + for (let i = start; i < end; i++) { + buf[i * s] = v; + } + } else { + for (let i = start; i < end; i++) { + buf.set(v, i * s); + } + } + } + } + + protected realignAttribs(newByteStride: number) { + if (this.order.length === 0 || newByteStride === this.byteStride) return; + console.warn(`realigning ${this.byteStride} -> ${newByteStride}...`); + const grow = newByteStride > this.byteStride; + let newAddr = this.addr; + if (grow) { + // TODO realloc + newAddr = this.pool.malloc(this.capacity * newByteStride); + assert(newAddr > 0, `out of memory`); + } + const sameBlock = newAddr === this.addr; + const num = this.capacity - 1; + const attribs = this.attribs; + const newAttribs: IObjectOf<[TypedArray, number]> = {}; + const specs = this.specs; + const order = grow ? [...this.order].reverse() : this.order; + // create resized attrib views (in old or new address space) + for (let id in specs) { + const a = specs[id]; + const dStride = newByteStride / SIZEOF[a.type]; + newAttribs[id] = [ + wrap( + a.type, + this.pool.buf, + newAddr + a.byteOffset, + num * dStride + a.size + ), + dStride + ]; + } + // process in opposite directions based on new stride size + for (let i of newByteStride < this.byteStride ? range(num + 1) : range(num, -1, -1)) { + // ...in offset order to avoid successor attrib vals + for (let id of order) { + const a = specs[id]; + const sStride = a.stride; + const src = attribs[id]; + const [dest, dStride] = newAttribs[id]; + if (typeof a.default === "number") { + dest[i * dStride] = src[i * sStride]; + } else { + const j = i * sStride; + sameBlock ? + (grow ? dest : src).copyWithin(i * dStride, j, j + a.size) : + dest.set(src.subarray(j, j + a.size), i * dStride); + } + } + } + if (this.addr != newAddr) { + this.pool.free(this.addr); + this.addr = newAddr; + } + this.byteStride = newByteStride; + for (let id in newAttribs) { + const a = newAttribs[id]; + attribs[id] = a[0]; + specs[id].stride = a[1]; + } + } +} diff --git a/packages/vector-pools/src/index.ts b/packages/vector-pools/src/index.ts index 0b30724ca7..92c8f045e9 100644 --- a/packages/vector-pools/src/index.ts +++ b/packages/vector-pools/src/index.ts @@ -1,6 +1,7 @@ export * from "./api"; export * from "./alist"; export * from "./array-list"; +export * from "./attrib-pool"; export * from "./linked-list"; export * from "./pool"; export * from "./wrap"; diff --git a/packages/vector-pools/src/linked-list.ts b/packages/vector-pools/src/linked-list.ts index 0c6e458616..f4b6d24581 100644 --- a/packages/vector-pools/src/linked-list.ts +++ b/packages/vector-pools/src/linked-list.ts @@ -1,8 +1,8 @@ -import { IVector, Vec } from "@thi.ng/vectors3/api"; +import { StridedVec, Vec } from "@thi.ng/vectors3/api"; import { AVecList } from "./alist"; import { VecFactory } from "./api"; -export class VecLinkedList> extends AVecList { +export class VecLinkedList extends AVecList { head: T; tail: T; @@ -26,12 +26,12 @@ export class VecLinkedList> extends AVecList { buffer: Vec, capacity: number, size: number, - factory?: VecFactory, + start = 0, cstride = 1, estride = size, - start = 0) { - - super(buffer, capacity, size, factory, cstride, estride, start); + factory?: VecFactory + ) { + super(buffer, capacity, size, cstride, estride, start, factory); this.closed = closed; this.head = null; this.tail = null; @@ -99,7 +99,7 @@ export class VecLinkedList> extends AVecList { remove(vec: T): boolean { if (this.has(vec)) { this._length--; - this._free.push(vec); + this.freeIDs.push(vec.i); const v: any = vec; if (v.prev) { v.prev.next = v.next;