Skip to content

Commit

Permalink
refactor: Using a data-view in buffer IO. (#38)
Browse files Browse the repository at this point in the history
  • Loading branch information
iwoplaza authored Dec 17, 2024
1 parent adb99b7 commit c2e353e
Show file tree
Hide file tree
Showing 4 changed files with 50 additions and 81 deletions.
21 changes: 4 additions & 17 deletions packages/typed-binary/src/io/bufferIOBase.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,14 +14,8 @@ export type BufferIOOptions = {
};

export class BufferIOBase {
protected readonly uint8View: Uint8Array;
protected readonly helperInt32View: Int32Array;
protected readonly helperUint32View: Uint32Array;
protected readonly helperUint16View: Uint16Array;
protected readonly helperFloat32View: Float32Array;
protected readonly helperByteView: Uint8Array;
protected readonly switchEndianness: boolean;

protected readonly dataView: DataView;
protected readonly littleEndian: boolean;
protected byteOffset = 0;

public readonly endianness: Endianness;
Expand All @@ -33,20 +27,13 @@ export class BufferIOBase {

const systemEndianness = getSystemEndianness();
this.endianness = endianness === 'system' ? systemEndianness : endianness;
this.switchEndianness = this.endianness !== systemEndianness;
this.littleEndian = this.endianness === 'little';

// Getting rid of the outer shell, which causes the Uint8Array line to create a copy, instead of a view.
const unwrapped = unwrapBuffer(buffer);
this.byteOffset += unwrapped.byteOffset;

this.uint8View = new Uint8Array(unwrapped.buffer, 0);

const helperBuffer = new ArrayBuffer(4);
this.helperInt32View = new Int32Array(helperBuffer);
this.helperUint32View = new Uint32Array(helperBuffer);
this.helperFloat32View = new Float32Array(helperBuffer);
this.helperByteView = new Uint8Array(helperBuffer);
this.helperUint16View = new Uint16Array(helperBuffer);
this.dataView = new DataView(unwrapped.buffer);
}

get currentByteOffset() {
Expand Down
47 changes: 20 additions & 27 deletions packages/typed-binary/src/io/bufferReader.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,59 +13,52 @@ export class BufferReader extends BufferIOBase implements ISerialInput {
return this._cachedTextDecoder;
}

private copyInputToHelper(bytes: number) {
for (let i = 0; i < bytes; ++i) {
this.helperByteView[this.switchEndianness ? bytes - 1 - i : i] =
this.uint8View[this.byteOffset++];
}
}

readBool() {
return this.uint8View[this.byteOffset++] !== 0;
return this.dataView.getUint8(this.byteOffset++) !== 0;
}

readByte() {
return this.uint8View[this.byteOffset++];
return this.dataView.getUint8(this.byteOffset++);
}

readFloat16() {
this.copyInputToHelper(2);

return float16ToNumber(this.helperUint16View);
const value = this.dataView.getUint16(this.byteOffset, this.littleEndian);
this.byteOffset += 2;
return float16ToNumber(value);
}

readFloat32() {
this.copyInputToHelper(4);

return this.helperFloat32View[0];
const value = this.dataView.getFloat32(this.byteOffset, this.littleEndian);
this.byteOffset += 4;
return value;
}

readInt32() {
this.copyInputToHelper(4);

return this.helperInt32View[0];
const value = this.dataView.getInt32(this.byteOffset, this.littleEndian);
this.byteOffset += 4;
return value;
}

readUint32() {
this.copyInputToHelper(4);

return this.helperUint32View[0];
const value = this.dataView.getUint32(this.byteOffset, this.littleEndian);
this.byteOffset += 4;
return value;
}

readString() {
// Looking for the 'NULL' byte.
let end = this.byteOffset;
while (end < this.uint8View.byteLength) {
if (this.uint8View[end++] === 0) {
let strLength = 0;
while (this.byteOffset + strLength < this.dataView.byteLength) {
if (this.dataView.getUint8(this.byteOffset + strLength++) === 0) {
break;
}
}

const result = this._textDecoder.decode(
this.uint8View.subarray(this.byteOffset, end - 1),
new Uint8Array(this.dataView.buffer, this.byteOffset, strLength - 1),
);

this.byteOffset = end;
this.byteOffset += strLength;

return result;
}
Expand All @@ -82,7 +75,7 @@ export class BufferReader extends BufferIOBase implements ISerialInput {
);

for (let i = 0; i < byteLength; ++i) {
destU8[i] = this.uint8View[this.byteOffset++];
destU8[i] = this.dataView.getUint8(this.byteOffset++);
}
}
}
40 changes: 17 additions & 23 deletions packages/typed-binary/src/io/bufferWriter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,53 +13,47 @@ export class BufferWriter extends BufferIOBase implements ISerialOutput {
return this._cachedTextEncoder;
}

private copyHelperToOutput(bytes: number) {
for (let i = 0; i < bytes; ++i)
this.uint8View[this.byteOffset++] =
this.helperByteView[this.switchEndianness ? bytes - 1 - i : i];
}

writeBool(value: boolean) {
this.uint8View[this.byteOffset++] = value ? 1 : 0;
this.dataView.setUint8(this.byteOffset++, value ? 1 : 0);
}

writeByte(value: number) {
this.uint8View[this.byteOffset++] = Math.floor(value) % 256;
this.dataView.setUint8(this.byteOffset++, value);
}

writeFloat16(value: number): void {
this.helperUint16View[0] = numberToFloat16(value)[0];

this.copyHelperToOutput(2);
this.dataView.setUint16(
this.byteOffset,
numberToFloat16(value),
this.littleEndian,
);
this.byteOffset += 2;
}

writeInt32(value: number) {
this.helperInt32View[0] = Math.floor(value);

this.copyHelperToOutput(4);
this.dataView.setInt32(this.byteOffset, value, this.littleEndian);
this.byteOffset += 4;
}

writeUint32(value: number) {
this.helperUint32View[0] = Math.floor(value);

this.copyHelperToOutput(4);
this.dataView.setUint32(this.byteOffset, value, this.littleEndian);
this.byteOffset += 4;
}

writeFloat32(value: number) {
this.helperFloat32View[0] = value;

this.copyHelperToOutput(4);
this.dataView.setFloat32(this.byteOffset, value, this.littleEndian);
this.byteOffset += 4;
}

writeString(value: string) {
const result = this._textEncoder.encodeInto(
value,
this.uint8View.subarray(this.byteOffset),
new Uint8Array(this.dataView.buffer, this.byteOffset),
);
this.byteOffset += result.written;

// Extra null character
this.uint8View[this.byteOffset++] = 0;
this.dataView.setUint8(this.byteOffset++, 0);
}

writeSlice(bufferView: ArrayLike<number> & ArrayBufferView): void {
Expand All @@ -72,7 +66,7 @@ export class BufferWriter extends BufferIOBase implements ISerialOutput {
);

for (const srcByte of srcU8) {
this.uint8View[this.byteOffset++] = srcByte;
this.dataView.setUint8(this.byteOffset++, srcByte);
}
}
}
23 changes: 9 additions & 14 deletions packages/typed-binary/src/io/float16converter.ts
Original file line number Diff line number Diff line change
@@ -1,27 +1,22 @@
export function numberToFloat16(value: number): Uint16Array {
export function numberToFloat16(value: number): number {
// conversion according to IEEE 754 binary16 format
if (value === 0) return new Uint16Array([0]);
if (Number.isNaN(value)) return new Uint16Array([0x7e00]);
if (!Number.isFinite(value))
return new Uint16Array([value > 0 ? 0x7c00 : 0xfc00]);
if (value === 0) return 0;
if (Number.isNaN(value)) return 0x7e00;
if (!Number.isFinite(value)) return value > 0 ? 0x7c00 : 0xfc00;

const sign = value < 0 ? 1 : 0;
const absValue = Math.abs(value);
const exponent = Math.floor(Math.log2(absValue));
const mantissa = absValue / 2 ** exponent - 1;
const biasedExponent = exponent + 15;
const mantissaBits = Math.floor(mantissa * 1024);
const float16 = (sign << 15) | (biasedExponent << 10) | mantissaBits;
const uint16Array = new Uint16Array(1);
uint16Array[0] = float16;
return uint16Array;
return (sign << 15) | (biasedExponent << 10) | mantissaBits;
}

export function float16ToNumber(uint16Array: Uint16Array): number {
const float16 = uint16Array[0];
const sign = (float16 & 0x8000) >> 15;
const exponent = (float16 & 0x7c00) >> 10;
const mantissa = float16 & 0x3ff;
export function float16ToNumber(uint16Encoding: number): number {
const sign = (uint16Encoding & 0x8000) >> 15;
const exponent = (uint16Encoding & 0x7c00) >> 10;
const mantissa = uint16Encoding & 0x3ff;
if (exponent === 0) {
return sign === 0 ? mantissa / 1024 : -mantissa / 1024;
}
Expand Down

0 comments on commit c2e353e

Please sign in to comment.