Skip to content

Commit

Permalink
feat(vectors): add proxied gvec(), update mapBuffer*() docs
Browse files Browse the repository at this point in the history
  • Loading branch information
postspectacular committed Nov 28, 2018
1 parent 1fe9650 commit 63458c2
Show file tree
Hide file tree
Showing 3 changed files with 188 additions and 64 deletions.
99 changes: 99 additions & 0 deletions packages/vectors3/src/gvec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
import { range } from "@thi.ng/transducers/iter/range";
import { map } from "@thi.ng/transducers/xform/map";
import { Vec } from "./api";

/**
* Wrapper for strided, arbitrary length vectors. Wraps given buffer in
* ES6 `Proxy` with custom property getters/setters and implements ES6
* `Iterable` interface. Furthermore, exposes read/write access to
* `IVector` properties:
*
* - `i` - start index
* - `s` - component stride
* - `buf` - backing buffer (readonly)
* - `length` - vector size
*
* Array index access uses bounds checking against [0..`size`) interval,
* but, for performance reasons, **not** against the actual wrapped
* buffer.
*
* Note: ES6 Proxy use is approx. 10x slower than standard array
* accesses. If several computations are to be performed on such vectors
* it will be much more efficient to first copy them to compact arrays
* and then copy result back if needed.
*
* ```
* // 3D vector w/ stride length of 4
* a = gvec([1,0,0,0,2,0,0,0,3,0,0,0], 3, 0, 4);
* a[0] // 1
* a[1] // 2
* a[2] // 3
*
* [...a]
* // [1, 2, 3]
*
* add([], a, a)
* // [2, 4, 6]
*
* add()
* ```
*
* @param buf
* @param size
* @param i
* @param s
*/
export const gvec = (buf: Vec, size: number, i = 0, s = 1) =>
new Proxy(buf, {
get: (obj, id) => {
switch (id) {
case Symbol.iterator:
return function* () {
for (let j = 0, ii = i, ss = s; j < size; j++) {
yield obj[ii + j * ss];
}
};
case "length":
return size;
case "buf":
return buf;
case "i":
return i;
case "s":
return s;
default:
let j = parseInt(<string>id);
return !isNaN(j) && j >= 0 && j < size ?
obj[i + j * s] :
undefined;
}
},
set: (obj, id, value) => {
const j = parseInt(<string>id);
if (!isNaN(j) && <any>j >= 0 && <any>j < size) {
obj[i + (<number>id | 0) * s] = value;
} else {
switch (id) {
case "i":
i = value;
break;
case "s":
s = value;
break;
case "length":
size = value;
break;
default:
return false;
}
}
return true
},
has: (_, id) =>
(<any>id >= 0 && <any>id < size) ||
id === "i" ||
id == "s" ||
id == "length",
ownKeys: () =>
[...map(String, range(size)), "i", "s", "length"],
});
1 change: 1 addition & 0 deletions packages/vectors3/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ export * from "./exp";
export * from "./face-forward";
export * from "./floor";
export * from "./fract";
export * from "./gvec";
export * from "./heading";
export * from "./homogeneous";
export * from "./invert";
Expand Down
152 changes: 88 additions & 64 deletions packages/vectors3/src/map.ts
Original file line number Diff line number Diff line change
@@ -1,134 +1,158 @@
import { IVector, VecOpV, VecOpVV, VecOpVN, VecOpVVV, VecOpVVN } from "./api";

/**
* Like `mapBufferVV`, but for `VecOpV` type ops and hence only using
* single input.
* Vec2/3/4 view based buffer transformation for `VecOpVV` type ops and
* supporting arbitrary component and element layouts of all input and
* output buffers. The given pre-initialized vectors MUST be separate
* instances, are used as sliding cursors / views of their respective
* backing buffers and will be modified as part of the transformation
* process (though the input buffers themselves are treated as
* immutable, unless `out` is configured to use one of the input
* buffers).
*
* In each iteration `op` is called via `op(out, a, b)`, followed by
* cursor updates to process the next vector. No bounds checking is
* performed.
*
* This function returns `out`'s backing buffer.
*
* ```
* // 4x 2D vectors in SOA layout, i.e. [x1,x2,x3,x4,y1,y2,y3,y4]
* buf = [1,3,5,7,2,4,6,8];
* // each input buffer contains 2 2D vectors, but using
* // different strided data layouts
* mapBufferVV(
* // transformation function
* add,
* // init output buffer view
* new Vec2(),
* // wrap 1st input buffer & configure offset & component stride
* new Vec2([1,0,2,0,0,0,0,0,3,0,4,0,0,0,0,0], 0, 2),
* // wrap 2nd input buffer
* new Vec2([0,10,0,0,20,0,0,30,0,0,40], 1, 3),
* 2, // num vectors
* 2, // output element stride
* 8, // input #1 element stride
* 6 // input #2 element stride
* );
* // [ 11, 22, 33, 44 ]
* ```
*
* // use `swapXY` to swizzle each vector and use AOS for output
* res = mapBufferV(swapXY, new Vec2(), new Vec2(buf,0,4), 4, 2, 1);
* // [ 2, 1, 4, 3, 6, 5, 8, 7 ]
* `Vec2/3/4.iterator()` combined with transducers can be used to
* achieve the same (and more flexible) transformations, but will incur
* more intermediate object allocations. `mapBuffer*()` functions only
* use (and mutate) the provided vector instances and do not allocate
* any further objects.
*
* // unpack result for demonstration purposes
* [...Vec2.iterator(res, 4)].map(v => [...v]);
* // [ [ 2, 1 ], [ 4, 3 ], [ 6, 5 ], [ 8, 7 ] ]
* ```
* // output buffer
* const out = new Array(4);
*
* tx.run(
* tx.map(([o, a, b]) => add(o, a, b)),
* tx.tuples(
* Vec2.iterator(out, 2),
* Vec2.iterator([1,0,2,0,0,0,0,0,3,0,4,0,0,0,0,0], 2, 0, 2, 8),
* Vec2.iterator([0,10,0,0,20,0,0,30,0,0,40], 2, 1, 3, 6),
* )
* );
*
* out
* // [ 11, 22, 33, 44 ]
* ```
*
* @param op
* @param out
* @param a
* @param b
* @param num
* @param so
* @param sa
* @param sb
*/
export const mapBufferV = (
op: VecOpV,
export const mapBufferVV = (
op: VecOpVV,
out: IVector<any>,
a: IVector<any>,
b: IVector<any>,
num: number,
so = out.length * out.s,
sa = a.length * a.s) => {
sa = a.length * a.s,
sb = b.length * b.s) => {

while (num-- > 0) {
op(out, a);
op(out, a, b);
out.i += so;
a.i += sa;
b.i += sb;
}
return out.buf;
};

/**
* Like `mapBufferVV`, but for `VecOpVN` type ops and hence using
* a single vector input buffer `a` and a scalar `n`.
* Like `mapBufferVV`, but for `VecOpV` type ops and hence only using
* single input.
*
* ```
* // 4x 2D vectors in SOA layout
* // i.e. [x1, x2, x3, x4, y1, y2, y3, y4]
* buf = [1, 3, 5, 7, 2, 4, 6, 8];
*
* // use `swapXY` to swizzle each vector and use AOS for output
* res = mapBufferV(swapXY, new Vec2(), new Vec2(buf, 0, 4), 4, 2, 1);
* // [ 2, 1, 4, 3, 6, 5, 8, 7 ]
*
* // unpack result for demonstration purposes
* [...Vec2.iterator(res, 4)].map(v => [...v]);
* // [ [ 2, 1 ], [ 4, 3 ], [ 6, 5 ], [ 8, 7 ] ]
* ```
*
* @param op
* @param out
* @param a
* @param n
* @param num
* @param so
* @param sa
*/
export const mapBufferVN = (
op: VecOpVN,
export const mapBufferV = (
op: VecOpV,
out: IVector<any>,
a: IVector<any>,
n: number,
num: number,
so = out.length * out.s,
sa = a.length * a.s) => {

while (num-- > 0) {
op(out, a, n);
op(out, a);
out.i += so;
a.i += sa;
}
return out.buf;
};

/**
* Vec2/3/4 view based buffer transformation for `VecOpVV` type ops and
* supporting arbitrary component and element layouts of all input and
* output buffers. The given pre-initialized vectors MUST be separate
* instances, are used as sliding cursors / views of their respective
* backing buffers and will be modified as part of the transformation
* process (though the input buffers themselves are treated as
* immutable, unless `out` is configured to use one of the input
* buffers).
*
* In each iteration `op` is called via `op(out, a, b)`, followed by
* cursor updates to process the next vector. No bounds checking is
* performed.
*
* This function returns `out`'s backing buffer.
*
* ```
* // each input buffer contains 2 2D vectors, but using
* // different strided data layouts
* mapBufferVV(
* // transformation function
* add,
* // init output buffer view
* new Vec2(),
* // wrap 1st input buffer & configure offset & component stride
* new Vec2([1,0,2,0,0,0,0,0,3,0,4,0,0,0,0,0], 0, 2),
* // wrap 2nd input buffer
* new Vec2([0,10,0,0,20,0,0,30,0,0,40], 1, 3),
* 2, // num vectors
* 2, // output element stride
* 8, // input #1 element stride
* 6 // input #2 element stride
* );
* // [ 11, 22, 33, 44 ]
* ```
* Like `mapBufferVV`, but for `VecOpVN` type ops and hence using
* a single vector input buffer `a` and a scalar `n`.
*
* @param op
* @param out
* @param a
* @param b
* @param n
* @param num
* @param so
* @param sa
* @param sb
*/
export const mapBufferVV = (
op: VecOpVV,
export const mapBufferVN = (
op: VecOpVN,
out: IVector<any>,
a: IVector<any>,
b: IVector<any>,
n: number,
num: number,
so = out.length * out.s,
sa = a.length * a.s,
sb = b.length * b.s) => {
sa = a.length * a.s) => {

while (num-- > 0) {
op(out, a, b);
op(out, a, n);
out.i += so;
a.i += sa;
b.i += sb;
}
return out.buf;
};
Expand Down

0 comments on commit 63458c2

Please sign in to comment.