Skip to content

Instantly share code, notes, and snippets.

@learosema
Created November 8, 2023 10:19
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save learosema/5f075982d271fa2b4dea862e8a25d9e4 to your computer and use it in GitHub Desktop.
Save learosema/5f075982d271fa2b4dea862e8a25d9e4 to your computer and use it in GitHub Desktop.
Uniform struct
import { UniformStruct } from "./uniform-struct";
describe('UniformStruct', () => {
it('should create an empty buffer and data structure by default', () => {
const us = new UniformStruct();
expect(us.buffer).toBeInstanceOf(ArrayBuffer);
expect(us.buffer.byteLength).toBe(0);
expect(us.data).toStrictEqual({});
});
it('should hold numeric data in the buffer',() => {
const us = new UniformStruct();
us.data.vector = [1,2,3];
expect(us.buffer).toStrictEqual(Float32Array.from([1, 2, 3]).buffer);
expect(us.buffer.byteLength).toEqual(3 * 4);
});
it('should be able to initialize a data structure in the constructor', () => {
const us = new UniformStruct({
aVector: [1,2,3],
roughlyPi: 22 / 7,
aNumber: 4,
});
expect(us.buffer).toStrictEqual(
Float32Array.from([1,2,3, 22 / 7, 4]).buffer
);
expect(us.buffer.byteLength).toEqual(5 * 4);
});
it('should be able to modify data inside the structure', () => {
const us = new UniformStruct({
arbitraryVector: [1,2,3],
roughlyPi: 22 / 7,
arbitraryNumber: 4
});
us.data.arbitraryNumber = 42;
us.data.arbitraryVector = [4, 5, 6];
expect(us.buffer).toStrictEqual(
Float32Array.from([4, 5, 6, 22 / 7, 42]).buffer
);
});
it('should be able to add properties inside the structure', () => {
const us = new UniformStruct({
arbitraryVector: [1,2,3],
roughlyPi: 22 / 7,
arbitraryNumber: 42
});
us.data.anotherNumber = 666;
expect(us.buffer).toStrictEqual(
Float32Array.from([1,2,3, 22 / 7, 42, 666]).buffer
);
});
it('should be able to add int (i32) properties', () => {
const us = new UniformStruct();
us.data.intNumber = 42;
expect(us.buffer).toStrictEqual(
new Int32Array([42]).buffer
);
});
});
export type UniformStructData = Record<string, number|Iterable<number>>;
/**
* UniformStruct provides a C-like "struct"
* which is synchronized to an array buffer.
*/
export class UniformStruct {
public buffer = new ArrayBuffer(0);
public typedArrays: Record<string, Float32Array|Int32Array> = {}
public data: UniformStructData;
#internalData: Record<string, number|Iterable<number>> = {};
constructor(initialData ?: UniformStructData) {
this.#internalData = Object.assign({}, initialData || {});
this.#createBuffer();
const self = this;
this.data = new Proxy<Record<string, number|Iterable<number>>>(
this.#internalData, {
set(target: Record<string, any>, key: string, value: any) {
const result = Reflect.set(target, key, value);
if (!self.typedArrays.hasOwnProperty(key)) {
self.#createBuffer();
} else {
self.typedArrays[key].set(
value instanceof Array ? value : [value]
);
}
return result;
}
});
}
#createBuffer() {
let pointer = 0;
const offsets: Record<string, number> = {};
for (const [key, val] of Object.entries(this.#internalData)) {
const count = (val instanceof Array) ? val.length : 1;
offsets[key] = pointer;
const itemSize = 4;
pointer += count * itemSize;
}
this.buffer = new ArrayBuffer(pointer);
this.typedArrays = {};
for (const [key, val] of (Object.entries(this.#internalData))) {
const offset = offsets[key];
const count = (val instanceof Array) ? val.length : 1;
const typedArray = key.startsWith('int') ?
new Int32Array(this.buffer, offset, count) :
new Float32Array(this.buffer, offset, count);
typedArray.set(val instanceof Array ? val: [val]);
this.typedArrays[key] = typedArray;
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment