From c01171b2a20ee56ccaedd6aa83f90b71726310a1 Mon Sep 17 00:00:00 2001 From: Seiji Ainoguchi Date: Sat, 26 Oct 2024 09:10:10 +0000 Subject: [PATCH] 0.0.3 - feat: Implement automatic byteOffset alignment to prevent TypedArray view creation errors --- CHANGELOG.md | 20 +++++++++- README.md | 11 ++---- package.json | 2 +- src/index.ts | 58 +++++++++++++++------------- tests/createArrayBufferViews.test.ts | 40 +++++++++++++------ 5 files changed, 82 insertions(+), 49 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 145a0a7..a75b57e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,9 +4,21 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). -## [0.0.1] - 2024-10-26 +## [0.0.3] - 2024-10-26 -- Initial release +### Added + +- **Alignment Adjustment for `byteOffset`**: Implemented automatic alignment of each `TypedArray` view’s `byteOffset` to prevent errors during view creation. +- **README.md Update**: + - Added a note in the `Overview` section that `byteOffset` alignment is essential for preventing errors when creating `TypedArray` views. + +### Changed + +- **Function Adjustment**: Modified the `createArrayBufferViews` function to automatically align the `byteOffset` of each `TypedArray` view, preventing errors across different data types. + +### Fixed + +- **Error Prevention**: Enforced correct alignment during `TypedArray` view creation to resolve potential errors. ## [0.0.2] - 2024-10-26 @@ -14,3 +26,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), - Updated README to replace the "Documentation" badge with a "CI" badge to reflect the status of continuous integration workflows. - Updated the badge URL and link to point to the correct GitHub Actions workflow. + +## [0.0.1] - 2024-10-26 + +- Initial release diff --git a/README.md b/README.md index 192c0d4..8bf782a 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# @ain1084/array-buffer-partitioner +# Array Buffer Partitioner [![npm version](https://badge.fury.io/js/@ain1084%2Farray-buffer-partitioner.svg)](https://badge.fury.io/js/@ain1084%2Farray-buffer-partitioner) [![CI](https://github.com/ain1084/array-buffer-partitioner/actions/workflows/ci.yml/badge.svg)](https://github.com/ain1084/array-buffer-partitioner/actions?query=workflow%3Aci) @@ -9,20 +9,19 @@ Partition an ArrayBuffer into multiple TypedArray views efficiently, while handl ## Overview -`@ain1084/array-buffer-partitioner` is a utility library designed to create multiple `TypedArray` views on a single `ArrayBuffer` or `SharedArrayBuffer`. This can be particularly useful when dealing with various data types that need to coexist within a single buffer, without manually calculating offsets and ensuring correct alignment. +`@ain1084/array-buffer-partitioner` is a utility library for creating multiple TypedArray views on a single `ArrayBuffer` or `SharedArrayBuffer`. This allows various data types to be efficiently placed within a single buffer. The library automatically adjusts each TypedArray’s byteOffset to prevent errors that can occur during view creation. ### Features - Create multiple `TypedArray` views from a single `ArrayBuffer` or `SharedArrayBuffer`. - Efficient memory partitioning without manual offset calculations. -- Future-proof: planned support for alignment adjustment to optimize memory access performance. ## Installation Install the library via npm: ```sh -npm install @ain1084/array-buffer-partitioner +npm i @ain1084/array-buffer-partitioner ``` ## Usage @@ -63,10 +62,6 @@ Creates multiple `TypedArray` views on a single `ArrayBuffer` or `SharedArrayBuf An object containing the views, with each key corresponding to the provided configuration. -## Future Plans - -- **Alignment Support**: Future versions will include alignment support to ensure efficient access patterns, especially when working with mixed data types like `Float32Array` and `Uint8Array`. This will improve the performance of memory access and help avoid inefficient alignments. - ## Contributing Contributions are welcome! If you have ideas, suggestions, or issues, please create an issue on the [GitHub repository](https://github.com/ain1084/array-buffer-partitioner). diff --git a/package.json b/package.json index fc354a8..4b7620f 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@ain1084/array-buffer-partitioner", - "version": "0.0.2", + "version": "0.0.3", "description": "Partition an ArrayBuffer into multiple TypedArray views efficiently.", "module": "dist/index.js", "types": "dist/index.d.ts", diff --git a/src/index.ts b/src/index.ts index d78863a..4a51741 100644 --- a/src/index.ts +++ b/src/index.ts @@ -30,50 +30,54 @@ * ``` */ export function createArrayBufferViews, number]>>( - BufferType: { new(size: number): ArrayBuffer }, - config: T -): { - [K in keyof T]: InstanceType -} { - const totalSize = (Object.values(config) as ArrayView[]) - .reduce((sum, [type, size]) => - sum + (type.BYTES_PER_ELEMENT * size), - 0 - ); + BufferType: { new(size: number): ArrayBuffer }, + config: T +): { + [K in keyof T]: InstanceType + } { + const alignTo = (value: number, alignment: number) => + (value + alignment - 1) & ~(alignment - 1); - // Create an instance of the specified buffer type with the calculated total size - const buffer = new BufferType(totalSize); + let totalSize = 0; - const result = {} as { [K in keyof T]: InstanceType }; + for (const [, [type, size]] of Object.entries(config) as Array<[string, ArrayView]>) { + const alignment = type.BYTES_PER_ELEMENT; + totalSize = alignTo(totalSize, alignment) + (alignment * size); + } - let offset = 0; + const buffer = new BufferType(totalSize); + const result = {} as { [K in keyof T]: InstanceType }; - for (const key in config) { - const [type, size] = config[key]; - result[key] = new type(buffer, offset, size) as InstanceType; - offset += type.BYTES_PER_ELEMENT * size; - } + let offset = 0; - return result; + for (const key in config) { + const [type, size] = config[key]; + const alignment = type.BYTES_PER_ELEMENT; + offset = alignTo(offset, alignment); + result[key] = new type(buffer, offset, size) as InstanceType; + offset += alignment * size; + } + + return result; } /** * Represents the constructor for supported TypedArray types. * @internal */ -type ArrayTypeConstructor = { - new(buffer: ArrayBuffer, byteOffset: number, length: number): R; - BYTES_PER_ELEMENT: number; +type ArrayTypeConstructor = { + new(buffer: ArrayBuffer, byteOffset: number, length: number): R; + BYTES_PER_ELEMENT: number; }; /** * Lists the supported TypedArray types. * @internal */ -type SupportedArrays = - Float32Array | Uint32Array | Uint8Array | - Int32Array | Int16Array | Uint16Array | - Float64Array | BigInt64Array | BigUint64Array; +type SupportedArrays = + Float32Array | Uint32Array | Uint8Array | + Int32Array | Int16Array | Uint16Array | + Float64Array | BigInt64Array | BigUint64Array; /** * Represents the configuration for creating TypedArray views. diff --git a/tests/createArrayBufferViews.test.ts b/tests/createArrayBufferViews.test.ts index a9fac0d..58318a6 100644 --- a/tests/createArrayBufferViews.test.ts +++ b/tests/createArrayBufferViews.test.ts @@ -2,32 +2,50 @@ import { createArrayBufferViews } from '../src'; +const alignTo = (offset: number, alignment: number): number => { + return (offset + alignment - 1) & ~(alignment - 1) +} + describe('createArrayBufferViews', () => { it('should create the correct TypedArray views with ArrayBuffer', () => { const views = createArrayBufferViews(ArrayBuffer, { data: [Float32Array, 1024], index: [Uint32Array, 1], - flag: [Uint8Array, 1] + flag: [Uint8Array, 1], + u32: [Uint32Array, 1], + u64: [BigUint64Array, 1], }); + expect(views.data.byteOffset).toBe(0); expect(views.data.length).toBe(1024); + expect(views.index.byteOffset).toBe(1024 * Float32Array.BYTES_PER_ELEMENT); expect(views.index.length).toBe(1); + expect(views.flag.byteOffset).toBe(alignTo(views.index.byteOffset + Uint32Array.BYTES_PER_ELEMENT, Uint8Array.BYTES_PER_ELEMENT)); expect(views.flag.length).toBe(1); - expect(views.data.byteOffset).toBe(0); - expect(views.index.byteOffset).toBe(1024 * Float32Array.BYTES_PER_ELEMENT); - expect(views.flag.byteOffset).toBe(1024 * Float32Array.BYTES_PER_ELEMENT + Uint32Array.BYTES_PER_ELEMENT); + expect(views.u32.byteOffset).toBe(alignTo(views.flag.byteOffset + Uint8Array.BYTES_PER_ELEMENT, Uint32Array.BYTES_PER_ELEMENT)); + expect(views.u32.length).toBe(1); + expect(views.u64.byteOffset).toBe(alignTo(views.u32.byteOffset + Uint32Array.BYTES_PER_ELEMENT, BigUint64Array.BYTES_PER_ELEMENT)); + expect(views.u64.length).toBe(1); }); it('should create the correct TypedArray views with SharedArrayBuffer', () => { const views = createArrayBufferViews(SharedArrayBuffer, { - buffer1: [Int16Array, 512], - buffer2: [Uint8Array, 256] + data: [Float32Array, 1024], + index: [Uint32Array, 1], + flag: [Uint8Array, 1], + u32: [Uint32Array, 1], + u64: [BigUint64Array, 1], }); - - expect(views.buffer1.length).toBe(512); - expect(views.buffer2.length).toBe(256); - expect(views.buffer1.byteOffset).toBe(0); - expect(views.buffer2.byteOffset).toBe(512 * Int16Array.BYTES_PER_ELEMENT); + expect(views.data.byteOffset).toBe(0); + expect(views.data.length).toBe(1024); + expect(views.index.byteOffset).toBe(1024 * Float32Array.BYTES_PER_ELEMENT); + expect(views.index.length).toBe(1); + expect(views.flag.byteOffset).toBe(alignTo(views.index.byteOffset + Uint32Array.BYTES_PER_ELEMENT, Uint8Array.BYTES_PER_ELEMENT)); + expect(views.flag.length).toBe(1); + expect(views.u32.byteOffset).toBe(alignTo(views.flag.byteOffset + Uint8Array.BYTES_PER_ELEMENT, Uint32Array.BYTES_PER_ELEMENT)); + expect(views.u32.length).toBe(1); + expect(views.u64.byteOffset).toBe(alignTo(views.u32.byteOffset + Uint32Array.BYTES_PER_ELEMENT, BigUint64Array.BYTES_PER_ELEMENT)); + expect(views.u64.length).toBe(1); }); it('should throw an error if the buffer size is insufficient', () => {