Skip to content

Instantly share code, notes, and snippets.

@ArcaneEngineer
Last active May 31, 2024 16:52
Show Gist options
  • Save ArcaneEngineer/0adf94cded8ed7443f1c1e166c97697a to your computer and use it in GitHub Desktop.
Save ArcaneEngineer/0adf94cded8ed7443f1c1e166c97697a to your computer and use it in GitHub Desktop.
Array-of-struct emulation using TypedArray and some utility functions -- fast, contiguously allocated arrays unlike regular JS arrays of objects.
const systemWordSize = 8; //bytes
const strideLength = 1; //bytes
//...these functions can be optimised to take advantage of 4 or 8 byte reads / writes, depending on system word size.
//Utility functions
function padAndCalcMemberOffsets(type)
{
let structSize = 0;
for (let memberName in type)
{
let member = type[memberName];
console.log(memberName, member);
member.offset = structSize;
structSize += member.size;
}
let remainder = structSize % systemWordSize;
if (remainder != 0)
{
let padSize = systemWordSize - remainder;
type._padding = {size: padSize, offset: structSize};
structSize += padSize;
}
return structSize;
}
function read(array, index, type, memberName)
{
let bytesLength = type[memberName].size;
let ibase = index * type._size;
let i = ibase + type[memberName].offset;
let iStart = i;
let value = 0;
//read out sequentially into a single multibyte value.
for (let b = 0; b < bytesLength; b += strideLength)
{
let bitsToShiftBy = b * 8; //bits per byte
value |= array[i] << bitsToShiftBy;
i += strideLength; //1 byte at a time
}
console.log("read ", type._name, "at ["+ibase+"],", memberName, "at ["+iStart+"] (length="+bytesLength+"):", value);
return value;
}
//write a multibyte field.
function write(array, index, type, memberName, value)
{
let bytesLength = type[memberName].size;
let ibase = index * type._size;
let i = ibase + type[memberName].offset;
let iStart = i;
let valueNew = 0;
//read out sequentially into a single multibyte value.
for (let b = 0; b < bytesLength; b += strideLength)
{
let bitsToShiftBy = b * 8; //bits per byte
let valueSegment = (value >> bitsToShiftBy) & 255;
array[i] = valueSegment;
i += strideLength; //1 byte at a time
}
console.log("write", type._name, "at ["+ibase+"],", memberName, "at ["+iStart+"] (length="+bytesLength+"):", value);
}
function prepareStructTypes()
{
for (let structTypeName in structTypes)
{
let structType = structTypes[structTypeName];
structType._size = padAndCalcMemberOffsets(structType);
structType._name = structTypeName; //optional, only used for console.log
console.log(structTypeName, structType);
}
}
//Set up structs
let structTypes = {
Boxer:
{
id: {size: 2, offset: 0},
hits: {size: 3, offset: 0},
time: {size: 4, offset: 0},
//total size of this struct would be 1+4+2=7 bytes, which does not align with
//typical 4 or 8 byte word boundaries, which make access (very) inefficient. So pad it.
},
Javelineer:
{
a: {size: 1, offset: 0},
b: {size: 3, offset: 0},
},
}
prepareStructTypes(structTypes); //pad them, figure out member offsets, and get struct total size.
//Test
const Boxer = structTypes.Boxer;
const arrayOfBoxer = new Uint8Array(8 * Boxer._size);
console.log("Interleaved Boxer array length:", arrayOfBoxer.length, "size per element", Boxer._size);
let i = 3;
let id = 888;
let time = 23983291;
//write
write(arrayOfBoxer, i, Boxer, "id", id); // i * Boxer._size + Boxer.id.offset,
write(arrayOfBoxer, i, Boxer, "time", time); // i * Boxer._size + Boxer.id.offset,
console.log(arrayOfBoxer);
//read
read(arrayOfBoxer, i, Boxer, "id");
read(arrayOfBoxer, i, Boxer, "time");
@ArcaneEngineer
Copy link
Author

Outputs:

Boxer {
    "id": {
        "size": 2,
        "offset": 0
    },
    "hits": {
        "size": 3,
        "offset": 2
    },
    "time": {
        "size": 4,
        "offset": 5
    },
    "_padding": {
        "size": 7,
        "offset": 9
    },
    "_size": 16,
    "_name": "Boxer"
}

And:

aos.js:70 write Boxer at [48], time at [53] (length=4): 23983291
aos.js:125 Uint8Array(128) [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, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 120, 3, 0, 0, 0, 187, 244, 109, 1, 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, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, …]
aos.js:46 read Boxer at [48], id at [48] (length=2): 888
aos.js:46 read Boxer at [48], time at [53] (length=4): 23983291

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment