Skip to content

Commit

Permalink
struct-type: Use native byte order
Browse files Browse the repository at this point in the history
  • Loading branch information
rbuckton committed Jun 6, 2023
1 parent b198451 commit f712fca
Show file tree
Hide file tree
Showing 9 changed files with 121 additions and 91 deletions.
37 changes: 19 additions & 18 deletions packages/struct-type/src/__tests__/array.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { ArrayType, bigint64, int16, int32, StructType } from "..";
import { isLittleEndian } from "../endianness";

describe("primitive typed array", () => {
const Int32Array = ArrayType(int32);
Expand All @@ -22,8 +23,8 @@ describe("primitive typed array", () => {
it("can init from buffer", () => {
const buffer = new ArrayBuffer(8);
const view = new DataView(buffer);
view.setInt32(0, 1);
view.setInt32(4, 2);
view.setInt32(0, 1, isLittleEndian);
view.setInt32(4, 2, isLittleEndian);
const ar = new Int32Array(buffer);
expect(ar[0]).toBe(1);
expect(ar[1]).toBe(2);
Expand Down Expand Up @@ -54,8 +55,8 @@ describe("primitive fixed-length typed array", () => {
it("can init from buffer", () => {
const buffer = new ArrayBuffer(8);
const view = new DataView(buffer);
view.setInt32(0, 1);
view.setInt32(4, 2);
view.setInt32(0, 1, isLittleEndian);
view.setInt32(4, 2, isLittleEndian);
const ar = new Int32Arrayx2(buffer);
expect(ar[0]).toBe(1);
expect(ar[1]).toBe(2);
Expand Down Expand Up @@ -87,8 +88,8 @@ describe("primitive fixed-length typed array from non-fixed typed array", () =>
it("can init from buffer", () => {
const buffer = new ArrayBuffer(8);
const view = new DataView(buffer);
view.setInt32(0, 1);
view.setInt32(4, 2);
view.setInt32(0, 1, isLittleEndian);
view.setInt32(4, 2, isLittleEndian);
const ar = new Int32Arrayx2(buffer);
expect(ar[0]).toBe(1);
expect(ar[1]).toBe(2);
Expand Down Expand Up @@ -140,10 +141,10 @@ describe("complex typed array", () => {
it("can init from buffer", () => {
const buffer = new ArrayBuffer(16);
const view = new DataView(buffer);
view.setInt32(0, 1);
view.setInt32(4, 2);
view.setInt32(8, 3);
view.setInt32(12, 4);
view.setInt32(0, 1, isLittleEndian);
view.setInt32(4, 2, isLittleEndian);
view.setInt32(8, 3, isLittleEndian);
view.setInt32(12, 4, isLittleEndian);
const l = new PointArray(buffer);
expect(l[0].x).toBe(1);
expect(l[0].y).toBe(2);
Expand Down Expand Up @@ -196,10 +197,10 @@ describe("complex fixed-length typed array", () => {
it("can init from buffer", () => {
const buffer = new ArrayBuffer(16);
const view = new DataView(buffer);
view.setInt32(0, 1);
view.setInt32(4, 2);
view.setInt32(8, 3);
view.setInt32(12, 4);
view.setInt32(0, 1, isLittleEndian);
view.setInt32(4, 2, isLittleEndian);
view.setInt32(8, 3, isLittleEndian);
view.setInt32(12, 4, isLittleEndian);
const l = new PointArrayx2(buffer);
expect(l[0].x).toBe(1);
expect(l[0].y).toBe(2);
Expand Down Expand Up @@ -243,10 +244,10 @@ describe("complex fixed-length typed array from non-fixed typed array", () => {
it("can init from buffer", () => {
const buffer = new ArrayBuffer(16);
const view = new DataView(buffer);
view.setInt32(0, 1);
view.setInt32(4, 2);
view.setInt32(8, 3);
view.setInt32(12, 4);
view.setInt32(0, 1, isLittleEndian);
view.setInt32(4, 2, isLittleEndian);
view.setInt32(8, 3, isLittleEndian);
view.setInt32(12, 4, isLittleEndian);
const l = new PointArrayx2(buffer);
expect(l[0].x).toBe(1);
expect(l[0].y).toBe(2);
Expand Down
23 changes: 12 additions & 11 deletions packages/struct-type/src/__tests__/struct.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { StructType, ArrayType, int32 } from "..";
import { isLittleEndian } from "../endianness";
import { bigint64 } from "../primitive";

describe("simple struct", () => {
Expand Down Expand Up @@ -43,8 +44,8 @@ describe("simple struct", () => {
it("can init from buffer", () => {
const buffer = new ArrayBuffer(8);
const view = new DataView(buffer);
view.setInt32(0, 1);
view.setInt32(4, 2);
view.setInt32(0, 1, isLittleEndian);
view.setInt32(4, 2, isLittleEndian);
const p = new Point(buffer);
expect(p.x).toBe(1);
expect(p.y).toBe(2);
Expand Down Expand Up @@ -95,10 +96,10 @@ describe("complex struct", () => {
it("can init from buffer", () => {
const buffer = new ArrayBuffer(16);
const view = new DataView(buffer);
view.setInt32(0, 1);
view.setInt32(4, 2);
view.setInt32(8, 3);
view.setInt32(12, 4);
view.setInt32(0, 1, isLittleEndian);
view.setInt32(4, 2, isLittleEndian);
view.setInt32(8, 3, isLittleEndian);
view.setInt32(12, 4, isLittleEndian);
const l = new Line(buffer);
expect(l.from.x).toBe(1);
expect(l.from.y).toBe(2);
Expand Down Expand Up @@ -148,8 +149,8 @@ describe("subclass", () => {
it("can init from buffer", () => {
const buffer = new ArrayBuffer(8);
const view = new DataView(buffer);
view.setInt32(0, 1);
view.setInt32(4, 2);
view.setInt32(0, 1, isLittleEndian);
view.setInt32(4, 2, isLittleEndian);
const p = new Point(buffer);
expect(p.x).toBe(1);
expect(p.y).toBe(2);
Expand Down Expand Up @@ -196,9 +197,9 @@ describe("baseType", () => {
it("can init from buffer", () => {
const buffer = new ArrayBuffer(12);
const view = new DataView(buffer);
view.setInt32(0, 1);
view.setInt32(4, 2);
view.setInt32(8, 3);
view.setInt32(0, 1, isLittleEndian);
view.setInt32(4, 2, isLittleEndian);
view.setInt32(8, 3, isLittleEndian);
const p = new Point3D(buffer);
expect(p.x).toBe(1);
expect(p.y).toBe(2);
Expand Down
30 changes: 30 additions & 0 deletions packages/struct-type/src/endianness.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
/*!
Copyright 2023 Ron Buckton
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

/**
* Indicates whether the current system is little endian.
*/
export const isLittleEndian = new Int32Array(new Uint8Array([0x12, 0x34, 0x56, 0x78]).buffer)[0] !== 0x12345678;

/**
* Indicats the endianess of the system.
*/
export const endianness: Endianness = isLittleEndian ? "LE" : "BE";

/**
* Indicates the byte order is either big-endian (`"BE"`) or little-endian (`"LE"`).
*/
export type Endianness = "BE" | "LE";
8 changes: 1 addition & 7 deletions packages/struct-type/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,15 +14,9 @@
limitations under the License.
*/

import { littleEndian } from "./internal/numbers.js";

export * from "./endianness.js";
export * from "./array.js";
export * from "./primitive.js";
export * from "./struct.js";
export type { InitType, RuntimeType, Type } from "./type.js";
export * from "./wasm.js";

/**
* Indicates whether the current host is little endian.
*/
export const isLittleEndian = littleEndian;
13 changes: 7 additions & 6 deletions packages/struct-type/src/internal/array/arrayTypeInfo.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
*/

import type { ArrayType, FixedLengthArrayType } from "../../array.js";
import { Endianness } from "../../endianness.js";
import type { RuntimeType, Type } from "../../type.js";
import { align } from "../numbers.js";
import { TypeInfo, TypeLike } from "../typeInfo.js";
Expand Down Expand Up @@ -63,19 +64,19 @@ export abstract class ArrayTypeInfo extends TypeInfo {
return value instanceof this.runtimeType ? value : new this.runtimeType(value);
}

readElementFrom(view: DataView, index: number, isLittleEndian?: boolean) {
return this.elementTypeInfo.readFrom(view, index * this.bytesPerElement, isLittleEndian);
readElementFrom(view: DataView, index: number, byteOrder?: Endianness) {
return this.elementTypeInfo.readFrom(view, index * this.bytesPerElement, byteOrder);
}

writeElementTo(view: DataView, index: number, value: RuntimeType<Type>, isLittleEndian?: boolean): void {
this.elementTypeInfo.writeTo(view, index * this.bytesPerElement, value, isLittleEndian);
writeElementTo(view: DataView, index: number, value: RuntimeType<Type>, byteOrder?: Endianness): void {
this.elementTypeInfo.writeTo(view, index * this.bytesPerElement, value, byteOrder);
}

readFrom(view: DataView, offset: number, _isLittleEndian?: boolean) {
readFrom(view: DataView, offset: number, byteOrder?: Endianness) {
return new this.runtimeType(view.buffer, view.byteOffset + offset);
}

writeTo(view: DataView, offset: number, value: RuntimeType<Type>, _isLittleEndian?: boolean): void {
writeTo(view: DataView, offset: number, value: RuntimeType<Type>, byteOrder?: Endianness): void {
if (!(value instanceof this.runtimeType)) throw new TypeError();
value.writeTo(view.buffer, view.byteOffset + offset);
}
Expand Down
69 changes: 34 additions & 35 deletions packages/struct-type/src/internal/numbers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@
limitations under the License.
*/

import { Endianness, endianness } from "../endianness.js";

/* @internal */
export type Alignment = 1 | 2 | 4 | 8;

Expand All @@ -22,9 +24,6 @@ export function align(offset: number, alignment: Alignment) {
return (offset + (alignment - 1)) & -alignment;
}

/* @internal */
export const littleEndian = new Int32Array(new Uint8Array([0x12, 0x34, 0x56, 0x78]).buffer)[0] !== 0x12345678;

/* @internal */
export interface ArrayBufferViewConstructor {
new (size: number): ArrayBufferView & Record<number, number | bigint>;
Expand Down Expand Up @@ -115,48 +114,48 @@ export function sizeOf(nt: NumberType): Alignment {
}

/* @internal */
export function putValueInView(view: DataView, nt: NumberType, byteOffset: number, value: number | bigint | boolean, isLittleEndian?: boolean) {
export function putValueInView(view: DataView, nt: NumberType, byteOffset: number, value: number | bigint | boolean, byteOrder: Endianness = endianness) {
if (isSharedArrayBuffer(view.buffer) && isAtomic(nt) && typeof value === "number" && ((view.byteOffset + byteOffset) % sizeOf(nt)) === 0) {
return putValueInBuffer(view.buffer, nt, view.byteOffset + byteOffset, value, isLittleEndian);
return putValueInBuffer(view.buffer, nt, view.byteOffset + byteOffset, value, byteOrder);
}
switch (nt) {
case NumberType.Bool8: return view.setUint8(byteOffset, Number(coerceValue(nt, value)));
case NumberType.Bool32: return view.setInt32(byteOffset, Number(coerceValue(nt, value)), isLittleEndian);
case NumberType.Bool32: return view.setInt32(byteOffset, Number(coerceValue(nt, value)), byteOrder === "LE");
case NumberType.Int8: return view.setInt8(byteOffset, coerceValue(nt, value));
case NumberType.Int16: return view.setInt16(byteOffset, coerceValue(nt, value), isLittleEndian);
case NumberType.Int32: return view.setInt32(byteOffset, coerceValue(nt, value), isLittleEndian);
case NumberType.Int16: return view.setInt16(byteOffset, coerceValue(nt, value), byteOrder === "LE");
case NumberType.Int32: return view.setInt32(byteOffset, coerceValue(nt, value), byteOrder === "LE");
case NumberType.Uint8: return view.setUint8(byteOffset, coerceValue(nt, value));
case NumberType.Uint16: return view.setUint16(byteOffset, coerceValue(nt, value), isLittleEndian);
case NumberType.Uint32: return view.setUint32(byteOffset, coerceValue(nt, value), isLittleEndian);
case NumberType.Float32: return view.setFloat32(byteOffset, coerceValue(nt, value), isLittleEndian);
case NumberType.Float64: return view.setFloat64(byteOffset, coerceValue(nt, value), isLittleEndian);
case NumberType.BigInt64: return view.setBigInt64(byteOffset, coerceValue(nt, value), isLittleEndian);
case NumberType.BigUint64: return view.setBigUint64(byteOffset, coerceValue(nt, value), isLittleEndian);
case NumberType.Uint16: return view.setUint16(byteOffset, coerceValue(nt, value), byteOrder === "LE");
case NumberType.Uint32: return view.setUint32(byteOffset, coerceValue(nt, value), byteOrder === "LE");
case NumberType.Float32: return view.setFloat32(byteOffset, coerceValue(nt, value), byteOrder === "LE");
case NumberType.Float64: return view.setFloat64(byteOffset, coerceValue(nt, value), byteOrder === "LE");
case NumberType.BigInt64: return view.setBigInt64(byteOffset, coerceValue(nt, value), byteOrder === "LE");
case NumberType.BigUint64: return view.setBigUint64(byteOffset, coerceValue(nt, value), byteOrder === "LE");
}
}

/* @internal */
export function getValueFromView<N extends NumberType>(view: DataView, nt: N, byteOffset: number, isLittleEndian?: boolean): NumberTypeToType[N];
export function getValueFromView<N extends NumberType>(view: DataView, nt: N, byteOffset: number, byteOrder?: Endianness): NumberTypeToType[N];
/* @internal */
export function getValueFromView(view: DataView, nt: NumberType, byteOffset: number, isLittleEndian?: boolean): number | bigint | boolean {
export function getValueFromView(view: DataView, nt: NumberType, byteOffset: number, byteOrder: Endianness = endianness): number | bigint | boolean {
// attempt an atomic read
if (isSharedArrayBuffer(view.buffer) && isAtomic(nt) && ((view.byteOffset + byteOffset) % sizeOf(nt)) === 0) {
return getValueFromBuffer(view.buffer, nt, view.byteOffset + byteOffset, isLittleEndian);
return getValueFromBuffer(view.buffer, nt, view.byteOffset + byteOffset, byteOrder);
}

switch (nt) {
case NumberType.Bool8: return Boolean(view.getUint8(byteOffset));
case NumberType.Bool32: return Boolean(view.getInt32(byteOffset, isLittleEndian));
case NumberType.Bool32: return Boolean(view.getInt32(byteOffset, byteOrder === "LE"));
case NumberType.Int8: return view.getInt8(byteOffset);
case NumberType.Int16: return view.getInt16(byteOffset, isLittleEndian);
case NumberType.Int32: return view.getInt32(byteOffset, isLittleEndian);
case NumberType.Int16: return view.getInt16(byteOffset, byteOrder === "LE");
case NumberType.Int32: return view.getInt32(byteOffset, byteOrder === "LE");
case NumberType.Uint8: return view.getUint8(byteOffset);
case NumberType.Uint16: return view.getUint16(byteOffset, isLittleEndian);
case NumberType.Uint32: return view.getUint32(byteOffset, isLittleEndian);
case NumberType.Float32: return view.getFloat32(byteOffset, isLittleEndian);
case NumberType.Float64: return view.getFloat64(byteOffset, isLittleEndian);
case NumberType.BigInt64: return view.getBigInt64(byteOffset, isLittleEndian);
case NumberType.BigUint64: return view.getBigUint64(byteOffset, isLittleEndian);
case NumberType.Uint16: return view.getUint16(byteOffset, byteOrder === "LE");
case NumberType.Uint32: return view.getUint32(byteOffset, byteOrder === "LE");
case NumberType.Float32: return view.getFloat32(byteOffset, byteOrder === "LE");
case NumberType.Float64: return view.getFloat64(byteOffset, byteOrder === "LE");
case NumberType.BigInt64: return view.getBigInt64(byteOffset, byteOrder === "LE");
case NumberType.BigUint64: return view.getBigUint64(byteOffset, byteOrder === "LE");
}
}

Expand Down Expand Up @@ -287,7 +286,7 @@ const bigInt64ArrayCache = new WeakGenerativeCache<SharedArrayBuffer, BigInt64Ar
const bigUint64ArrayCache = new WeakGenerativeCache<SharedArrayBuffer, BigUint64Array>();

/* @internal */
export function putValueInBuffer(buffer: ArrayBufferLike, nt: NumberType, byteOffset: number, value: number | bigint | boolean, isLittleEndian: boolean = false) {
export function putValueInBuffer(buffer: ArrayBufferLike, nt: NumberType, byteOffset: number, value: number | bigint | boolean, byteOrder: Endianness = endianness) {
// attempt an atomic write
if (isSharedArrayBuffer(buffer) && isAtomic(nt) && typeof value === "number") {
const size = sizeOf(nt);
Expand All @@ -296,18 +295,18 @@ export function putValueInBuffer(buffer: ArrayBufferLike, nt: NumberType, byteOf
const array = getTypedArray(buffer, nt);
const arrayIndex = byteOffset / size;
const coercedValue = coerceValue(nt, value);
const correctedValue = size === 1 || isLittleEndian === littleEndian ? coercedValue : swapByteOrder(nt, coercedValue);
const correctedValue = size === 1 || byteOrder === endianness ? coercedValue : swapByteOrder(nt, coercedValue);
Atomics.store(array, arrayIndex, typeof correctedValue === "boolean" ? Number(correctedValue) : correctedValue);
return;
}
}
putValueInView(getDataView(buffer), nt, byteOffset, value, isLittleEndian);
putValueInView(getDataView(buffer), nt, byteOffset, value, byteOrder);
}

/* @internal */
export function getValueFromBuffer<N extends NumberType>(buffer: ArrayBufferLike, nt: N, byteOffset: number, isLittleEndian?: boolean): NumberTypeToType[N];
export function getValueFromBuffer<N extends NumberType>(buffer: ArrayBufferLike, nt: N, byteOffset: number, byteOrder?: Endianness): NumberTypeToType[N];
/* @internal */
export function getValueFromBuffer<N extends NumberType>(buffer: ArrayBufferLike, nt: N, byteOffset: number, isLittleEndian: boolean = false): number | bigint | boolean {
export function getValueFromBuffer<N extends NumberType>(buffer: ArrayBufferLike, nt: N, byteOffset: number, byteOrder: Endianness = endianness): number | bigint | boolean {
// attempt an atomic read
if (isSharedArrayBuffer(buffer) && isAtomic(nt)) {
const size = sizeOf(nt);
Expand All @@ -317,10 +316,10 @@ export function getValueFromBuffer<N extends NumberType>(buffer: ArrayBufferLike
const arrayIndex = byteOffset / size;
const value = Atomics.load(array, arrayIndex);
if (nt === NumberType.Bool8 || nt === NumberType.Bool32) return Boolean(value);
return size === 1 || isLittleEndian === littleEndian ? value : swapByteOrder(nt, value);
return size === 1 || byteOrder === endianness ? value : swapByteOrder(nt, value);
}
}
return getValueFromView(getDataView(buffer), nt, byteOffset, isLittleEndian);
return getValueFromView(getDataView(buffer), nt, byteOffset, byteOrder);
}

function getTypedArrayConstructor<N extends NumberType>(nt: N): new (buffer: ArrayBufferLike) => NumberTypeToTypedArray[N];
Expand Down Expand Up @@ -397,8 +396,8 @@ function isAtomic(nt: NumberType): nt is AtomicNumberTypes {
const tempDataView = new DataView(new ArrayBuffer(8));

function swapByteOrder<N extends NumberType>(nt: N, value: NumberTypeToType[N]): NumberTypeToType[N] {
putValueInView(tempDataView, nt, 0, value, false);
return getValueFromView(tempDataView, nt, 0, true);
putValueInView(tempDataView, nt, 0, value, "BE");
return getValueFromView(tempDataView, nt, 0, "LE");
}

function getTypeCoersion<N extends NumberType>(nt: N): NumberTypeToCoersion<N>;
Expand Down
Loading

0 comments on commit f712fca

Please sign in to comment.