mirror of
https://gitlab.com/wgp/dougal/software.git
synced 2025-12-06 06:47:07 +00:00
908 lines
26 KiB
JavaScript
908 lines
26 KiB
JavaScript
const codeToType = {
|
|
0: Int8Array,
|
|
1: Uint8Array,
|
|
2: Int16Array,
|
|
3: Uint16Array,
|
|
4: Int32Array,
|
|
5: Uint32Array,
|
|
7: Float32Array,
|
|
8: Float64Array,
|
|
9: BigInt64Array,
|
|
10: BigUint64Array
|
|
};
|
|
|
|
const typeToBytes = {
|
|
Int8Array: 1,
|
|
Uint8Array: 1,
|
|
Int16Array: 2,
|
|
Uint16Array: 2,
|
|
Int32Array: 4,
|
|
Uint32Array: 4,
|
|
Float32Array: 4,
|
|
Float64Array: 8,
|
|
BigInt64Array: 8,
|
|
BigUint64Array: 8
|
|
};
|
|
|
|
function readTypedValue(view, offset, type) {
|
|
switch (type) {
|
|
case Int8Array: return view.getInt8(offset);
|
|
case Uint8Array: return view.getUint8(offset);
|
|
case Int16Array: return view.getInt16(offset, true);
|
|
case Uint16Array: return view.getUint16(offset, true);
|
|
case Int32Array: return view.getInt32(offset, true);
|
|
case Uint32Array: return view.getUint32(offset, true);
|
|
case Float32Array: return view.getFloat32(offset, true);
|
|
case Float64Array: return view.getFloat64(offset, true);
|
|
case BigInt64Array: return view.getBigInt64(offset, true);
|
|
case BigUint64Array: return view.getBigUint64(offset, true);
|
|
default: throw new Error(`Unsupported type: ${type.name}`);
|
|
}
|
|
}
|
|
|
|
function writeTypedValue(view, offset, value, type) {
|
|
switch (type) {
|
|
case Int8Array: view.setInt8(offset, value); break;
|
|
case Uint8Array: view.setUint8(offset, value); break;
|
|
case Int16Array: view.setInt16(offset, value, true); break;
|
|
case Uint16Array: view.setUint16(offset, value, true); break;
|
|
case Int32Array: view.setInt32(offset, value, true); break;
|
|
case Uint32Array: view.setUint32(offset, value, true); break;
|
|
case Float32Array: view.setFloat32(offset, value, true); break;
|
|
case Float64Array: view.setFloat64(offset, value, true); break;
|
|
case BigInt64Array: view.setBigInt64(offset, BigInt(value), true); break;
|
|
case BigUint64Array: view.setBigUint64(offset, BigInt(value), true); break;
|
|
default: throw new Error(`Unsupported type: ${type.name}`);
|
|
}
|
|
}
|
|
|
|
class DougalBinaryBundle extends ArrayBuffer {
|
|
|
|
static HEADER_LENGTH = 4; // Length of a bundle header
|
|
|
|
/** Clone an existing ByteArray into a DougalBinaryBundle
|
|
*/
|
|
static clone (buffer) {
|
|
const clone = new DougalBinaryBundle(buffer.byteLength);
|
|
const uint8Array = new Uint8Array(buffer);
|
|
const uint8ArrayClone = new Uint8Array(clone);
|
|
uint8ArrayClone.set(uint8Array);
|
|
return clone;
|
|
}
|
|
|
|
constructor (length, options) {
|
|
super (length, options);
|
|
}
|
|
|
|
/** Get the count of bundles in this ByteArray.
|
|
*
|
|
* Stops at the first non-bundle looking offset
|
|
*/
|
|
get bundleCount () {
|
|
let count = 0;
|
|
let currentBundleOffset = 0;
|
|
const view = new DataView(this);
|
|
|
|
while (currentBundleOffset < this.byteLength) {
|
|
|
|
const currentBundleHeader = view.getUint32(currentBundleOffset, true);
|
|
if ((currentBundleHeader & 0xff) !== 0x1c) {
|
|
// This is not a bundle
|
|
return count;
|
|
}
|
|
let currentBundleLength = currentBundleHeader >>> 8;
|
|
|
|
currentBundleOffset += currentBundleLength + DougalBinaryBundle.HEADER_LENGTH;
|
|
count++;
|
|
}
|
|
|
|
return count;
|
|
}
|
|
|
|
|
|
/** Get the number of chunks in the bundles of this ByteArray
|
|
*/
|
|
get chunkCount () {
|
|
let count = 0;
|
|
let bundleOffset = 0;
|
|
const view = new DataView(this);
|
|
|
|
while (bundleOffset < this.byteLength) {
|
|
const header = view.getUint32(bundleOffset, true);
|
|
if ((header & 0xFF) !== 0x1C) break;
|
|
const length = header >>> 8;
|
|
if (bundleOffset + 4 + length > this.byteLength) break;
|
|
|
|
let chunkOffset = bundleOffset + 4; // relative to buffer start
|
|
|
|
while (chunkOffset < bundleOffset + 4 + length) {
|
|
const chunkType = view.getUint8(chunkOffset);
|
|
if (chunkType !== 0x11 && chunkType !== 0x12) break;
|
|
|
|
const cCount = view.getUint16(chunkOffset + 2, true);
|
|
const ΔelemC = view.getUint8(chunkOffset + 10);
|
|
const elemC = view.getUint8(chunkOffset + 11);
|
|
|
|
let localOffset = 12; // header size
|
|
|
|
localOffset += ΔelemC + elemC; // preface
|
|
|
|
// initial values
|
|
for (let k = 0; k < ΔelemC; k++) {
|
|
const typeByte = view.getUint8(chunkOffset + 12 + k);
|
|
const baseCode = typeByte & 0xF;
|
|
const baseType = codeToType[baseCode];
|
|
if (!baseType) throw new Error('Invalid base type code');
|
|
localOffset += typeToBytes[baseType.name];
|
|
}
|
|
|
|
// pad after initial
|
|
while (localOffset % 4 !== 0) localOffset++;
|
|
|
|
if (chunkType === 0x11) { // Sequential
|
|
// record data: Δelems incrs
|
|
for (let k = 0; k < ΔelemC; k++) {
|
|
const typeByte = view.getUint8(chunkOffset + 12 + k);
|
|
const incrCode = typeByte >> 4;
|
|
const incrType = codeToType[incrCode];
|
|
if (!incrType) throw new Error('Invalid incr type code');
|
|
localOffset += cCount * typeToBytes[incrType.name];
|
|
}
|
|
|
|
// elems
|
|
for (let k = 0; k < elemC; k++) {
|
|
const typeCode = view.getUint8(chunkOffset + 12 + ΔelemC + k);
|
|
const type = codeToType[typeCode];
|
|
if (!type) throw new Error('Invalid elem type code');
|
|
localOffset += cCount * typeToBytes[type.name];
|
|
}
|
|
} else { // Interleaved
|
|
// Compute exact stride for interleaved record data
|
|
let ΔelemStride = 0;
|
|
for (let k = 0; k < ΔelemC; k++) {
|
|
const typeByte = view.getUint8(chunkOffset + 12 + k);
|
|
const incrCode = typeByte >> 4;
|
|
const incrType = codeToType[incrCode];
|
|
if (!incrType) throw new Error('Invalid incr type code');
|
|
ΔelemStride += typeToBytes[incrType.name];
|
|
}
|
|
let elemStride = 0;
|
|
for (let k = 0; k < elemC; k++) {
|
|
const typeCode = view.getUint8(chunkOffset + 12 + ΔelemC + k);
|
|
const type = codeToType[typeCode];
|
|
if (!type) throw new Error('Invalid elem type code');
|
|
elemStride += typeToBytes[type.name];
|
|
}
|
|
const recordStride = ΔelemStride + elemStride;
|
|
localOffset += cCount * recordStride;
|
|
}
|
|
|
|
// pad after record
|
|
while (localOffset % 4 !== 0) localOffset++;
|
|
|
|
chunkOffset += localOffset;
|
|
count++;
|
|
}
|
|
|
|
bundleOffset += 4 + length;
|
|
}
|
|
|
|
return count;
|
|
}
|
|
|
|
/** Return an array of DougalBinaryChunkSequential or DougalBinaryChunkInterleaved instances
|
|
*/
|
|
chunks () {
|
|
const chunks = [];
|
|
let bundleOffset = 0;
|
|
const view = new DataView(this);
|
|
|
|
while (bundleOffset < this.byteLength) {
|
|
const header = view.getUint32(bundleOffset, true);
|
|
if ((header & 0xFF) !== 0x1C) break;
|
|
const length = header >>> 8;
|
|
if (bundleOffset + 4 + length > this.byteLength) break;
|
|
|
|
let chunkOffset = bundleOffset + 4;
|
|
|
|
while (chunkOffset < bundleOffset + 4 + length) {
|
|
const chunkType = view.getUint8(chunkOffset);
|
|
if (chunkType !== 0x11 && chunkType !== 0x12) break;
|
|
|
|
const cCount = view.getUint16(chunkOffset + 2, true);
|
|
const ΔelemC = view.getUint8(chunkOffset + 10);
|
|
const elemC = view.getUint8(chunkOffset + 11);
|
|
|
|
let localOffset = 12;
|
|
|
|
localOffset += ΔelemC + elemC;
|
|
|
|
// initial values
|
|
for (let k = 0; k < ΔelemC; k++) {
|
|
const typeByte = view.getUint8(chunkOffset + 12 + k);
|
|
const baseCode = typeByte & 0xF;
|
|
const baseType = codeToType[baseCode];
|
|
if (!baseType) throw new Error('Invalid base type code');
|
|
localOffset += typeToBytes[baseType.name];
|
|
}
|
|
|
|
// pad after initial
|
|
while (localOffset % 4 !== 0) localOffset++;
|
|
|
|
if (chunkType === 0x11) { // Sequential
|
|
// record data: Δelems incrs
|
|
for (let k = 0; k < ΔelemC; k++) {
|
|
const typeByte = view.getUint8(chunkOffset + 12 + k);
|
|
const incrCode = typeByte >> 4;
|
|
const incrType = codeToType[incrCode];
|
|
if (!incrType) throw new Error('Invalid incr type code');
|
|
localOffset += cCount * typeToBytes[incrType.name];
|
|
}
|
|
|
|
// elems
|
|
for (let k = 0; k < elemC; k++) {
|
|
const typeCode = view.getUint8(chunkOffset + 12 + ΔelemC + k);
|
|
const type = codeToType[typeCode];
|
|
if (!type) throw new Error('Invalid elem type code');
|
|
localOffset += cCount * typeToBytes[type.name];
|
|
}
|
|
} else { // Interleaved
|
|
// Compute exact stride for interleaved record data
|
|
let ΔelemStride = 0;
|
|
for (let k = 0; k < ΔelemC; k++) {
|
|
const typeByte = view.getUint8(chunkOffset + 12 + k);
|
|
const incrCode = typeByte >> 4;
|
|
const incrType = codeToType[incrCode];
|
|
if (!incrType) throw new Error('Invalid incr type code');
|
|
ΔelemStride += typeToBytes[incrType.name];
|
|
}
|
|
let elemStride = 0;
|
|
for (let k = 0; k < elemC; k++) {
|
|
const typeCode = view.getUint8(chunkOffset + 12 + ΔelemC + k);
|
|
const type = codeToType[typeCode];
|
|
if (!type) throw new Error('Invalid elem type code');
|
|
elemStride += typeToBytes[type.name];
|
|
}
|
|
const recordStride = ΔelemStride + elemStride;
|
|
localOffset += cCount * recordStride;
|
|
}
|
|
|
|
// pad after record
|
|
while (localOffset % 4 !== 0) localOffset++;
|
|
|
|
switch (chunkType) {
|
|
case 0x11:
|
|
chunks.push(new DougalBinaryChunkSequential(this, chunkOffset, localOffset));
|
|
break;
|
|
case 0x12:
|
|
chunks.push(new DougalBinaryChunkInterleaved(this, chunkOffset, localOffset));
|
|
break;
|
|
default:
|
|
throw new Error('Invalid chunk type');
|
|
}
|
|
|
|
chunkOffset += localOffset;
|
|
}
|
|
|
|
bundleOffset += 4 + length;
|
|
}
|
|
|
|
return chunks;
|
|
}
|
|
|
|
/** Return a ByteArray containing all data from all
|
|
* chunks including reconstructed i, j and incremental
|
|
* values as follows:
|
|
*
|
|
* <i_0> <i_1> … <i_x> // i values (constant)
|
|
* <j_0> <j_1> … <j_x> // j values (j0 + Δj*i)
|
|
* <Δelem_0_0> <Δelem_0_1> … <Δelem_0_x> // reconstructed Δelem0 (uses baseType)
|
|
* <Δelem_1_0> <Δelem_1_1> … <Δelem_1_x> // reconstructed Δelem1
|
|
* …
|
|
* <Δelem_y_0> <Δelem_y_1> … <Δelem_y_x> // reconstructed Δelem1
|
|
* <elem_0_0> <elem_0_1> … <elem_0_x> // First elem
|
|
* <elem_1_0> <elem_1_1> … <elem_1_x> // Second elem
|
|
* …
|
|
* <elem_z_0> <elem_z_1> … <elem_z_x> // Last elem
|
|
*
|
|
* It does not matter whether the underlying chunks are
|
|
* sequential or interleaved. This function will transform
|
|
* as necessary.
|
|
*
|
|
*/
|
|
getDataSequentially () {
|
|
const chunks = this.chunks();
|
|
if (chunks.length === 0) return new ArrayBuffer(0);
|
|
|
|
const firstChunk = chunks[0];
|
|
const ΔelemC = firstChunk.ΔelemCount;
|
|
const elemC = firstChunk.elemCount;
|
|
|
|
// Check consistency across chunks
|
|
for (const chunk of chunks) {
|
|
if (chunk.ΔelemCount !== ΔelemC || chunk.elemCount !== elemC) {
|
|
throw new Error('Inconsistent chunk structures');
|
|
}
|
|
}
|
|
|
|
// Get types from first chunk
|
|
const view = new DataView(firstChunk);
|
|
const ΔelemBaseTypes = [];
|
|
for (let k = 0; k < ΔelemC; k++) {
|
|
const typeByte = view.getUint8(12 + k);
|
|
const baseCode = typeByte & 0xF;
|
|
const baseType = codeToType[baseCode];
|
|
if (!baseType) throw new Error('Invalid base type code');
|
|
ΔelemBaseTypes.push(baseType);
|
|
}
|
|
const elemTypes = [];
|
|
for (let k = 0; k < elemC; k++) {
|
|
const typeCode = view.getUint8(12 + ΔelemC + k);
|
|
const type = codeToType[typeCode];
|
|
if (!type) throw new Error('Invalid elem type code');
|
|
elemTypes.push(type);
|
|
}
|
|
|
|
// Compute total records
|
|
const totalN = chunks.reduce((sum, c) => sum + c.jCount, 0);
|
|
|
|
// Compute sizes
|
|
const size_i = totalN * 2; // Uint16 for i
|
|
const size_j = totalN * 4; // Int32 for j
|
|
let size_Δelems = 0;
|
|
for (const t of ΔelemBaseTypes) {
|
|
size_Δelems += totalN * typeToBytes[t.name];
|
|
}
|
|
let size_elems = 0;
|
|
for (const t of elemTypes) {
|
|
size_elems += totalN * typeToBytes[t.name];
|
|
}
|
|
const totalSize = size_i + size_j + size_Δelems + size_elems;
|
|
|
|
const ab = new ArrayBuffer(totalSize);
|
|
const dv = new DataView(ab);
|
|
|
|
// Write i's
|
|
let off = 0;
|
|
for (const chunk of chunks) {
|
|
const i = chunk.i;
|
|
for (let idx = 0; idx < chunk.jCount; idx++) {
|
|
dv.setUint16(off, i, true);
|
|
off += 2;
|
|
}
|
|
}
|
|
|
|
// Write j's
|
|
off = size_i;
|
|
for (const chunk of chunks) {
|
|
const j0 = chunk.j0;
|
|
const Δj = chunk.Δj;
|
|
for (let idx = 0; idx < chunk.jCount; idx++) {
|
|
const j = j0 + idx * Δj;
|
|
dv.setInt32(off, j, true);
|
|
off += 4;
|
|
}
|
|
}
|
|
|
|
// Write Δelems
|
|
off = size_i + size_j;
|
|
for (let m = 0; m < ΔelemC; m++) {
|
|
const type = ΔelemBaseTypes[m];
|
|
const bytes = typeToBytes[type.name];
|
|
for (const chunk of chunks) {
|
|
const arr = chunk.Δelem(m);
|
|
for (let idx = 0; idx < chunk.jCount; idx++) {
|
|
writeTypedValue(dv, off, arr[idx], type);
|
|
off += bytes;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Write elems
|
|
for (let m = 0; m < elemC; m++) {
|
|
const type = elemTypes[m];
|
|
const bytes = typeToBytes[type.name];
|
|
for (const chunk of chunks) {
|
|
const arr = chunk.elem(m);
|
|
for (let idx = 0; idx < chunk.jCount; idx++) {
|
|
writeTypedValue(dv, off, arr[idx], type);
|
|
off += bytes;
|
|
}
|
|
}
|
|
}
|
|
|
|
return ab;
|
|
}
|
|
|
|
/** Return a ByteArray containing all data from all
|
|
* chunks including reconstructed i, j and incremental
|
|
* values, interleaved as follows:
|
|
*
|
|
* <i_0> <j_0> <Δelem_0_0> <Δelem_1_0> … <Δelem_y_0> <elem_0_0> <elem_1_0> … <elem_z_0>
|
|
* <i_1> <j_1> <Δelem_0_1> <Δelem_1_1> … <Δelem_y_1> <elem_0_1> <elem_1_1> … <elem_z_1>
|
|
* <i_x> <j_x> <Δelem_0_x> <Δelem_1_x> … <Δelem_y_x> <elem_0_x> <elem_1_x> … <elem_z_x>
|
|
*
|
|
* It does not matter whether the underlying chunks are
|
|
* sequential or interleaved. This function will transform
|
|
* as necessary.
|
|
*
|
|
*/
|
|
getDataInterleaved () {
|
|
const chunks = this.chunks();
|
|
if (chunks.length === 0) return new ArrayBuffer(0);
|
|
|
|
const firstChunk = chunks[0];
|
|
const ΔelemC = firstChunk.ΔelemCount;
|
|
const elemC = firstChunk.elemCount;
|
|
|
|
// Check consistency across chunks
|
|
for (const chunk of chunks) {
|
|
if (chunk.ΔelemCount !== ΔelemC || chunk.elemCount !== elemC) {
|
|
throw new Error('Inconsistent chunk structures');
|
|
}
|
|
}
|
|
|
|
// Get types from first chunk
|
|
const view = new DataView(firstChunk);
|
|
const ΔelemBaseTypes = [];
|
|
for (let k = 0; k < ΔelemC; k++) {
|
|
const typeByte = view.getUint8(12 + k);
|
|
const baseCode = typeByte & 0xF;
|
|
const baseType = codeToType[baseCode];
|
|
if (!baseType) throw new Error('Invalid base type code');
|
|
ΔelemBaseTypes.push(baseType);
|
|
}
|
|
const elemTypes = [];
|
|
for (let k = 0; k < elemC; k++) {
|
|
const typeCode = view.getUint8(12 + ΔelemC + k);
|
|
const type = codeToType[typeCode];
|
|
if (!type) throw new Error('Invalid elem type code');
|
|
elemTypes.push(type);
|
|
}
|
|
|
|
// Compute total records
|
|
const totalN = chunks.reduce((sum, c) => sum + c.jCount, 0);
|
|
|
|
// Compute record size
|
|
const recordSize = 2 + 4 + // i (Uint16) + j (Int32)
|
|
ΔelemBaseTypes.reduce((sum, t) => sum + typeToBytes[t.name], 0) +
|
|
elemTypes.reduce((sum, t) => sum + typeToBytes[t.name], 0);
|
|
const totalSize = totalN * recordSize;
|
|
|
|
const ab = new ArrayBuffer(totalSize);
|
|
const dv = new DataView(ab);
|
|
|
|
let off = 0;
|
|
for (const chunk of chunks) {
|
|
const i = chunk.i;
|
|
const j0 = chunk.j0;
|
|
const Δj = chunk.Δj;
|
|
for (let idx = 0; idx < chunk.jCount; idx++) {
|
|
dv.setUint16(off, i, true);
|
|
off += 2;
|
|
const j = j0 + idx * Δj;
|
|
dv.setInt32(off, j, true);
|
|
off += 4;
|
|
for (let m = 0; m < ΔelemC; m++) {
|
|
const type = ΔelemBaseTypes[m];
|
|
const bytes = typeToBytes[type.name];
|
|
const arr = chunk.Δelem(m);
|
|
writeTypedValue(dv, off, arr[idx], type);
|
|
off += bytes;
|
|
}
|
|
for (let m = 0; m < elemC; m++) {
|
|
const type = elemTypes[m];
|
|
const bytes = typeToBytes[type.name];
|
|
const arr = chunk.elem(m);
|
|
writeTypedValue(dv, off, arr[idx], type);
|
|
off += bytes;
|
|
}
|
|
}
|
|
}
|
|
|
|
return ab;
|
|
}
|
|
|
|
}
|
|
|
|
|
|
class DougalBinaryChunkSequential extends ArrayBuffer {
|
|
|
|
constructor (buffer, offset, length) {
|
|
super(length);
|
|
new Uint8Array(this).set(new Uint8Array(buffer, offset, length));
|
|
this._ΔelemCaches = new Array(this.ΔelemCount);
|
|
this._elemCaches = new Array(this.elemCount);
|
|
this._ΔelemBlockOffsets = null;
|
|
this._elemBlockOffsets = null;
|
|
this._recordOffset = null;
|
|
}
|
|
|
|
_getRecordOffset() {
|
|
if (this._recordOffset !== null) return this._recordOffset;
|
|
const view = new DataView(this);
|
|
const ΔelemC = this.ΔelemCount;
|
|
const elemC = this.elemCount;
|
|
|
|
let recordOffset = 12 + ΔelemC + elemC;
|
|
for (let k = 0; k < ΔelemC; k++) {
|
|
const tb = view.getUint8(12 + k);
|
|
const bc = tb & 0xF;
|
|
const bt = codeToType[bc];
|
|
recordOffset += typeToBytes[bt.name];
|
|
}
|
|
while (recordOffset % 4 !== 0) recordOffset++;
|
|
this._recordOffset = recordOffset;
|
|
return recordOffset;
|
|
}
|
|
|
|
_initBlockOffsets() {
|
|
if (this._ΔelemBlockOffsets !== null) return;
|
|
const view = new DataView(this);
|
|
const count = this.jCount;
|
|
const ΔelemC = this.ΔelemCount;
|
|
const elemC = this.elemCount;
|
|
|
|
const recordOffset = this._getRecordOffset();
|
|
|
|
this._ΔelemBlockOffsets = [];
|
|
let o = recordOffset;
|
|
for (let k = 0; k < ΔelemC; k++) {
|
|
this._ΔelemBlockOffsets[k] = o;
|
|
const tb = view.getUint8(12 + k);
|
|
const ic = tb >> 4;
|
|
const it = codeToType[ic];
|
|
o += count * typeToBytes[it.name];
|
|
}
|
|
|
|
this._elemBlockOffsets = [];
|
|
for (let k = 0; k < elemC; k++) {
|
|
this._elemBlockOffsets[k] = o;
|
|
const tc = view.getUint8(12 + ΔelemC + k);
|
|
const t = codeToType[tc];
|
|
o += count * typeToBytes[t.name];
|
|
}
|
|
}
|
|
|
|
/** Return the user-defined value
|
|
*/
|
|
get udv () {
|
|
return new DataView(this).getUint8(1);
|
|
}
|
|
|
|
/** Return the number of j elements in this chunk
|
|
*/
|
|
get jCount () {
|
|
return new DataView(this).getUint16(2, true);
|
|
}
|
|
|
|
/** Return the i value in this chunk
|
|
*/
|
|
get i () {
|
|
return new DataView(this).getUint16(4, true);
|
|
}
|
|
|
|
/** Return the j0 value in this chunk
|
|
*/
|
|
get j0 () {
|
|
return new DataView(this).getUint16(6, true);
|
|
}
|
|
|
|
/** Return the Δj value in this chunk
|
|
*/
|
|
get Δj () {
|
|
return new DataView(this).getInt16(8, true);
|
|
}
|
|
|
|
/** Return the Δelem_count value in this chunk
|
|
*/
|
|
get ΔelemCount () {
|
|
return new DataView(this).getUint8(10);
|
|
}
|
|
|
|
/** Return the elem_count value in this chunk
|
|
*/
|
|
get elemCount () {
|
|
return new DataView(this).getUint8(11);
|
|
}
|
|
|
|
/** Return a TypedArray (e.g., Uint16Array, …) for the n-th Δelem in the chunk
|
|
*/
|
|
Δelem (n) {
|
|
if (this._ΔelemCaches[n]) return this._ΔelemCaches[n];
|
|
|
|
if (n < 0 || n >= this.ΔelemCount) throw new Error(`Invalid Δelem index: ${n}`);
|
|
const view = new DataView(this);
|
|
const count = this.jCount;
|
|
const ΔelemC = this.ΔelemCount;
|
|
|
|
const typeByte = view.getUint8(12 + n);
|
|
const baseCode = typeByte & 0xF;
|
|
const incrCode = typeByte >> 4;
|
|
const baseType = codeToType[baseCode];
|
|
const incrType = codeToType[incrCode];
|
|
if (!baseType || !incrType) throw new Error('Invalid type codes for Δelem');
|
|
|
|
// Find offset for initial value of this Δelem
|
|
let initialOffset = 12 + ΔelemC + this.elemCount;
|
|
for (let k = 0; k < n; k++) {
|
|
const tb = view.getUint8(12 + k);
|
|
const bc = tb & 0xF;
|
|
const bt = codeToType[bc];
|
|
initialOffset += typeToBytes[bt.name];
|
|
}
|
|
|
|
let current = readTypedValue(view, initialOffset, baseType);
|
|
|
|
// Advance to start of record data (after all initials and pad)
|
|
const recordOffset = this._getRecordOffset();
|
|
|
|
// Find offset for deltas of this Δelem (skip previous Δelems' delta blocks)
|
|
this._initBlockOffsets();
|
|
const deltaOffset = this._ΔelemBlockOffsets[n];
|
|
|
|
// Reconstruct the array
|
|
const arr = new baseType(count);
|
|
const isBigInt = baseType === BigInt64Array || baseType === BigUint64Array;
|
|
arr[0] = current;
|
|
for (let idx = 1; idx < count; idx++) {
|
|
let delta = readTypedValue(view, deltaOffset + idx * typeToBytes[incrType.name], incrType);
|
|
if (isBigInt) {
|
|
delta = BigInt(delta);
|
|
current += delta;
|
|
} else {
|
|
current += delta;
|
|
}
|
|
arr[idx] = current;
|
|
}
|
|
|
|
this._ΔelemCaches[n] = arr;
|
|
return arr;
|
|
}
|
|
|
|
/** Return a TypedArray (e.g., Uint16Array, …) for the n-th elem in the chunk
|
|
*/
|
|
elem (n) {
|
|
if (this._elemCaches[n]) return this._elemCaches[n];
|
|
|
|
if (n < 0 || n >= this.elemCount) throw new Error(`Invalid elem index: ${n}`);
|
|
const view = new DataView(this);
|
|
const count = this.jCount;
|
|
const ΔelemC = this.ΔelemCount;
|
|
const elemC = this.elemCount;
|
|
|
|
const typeCode = view.getUint8(12 + ΔelemC + n);
|
|
const type = codeToType[typeCode];
|
|
if (!type) throw new Error('Invalid type code for elem');
|
|
|
|
// Find offset for this elem's data block
|
|
this._initBlockOffsets();
|
|
const elemOffset = this._elemBlockOffsets[n];
|
|
|
|
// Create and populate the array
|
|
const arr = new type(count);
|
|
const bytes = typeToBytes[type.name];
|
|
for (let idx = 0; idx < count; idx++) {
|
|
arr[idx] = readTypedValue(view, elemOffset + idx * bytes, type);
|
|
}
|
|
|
|
this._elemCaches[n] = arr;
|
|
return arr;
|
|
}
|
|
|
|
getRecord (index) {
|
|
if (index < 0 || index >= this.jCount) throw new Error(`Invalid record index: ${index}`);
|
|
|
|
const arr = [this.udv, this.i, this.j0 + index * this.Δj];
|
|
|
|
for (let m = 0; m < this.ΔelemCount; m++) {
|
|
const values = this.Δelem(m);
|
|
arr.push(values[index]);
|
|
}
|
|
|
|
for (let m = 0; m < this.elemCount; m++) {
|
|
const values = this.elem(m);
|
|
arr.push(values[index]);
|
|
}
|
|
|
|
return arr;
|
|
}
|
|
}
|
|
|
|
|
|
class DougalBinaryChunkInterleaved extends ArrayBuffer {
|
|
constructor(buffer, offset, length) {
|
|
super(length);
|
|
new Uint8Array(this).set(new Uint8Array(buffer, offset, length));
|
|
this._incrStrides = [];
|
|
this._elemStrides = [];
|
|
this._incrOffsets = [];
|
|
this._elemOffsets = [];
|
|
this._recordStride = 0;
|
|
this._recordOffset = null;
|
|
this._initStrides();
|
|
this._ΔelemCaches = new Array(this.ΔelemCount);
|
|
this._elemCaches = new Array(this.elemCount);
|
|
}
|
|
|
|
_getRecordOffset() {
|
|
if (this._recordOffset !== null) return this._recordOffset;
|
|
const view = new DataView(this);
|
|
const ΔelemC = this.ΔelemCount;
|
|
const elemC = this.elemCount;
|
|
|
|
let recordOffset = 12 + ΔelemC + elemC;
|
|
for (let k = 0; k < ΔelemC; k++) {
|
|
const tb = view.getUint8(12 + k);
|
|
const bc = tb & 0xF;
|
|
const bt = codeToType[bc];
|
|
recordOffset += typeToBytes[bt.name];
|
|
}
|
|
while (recordOffset % 4 !== 0) recordOffset++;
|
|
this._recordOffset = recordOffset;
|
|
return recordOffset;
|
|
}
|
|
|
|
_initStrides() {
|
|
const view = new DataView(this);
|
|
const ΔelemC = this.ΔelemCount;
|
|
const elemC = this.elemCount;
|
|
|
|
// Compute incr strides and offsets
|
|
let incrOffset = 0;
|
|
for (let k = 0; k < ΔelemC; k++) {
|
|
const typeByte = view.getUint8(12 + k);
|
|
const incrCode = typeByte >> 4;
|
|
const incrType = codeToType[incrCode];
|
|
if (!incrType) throw new Error('Invalid incr type code');
|
|
this._incrOffsets.push(incrOffset);
|
|
const bytes = typeToBytes[incrType.name];
|
|
this._incrStrides.push(bytes);
|
|
incrOffset += bytes;
|
|
this._recordStride += bytes;
|
|
}
|
|
|
|
// Compute elem strides and offsets
|
|
let elemOffset = incrOffset;
|
|
for (let k = 0; k < elemC; k++) {
|
|
const typeCode = view.getUint8(12 + ΔelemC + k);
|
|
const type = codeToType[typeCode];
|
|
if (!type) throw new Error('Invalid elem type code');
|
|
this._elemOffsets.push(elemOffset);
|
|
const bytes = typeToBytes[type.name];
|
|
this._elemStrides.push(bytes);
|
|
elemOffset += bytes;
|
|
this._recordStride += bytes;
|
|
}
|
|
}
|
|
|
|
get udv() {
|
|
return new DataView(this).getUint8(1);
|
|
}
|
|
|
|
get jCount() {
|
|
return new DataView(this).getUint16(2, true);
|
|
}
|
|
|
|
get i() {
|
|
return new DataView(this).getUint16(4, true);
|
|
}
|
|
|
|
get j0() {
|
|
return new DataView(this).getUint16(6, true);
|
|
}
|
|
|
|
get Δj() {
|
|
return new DataView(this).getInt16(8, true);
|
|
}
|
|
|
|
get ΔelemCount() {
|
|
return new DataView(this).getUint8(10);
|
|
}
|
|
|
|
get elemCount() {
|
|
return new DataView(this).getUint8(11);
|
|
}
|
|
|
|
Δelem(n) {
|
|
if (this._ΔelemCaches[n]) return this._ΔelemCaches[n];
|
|
|
|
if (n < 0 || n >= this.ΔelemCount) throw new Error(`Invalid Δelem index: ${n}`);
|
|
const view = new DataView(this);
|
|
const count = this.jCount;
|
|
const ΔelemC = this.ΔelemCount;
|
|
|
|
const typeByte = view.getUint8(12 + n);
|
|
const baseCode = typeByte & 0xF;
|
|
const incrCode = typeByte >> 4;
|
|
const baseType = codeToType[baseCode];
|
|
const incrType = codeToType[incrCode];
|
|
if (!baseType || !incrType) throw new Error('Invalid type codes for Δelem');
|
|
|
|
// Find offset for initial value of this Δelem
|
|
let initialOffset = 12 + ΔelemC + this.elemCount;
|
|
for (let k = 0; k < n; k++) {
|
|
const tb = view.getUint8(12 + k);
|
|
const bc = tb & 0xF;
|
|
const bt = codeToType[bc];
|
|
initialOffset += typeToBytes[bt.name];
|
|
}
|
|
|
|
let current = readTypedValue(view, initialOffset, baseType);
|
|
|
|
// Find offset to start of record data
|
|
const recordOffset = this._getRecordOffset();
|
|
|
|
// Use precomputed offset for this Δelem
|
|
const deltaOffset = recordOffset + this._incrOffsets[n];
|
|
|
|
// Reconstruct the array
|
|
const arr = new baseType(count);
|
|
const isBigInt = baseType === BigInt64Array || baseType === BigUint64Array;
|
|
arr[0] = current;
|
|
for (let idx = 1; idx < count; idx++) {
|
|
let delta = readTypedValue(view, deltaOffset + idx * this._recordStride, incrType);
|
|
if (isBigInt) {
|
|
delta = BigInt(delta);
|
|
current += delta;
|
|
} else {
|
|
current += delta;
|
|
}
|
|
arr[idx] = current;
|
|
}
|
|
|
|
this._ΔelemCaches[n] = arr;
|
|
return arr;
|
|
}
|
|
|
|
elem(n) {
|
|
if (this._elemCaches[n]) return this._elemCaches[n];
|
|
|
|
if (n < 0 || n >= this.elemCount) throw new Error(`Invalid elem index: ${n}`);
|
|
const view = new DataView(this);
|
|
const count = this.jCount;
|
|
const ΔelemC = this.ΔelemCount;
|
|
|
|
const typeCode = view.getUint8(12 + ΔelemC + n);
|
|
const type = codeToType[typeCode];
|
|
if (!type) throw new Error('Invalid type code for elem');
|
|
|
|
// Find offset to start of record data
|
|
const recordOffset = this._getRecordOffset();
|
|
|
|
// Use precomputed offset for this elem (relative to start of record data)
|
|
const elemOffset = recordOffset + this._elemOffsets[n];
|
|
|
|
// Create and populate the array
|
|
const arr = new type(count);
|
|
const bytes = typeToBytes[type.name];
|
|
for (let idx = 0; idx < count; idx++) {
|
|
arr[idx] = readTypedValue(view, elemOffset + idx * this._recordStride, type);
|
|
}
|
|
|
|
this._elemCaches[n] = arr;
|
|
return arr;
|
|
}
|
|
|
|
getRecord (index) {
|
|
if (index < 0 || index >= this.jCount) throw new Error(`Invalid record index: ${index}`);
|
|
|
|
const arr = [this.udv, this.i, this.j0 + index * this.Δj];
|
|
|
|
for (let m = 0; m < this.ΔelemCount; m++) {
|
|
const values = this.Δelem(m);
|
|
arr.push(values[index]);
|
|
}
|
|
|
|
for (let m = 0; m < this.elemCount; m++) {
|
|
const values = this.elem(m);
|
|
arr.push(values[index]);
|
|
}
|
|
|
|
return arr;
|
|
}
|
|
}
|
|
|
|
|
|
module.exports = { DougalBinaryBundle, DougalBinaryChunkSequential, DougalBinaryChunkInterleaved }
|