diff --git a/src/types/_common.ts b/src/types/common.ts similarity index 82% rename from src/types/_common.ts rename to src/types/common.ts index c86d7f4..23496bc 100644 --- a/src/types/_common.ts +++ b/src/types/common.ts @@ -4,5 +4,6 @@ export interface Options { byteOffset: number; } +/** Extract the inner value of a codec */ export type InnerType = T extends Unsized ? I : never; export type ValueOf = T[keyof T]; diff --git a/src/types/mod.ts b/src/types/mod.ts index 64f8c69..951f16b 100644 --- a/src/types/mod.ts +++ b/src/types/mod.ts @@ -1,3 +1,3 @@ -export * from "./_common.ts"; +export * from "./common.ts"; export { SizedType } from "./sized.ts"; export { UnsizedType } from "./unsized.ts"; diff --git a/src/types/sized.ts b/src/types/sized.ts index 9d8141a..1d2aafe 100644 --- a/src/types/sized.ts +++ b/src/types/sized.ts @@ -1,19 +1,27 @@ import { type Unsized, UnsizedType } from "./unsized.ts"; -import type { Options } from "./_common.ts"; +import type { Options } from "./common.ts"; interface Sized extends Unsized { readonly byteSize: number; } +/** + * `SizedType` is one of the two base classes for implementing a codec. + * + * It is recommended to use this class if you know the size of your type ahead of time. + * In future released this may be used for certain optimizations + */ export abstract class SizedType extends UnsizedType implements Sized { constructor(readonly byteSize: number, byteAlignment: number = 1) { super(byteAlignment); } + /** Increments offset by `this.byteSize` */ protected override incrementOffset(options: Options): void { super.incrementOffset(options, this.byteSize); } + /** Allows you to check upfront if you will go out of bound */ protected rangeCheck(byteLength: number, offset: number): void { if (this.byteSize > (byteLength - offset)) { throw new RangeError("Out of bound"); diff --git a/src/types/unsized.ts b/src/types/unsized.ts index 6ed20f1..e808540 100644 --- a/src/types/unsized.ts +++ b/src/types/unsized.ts @@ -1,5 +1,5 @@ import { align } from "../util.ts"; -import type { Options } from "./_common.ts"; +import type { Options } from "./common.ts"; export interface Unsized { readonly byteAlignment: number; @@ -8,28 +8,66 @@ export interface Unsized { writePacked(value: T, dt: DataView, options?: Options): void; } +/** + * `UnsizedType` is one of the two base classes for implementing a codec. + * + * This is the most common used class for when you do not know the size of your struct. + */ export abstract class UnsizedType implements Unsized { constructor(readonly byteAlignment: number) {} + /** + * Read a value from the provided buffer while consuming as little bytes as possible. + * This method is used for data over the network or when reading memory that may not be aligned + * + * ### Implementors be aware! + * - This method is the base functionality of `Unsized.read()` if that method is not overridden. + * - This method does not automatically offset or align the `Options.byteOffset`. You need to do this yourself. + */ abstract readPacked(dt: DataView, options?: Options): T; + + /** + * write a value into the provided buffer while consuming as little bytes as possible. + * This method is used for data over the network or when writing memory that may not be aligned + * + * ### Implementors be aware! + * - This method is the base functionality of `Unsized.write()` if it's not implemented + * - This method does not automatically offset or align the `Options.byteOffset`. You need to do this yourself + */ abstract writePacked(value: T, dt: DataView, options?: Options): void; - /** In most cases you don't need to reimplement read. as long as your `readPacked` is correct */ + /** + * Read a aligned value from the provided buffer and optional byte offset + * + * ### Implementors be aware + * - This is a function that is automatically implemented but may not be correct all the time. + * - This function only works in a vacuum. This means that it will only work on single types. + * - Composable types may not give the correct result and need to be written from scratch. + */ read(dt: DataView, options: Options = { byteOffset: 0 }): T { this.alignOffset(options); return this.readPacked(dt, options); } - /** In most cases you don't need to reimplement write. as long as your `writePacked` is correct */ + /** + * Write a value into the provided buffer at a optional offset that is automatically aligned + * + * ### Implementors be aware + * - This is a function that is automatically implemented but may not be correct all the time. + * - This function only works in a vacuum. This means that it will only work on single types. + * - Composable types may not give the correct result and need to be written from scratch. + */ write(value: T, dt: DataView, options: Options = { byteOffset: 0 }): void { this.alignOffset(options); this.writePacked(value, dt, options); } + /** Align the offset of `Options.byteOffset` to the nearest integer divisable by `UnsizedType.byteAlignment` */ protected alignOffset(options: Options) { options.byteOffset = align(options.byteOffset, this.byteAlignment); } + /** Increment offset by the provided `byteSize` */ protected incrementOffset(options: Options, byteSize: number) { options.byteOffset += byteSize; } diff --git a/src/util.ts b/src/util.ts index dc35bf4..65d928a 100644 --- a/src/util.ts +++ b/src/util.ts @@ -9,11 +9,13 @@ export const isLittleEndian = (() => { return new Uint16Array(buffer)[0] === 256; })(); +/** Align the value `unaligned` to the first integer that is divisible by `alignment` */ export const align = (unaligned: number, alignment: number) => (unaligned + alignment - 1) & ~(alignment - 1); type ArrayOrRecord = T[] | Record; +/** Find and returns the biggest alignment out of a record / array of types */ export const getBiggestAlignment = ( input: ArrayOrRecord>, ) =>