Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 0 additions & 3 deletions .eslintrc.yml

This file was deleted.

23 changes: 8 additions & 15 deletions src/IOBuffer.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,8 @@
import { increaseBufferSize, isHostBigEndian } from './env';
import { decode, encode } from './text';

const defaultByteLength = 1024 * 8;

const hostBigEndian = (() => {
const array = new Uint8Array(4);
const view = new Uint32Array(array.buffer);
return !((view[0] = 1) & array[0]);
})();

type InputData = number | ArrayBufferLike | ArrayBufferView | IOBuffer | Buffer;

const typedArrays = {
Expand All @@ -21,7 +16,7 @@ const typedArrays = {
int64: globalThis.BigInt64Array,
float32: globalThis.Float32Array,
float64: globalThis.Float64Array,
};
} as const;

type TypedArrays = typeof typedArrays;

Expand Down Expand Up @@ -251,13 +246,11 @@ export class IOBuffer {
public ensureAvailable(byteLength = 1): this {
if (!this.available(byteLength)) {
const lengthNeeded = this.offset + byteLength;
const newLength = lengthNeeded * 2;
const newArray = new Uint8Array(newLength);
newArray.set(new Uint8Array(this.buffer));
this.buffer = newArray.buffer;
this.length = newLength;
this.byteLength = newLength;
this._data = new DataView(this.buffer);
const newBuffer = increaseBufferSize(this.buffer, lengthNeeded);
this.buffer = newBuffer;
this.length = newBuffer.byteLength;
this.byteLength = newBuffer.byteLength;
this._data = new DataView(newBuffer);
}
return this;
}
Expand Down Expand Up @@ -319,7 +312,7 @@ export class IOBuffer {
const offset = this.byteOffset + this.offset;
const slice = this.buffer.slice(offset, offset + bytes);
if (
this.littleEndian === hostBigEndian &&
this.littleEndian === isHostBigEndian() &&
type !== 'uint8' &&
type !== 'int8'
) {
Expand Down
3 changes: 3 additions & 0 deletions src/__tests__/write.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,9 @@ describe('write data', () => {
theBuffer.seek(20);
theBuffer.ensureAvailable(30);
expect(theBuffer.byteLength).toBeGreaterThanOrEqual(50);
expect(() => theBuffer.ensureAvailable(Number.MAX_SAFE_INTEGER)).toThrow(
RangeError,
);
});

it('writeUtf8', () => {
Expand Down
73 changes: 73 additions & 0 deletions src/env.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
let hostIsBigEndian: boolean | undefined;

/**
* Checks if the host system is big-endian.
* @returns `true` if the host system is big-endian, `false` if it is little-endian.
*/
export function isHostBigEndian() {
return hostIsBigEndian ?? (hostIsBigEndian = detectEndianness());
}

function detectEndianness() {
const array = new Uint8Array(4);
const view = new Uint32Array(array.buffer);
return !((view[0] = 1) & array[0]);
}

/**
* Returns a new buffer with the same contents as the input buffer, but with a larger size.
* Try to at least double the size of the buffer to avoid frequent reallocations.
* @param buffer - The buffer to increase the size of.
* @param minimumSize - The minimum size of the new buffer.
* @returns The new buffer.
*/
export function increaseBufferSize(
buffer: ArrayBuffer,
minimumSize: number,
): ArrayBuffer {
let maxAllowedLength = 2 ** 29; // 512MB
if (minimumSize > maxAllowedLength) {
// The check is expensive, don't do it until a large buffer is requested.
maxAllowedLength = getMaxUint8ArrayLength();
if (minimumSize > maxAllowedLength) {
throw new RangeError(
`Cannot create a buffer with more than ${maxAllowedLength} bytes`,
);
}
}
// Don't try to allocate more than the maximum allowed length.
const doubleOrMax = Math.min(buffer.byteLength * 2, maxAllowedLength);
const newLength = Math.max(doubleOrMax, minimumSize);
const newBuffer = new ArrayBuffer(newLength);
new Uint8Array(newBuffer).set(new Uint8Array(buffer));
return newBuffer;
}

let maxUint8ArrayLength: number | undefined;

/**
* Detects and returns the maximum length that of an `ArrayBuffer`.
* @returns The maximum length that an `ArrayBuffer` can have.
*/
export function getMaxUint8ArrayLength() {
return (
maxUint8ArrayLength ?? (maxUint8ArrayLength = detectMaxUint8ArrayLength())
);
}

function detectMaxUint8ArrayLength() {
// Use a binary search to find the maximum length of an Uint8Array.
let low = 1;
let high = Number.MAX_SAFE_INTEGER - 1;
while (low < high) {
const mid = Math.trunc((low + high) / 2);
try {
// eslint-disable-next-line no-new
new Uint8Array(mid);
low = mid + 1;
} catch {
high = mid;
}
}
return low - 1;
}
11 changes: 11 additions & 0 deletions src/text.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,21 @@
/**
* Decode a Uint8Array to a string.
* @param bytes - The Uint8Array to decode.
* @param encoding - The encoding to use. Defaults to 'utf8'.
* @returns The decoded string.
*/
export function decode(bytes: Uint8Array, encoding = 'utf8'): string {
const decoder = new TextDecoder(encoding);
return decoder.decode(bytes);
}

const encoder = new TextEncoder();

/**
* Encode a string as UTF-8 to a Uint8Array.
* @param str - The string to encode.
* @returns The encoded Uint8Array.
*/
export function encode(str: string): Uint8Array {
return encoder.encode(str);
}
1 change: 1 addition & 0 deletions tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
"sourceMap": true,
"strict": true,
"target": "es2022",
"lib": ["es2022"],
"skipLibCheck": true,
"allowJs": false,
"noEmit": true
Expand Down
Loading