import type { Chunk, CodecMetadata, DataType, TypedArrayConstructor, } from "../metadata.js"; import { byteswapInplace, getCtr, getStrides } from "../util.js"; const LITTLE_ENDIAN_OS = systemIsLittleEndian(); function systemIsLittleEndian(): boolean { const a = new Uint32Array([0x12345678]); const b = new Uint8Array(a.buffer, a.byteOffset, a.byteLength); return !(b[0] === 0x12); } function bytesPerElement( TypedArray: TypedArrayConstructor, ): number { if ("BYTES_PER_ELEMENT" in TypedArray) { return TypedArray.BYTES_PER_ELEMENT as number; } // Unicode string array is backed by a Int32Array. return 4; } export class BytesCodec> { kind = "array_to_bytes"; #stride: Array; #TypedArray: TypedArrayConstructor; #BYTES_PER_ELEMENT: number; #shape: Array; #endian?: "little" | "big"; constructor( configuration: { endian?: "little" | "big" } | undefined, meta: { dataType: D; shape: number[]; codecs: CodecMetadata[] }, ) { this.#endian = configuration?.endian; this.#TypedArray = getCtr(meta.dataType); this.#shape = meta.shape; this.#stride = getStrides(meta.shape, "C"); // TODO: fix me. // hack to get bytes per element since it's dynamic for string types. const sample = new this.#TypedArray(0); this.#BYTES_PER_ELEMENT = sample.BYTES_PER_ELEMENT; } static fromConfig>( configuration: { endian: "little" | "big" }, meta: { dataType: D; shape: number[]; codecs: CodecMetadata[] }, ): BytesCodec { return new BytesCodec(configuration, meta); } encode(arr: Chunk): Uint8Array { let bytes = new Uint8Array(arr.data.buffer); if (LITTLE_ENDIAN_OS && this.#endian === "big") { bytes = bytes.slice(); byteswapInplace(bytes, bytesPerElement(this.#TypedArray)); } return bytes; } computeEncodedSize(decodedSize: number): number { return decodedSize; } decode(bytes: Uint8Array): Chunk { if (LITTLE_ENDIAN_OS && this.#endian === "big") { // Copy before swapping so we never mutate the input in place; it // may be a shared buffer (e.g. from `withByteCaching`). See #431. bytes = bytes.slice(); byteswapInplace(bytes, bytesPerElement(this.#TypedArray)); } if (bytes.byteOffset % this.#BYTES_PER_ELEMENT !== 0) { // Typed array views must start at an offset that's a multiple of // their element size. The store may hand back a view into a larger // buffer (e.g. a shard's suffix bytes) that doesn't happen to land // on such a boundary, so copy into a fresh, aligned buffer. bytes = bytes.slice(); } return { data: new this.#TypedArray( bytes.buffer, bytes.byteOffset, bytes.byteLength / this.#BYTES_PER_ELEMENT, ), shape: this.#shape, stride: this.#stride, }; } }