You do not need binary string conversion to get array-of-struct access. That is slow.
Do as C, Rust, Java, C# etc. have always done at compile time (albeit at runtime in JS): use offsets and sizes to working with your structs inside the array. Add some member names et voila!
This works for both interleaved (multi-member structs) and non-interleaved arrays. Obviously you are now using function calls instead of array[] (unlike C & Rust, TMK), but these may (?) be inlined under the hood. I suspect any slowdown due to read/write functions is far outweighed by performance gains associated with accessing contiguous data.
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;
//write 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(structTypes)
{
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);
}
}
Usage:
let structTypes = //incomplete for now
{
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:
{
throws: {size: 3, offset: 0},
misses: {size: 1, offset: 0},
},
}
prepareStructTypes(structTypes); //complete them: pad, deduce member offsets and struct total size.
//Test
const Boxer = structTypes.Boxer;
const numBoxers = 8;
const arrayOfBoxer = new Uint8Array(numBoxers * Boxer._size);
console.log("Interleaved Boxer array length:", arrayOfBoxer.length, "size per element", Boxer._size);
//arbitrary values
let i = 3;
let id = 888;
let time = 23983291;
//write
write(arrayOfBoxer, i, Boxer, "id", id);
write(arrayOfBoxer, i, Boxer, "time", time);
console.log(arrayOfBoxer);
//read
read(arrayOfBoxer, i, Boxer, "id");
read(arrayOfBoxer, i, Boxer, "time");
Output:
write Boxer at [80], id at [82]: 888
write Boxer at [80], name at [89]: 23983291
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, 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, …]
read Boxer at [80], id at [82]: 888
read Boxer at [80], name at [89]: 23983291
DataView. Btw, from what I read js-ctypes are a library wrapper on standard javascript, so it should be save (and easy) to use.