Skip to content

Commit

Permalink
feat(typedarray): add TypedArrayIo type for efficient typed array han…
Browse files Browse the repository at this point in the history
…dling
  • Loading branch information
fallenoak committed Dec 21, 2023
1 parent c86540b commit 09102b7
Show file tree
Hide file tree
Showing 3 changed files with 190 additions and 2 deletions.
6 changes: 5 additions & 1 deletion src/lib/io/type.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import ArrayIo, { ArrayOptions } from '../type/ArrayIo.js';
import StringIo, { StringOptions } from '../type/StringIo.js';
import StructIo, { StructFields, StructOptions } from '../type/StructIo.js';
import TlvIo, { TlvOptions, TlvTag } from '../type/TlvIo.js';
import TypedArrayIo, { TypedArrayOptions } from '../type/TypedArrayIo.js';

const array = (type: IoType, options: ArrayOptions = {}) =>
new ArrayIo(type, options);
Expand All @@ -19,4 +20,7 @@ const tlv = (
options: TlvOptions = {},
) => new TlvIo(tagType, lengthType, valueTypes, options);

export { array, string, struct, tlv };
const typedArray = (type: IoType, options: TypedArrayOptions = {}) =>
new TypedArrayIo(type, options);

export { array, string, struct, tlv, typedArray };
178 changes: 178 additions & 0 deletions src/lib/type/TypedArrayIo.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,178 @@
import {
Endianness,
IoMode,
RUNTIME_ENDIANNESS,
validateType,
} from '../util.js';
import { openStream } from '../stream/util.js';
import { IoSource, IoType } from '../types.js';
import * as io from '../io/numeric.js';

type TypedArrayOptions = {
size?: number;
endianness?: Endianness;
};

type TypedArray =
| Int8Array
| Uint8Array
| Int16Array
| Uint16Array
| Int32Array
| Uint32Array
| BigInt64Array
| BigUint64Array
| Float32Array
| Float64Array;

type TypedArrayConstructor =
| Int8ArrayConstructor
| Uint8ArrayConstructor
| Int16ArrayConstructor
| Uint16ArrayConstructor
| Int32ArrayConstructor
| Uint32ArrayConstructor
| BigInt64ArrayConstructor
| BigUint64ArrayConstructor
| Float32ArrayConstructor
| Float64ArrayConstructor;

const CONSTRUCTOR_MAP = new Map<IoType, TypedArrayConstructor>([
[io.int8, Int8Array],
[io.uint8, Uint8Array],
[io.int16, Int16Array],
[io.int16le, Int16Array],
[io.int16be, Int16Array],
[io.uint16, Uint16Array],
[io.uint16le, Uint16Array],
[io.uint16be, Uint16Array],
[io.int32, Int32Array],
[io.int32le, Int32Array],
[io.int32be, Int32Array],
[io.uint32, Uint32Array],
[io.uint32le, Uint32Array],
[io.uint32be, Uint32Array],
[io.int64, BigInt64Array],
[io.int64le, BigInt64Array],
[io.int64be, BigInt64Array],
[io.uint64, BigUint64Array],
[io.uint64le, BigUint64Array],
[io.uint64be, BigUint64Array],
[io.float32, Float32Array],
[io.float32le, Float32Array],
[io.float32be, Float32Array],
[io.float64, Float64Array],
[io.float64le, Float64Array],
[io.float64be, Float64Array],
]);

const EXPLICIT_LITTLE_ENDIAN = new Set<IoType>([
io.int16le,
io.uint16le,
io.int32le,
io.uint32le,
io.int64le,
io.uint64le,
io.float32le,
io.float64le,
]);

const EXPLICIT_BIG_ENDIAN = new Set<IoType>([
io.int16be,
io.uint16be,
io.int32be,
io.uint32be,
io.int64be,
io.uint64be,
io.float32be,
io.float64be,
]);

class TypedArrayIo implements IoType {
#type: IoType;
#options: TypedArrayOptions;
#arrayConstructor: TypedArrayConstructor;
#fastIo: boolean;

constructor(type: IoType, options: TypedArrayOptions = {}) {
if (type === undefined) {
throw new Error('Missing required argument: type');
}

validateType(type);

if (!CONSTRUCTOR_MAP.has(type)) {
throw new Error(`Unsupported type for typed array: ${type}`);
}

this.#type = type;
this.#options = options;
this.#arrayConstructor = CONSTRUCTOR_MAP.get(this.#type);

// If type endianness matches runtime endianness, we'll back the typed array with raw bytes
this.#fastIo =
(RUNTIME_ENDIANNESS === Endianness.Little &&
EXPLICIT_LITTLE_ENDIAN.has(this.#type)) ||
(RUNTIME_ENDIANNESS === Endianness.Big &&
EXPLICIT_BIG_ENDIAN.has(this.#type));
}

getSize(value: any[]) {
return this.#options.size === undefined
? this.#type.getSize(undefined) * value.length
: this.#type.getSize(undefined) * this.#options.size;
}

read(source: IoSource) {
const stream = openStream(source, IoMode.Read, this.#options.endianness);

// Ambiguous size reads

if (this.#options.size === undefined) {
const value = [];

while (!stream.eof) {
value.push(this.#type.read(stream));
}

return new this.#arrayConstructor(value);
}

// Fixed size reads

if (this.#fastIo) {
const size = this.#type.getSize(undefined) * this.#options.size;
const bytes = stream.readBytes(size);
return new this.#arrayConstructor(bytes.buffer);
}

const value = new this.#arrayConstructor(this.#options.size);

for (let i = 0; i < this.#options.size; i++) {
value[i] = this.#type.read(stream);
}

return value;
}

write(source: IoSource, value: TypedArray) {
const stream = openStream(source, IoMode.Write, this.#options.endianness);
const expectedEndOffset = stream.offset + this.getSize(undefined);

if (this.#fastIo) {
stream.writeBytes(new Uint8Array(value.buffer));
} else {
for (let i = 0; i < value.length; i++) {
this.#type.write(stream, value[i]);
}
}

if (stream.offset !== expectedEndOffset) {
const zeroBytes = new Uint8Array(expectedEndOffset - stream.offset);
stream.writeBytes(zeroBytes);
}
}
}

export default TypedArrayIo;
export { TypedArrayOptions };
8 changes: 7 additions & 1 deletion src/lib/util.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,12 @@ enum IoMode {
Write = 2,
}

const ENDIANNESS_CHECK = new Uint32Array(
new Uint8Array([0xaa, 0xbb, 0xcc, 0xdd]).buffer,
);
const RUNTIME_ENDIANNESS =
ENDIANNESS_CHECK[0] === 0xddccbbaa ? Endianness.Little : Endianness.Big;

const validateType = (type: IoType) => {
if (typeof type.getSize !== 'function') {
throw new Error('Missing required function: getSize');
Expand All @@ -33,4 +39,4 @@ const resolveValue = (ref: number | string, ...objects: object[]) => {
}
};

export { Endianness, IoMode, validateType, resolveValue };
export { RUNTIME_ENDIANNESS, Endianness, IoMode, validateType, resolveValue };

0 comments on commit 09102b7

Please sign in to comment.