Add many typed array helpers

This commit is contained in:
Sirz Benjie 2025-09-11 19:56:33 -05:00
parent 0ebdc1b0ef
commit 284df1ac1a
No known key found for this signature in database
GPG Key ID: 4A524B4D196C759E
7 changed files with 764 additions and 22 deletions

526
src/@types/typed-arrays.ts Normal file
View File

@ -0,0 +1,526 @@
/*
* SPDX-FileCopyrightText: 2025 Pagefault Games
* SPDX-FileContributor: SirzBenjie
*
* SPDX-License-Identifier: AGPL-3.0-only
*/
/**
* Collection of utility types for working with
* {@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/TypedArray | TypedArray}
* with enhanced type safety and usability.
* @module
*/
/**
* Union type of all {@linkcode https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/TypedArray | TypedArray}s
*/
export type TypedArray =
| Int8Array
| Uint8Array
| Uint8ClampedArray
| Int16Array
| Uint16Array
| Int32Array
| Uint32Array
| Float32Array
| Float64Array
| BigInt64Array
| BigUint64Array;
/**
* A readonly version of {@linkcode https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Int8Array | Int8Array}
*
* @remarks
* Is to `Int8Array` what `ReadonlyArray` is to `Array`
*/
export interface ReadonlyInt8Array<TArrayBuffer extends ArrayBufferLike = ArrayBuffer>
extends Omit<Int8Array, "fill" | "set" | "sort" | "reverse" | "copyWithin" | "subarray"> {
subarray(begin?: number, end?: number): ReadonlyInt8Array<TArrayBuffer>;
readonly [index: number]: number;
}
/**
* A readonly version of {@linkcode https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Uint8Array | Uint8Array}
*
* @remarks
* Is to `Uint8Array` what `ReadonlyArray` is to `Array`
*/
export interface ReadonlyUint8Array<TArrayBuffer extends ArrayBufferLike = ArrayBuffer>
extends Omit<Uint8Array, "fill" | "set" | "sort" | "reverse" | "copyWithin" | "subarray"> {
subarray(begin?: number, end?: number): ReadonlyUint8Array<TArrayBuffer>;
readonly [index: number]: number;
}
/**
* A readonly version of {@linkcode https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Uint8ClampedArray | Uint8ClampedArray}
*
* @remarks
* Is to `Uint8ClampedArray` what `ReadonlyArray` is to `Array`
*/
export interface ReadonlyUint8ClampedArray<TArrayBuffer extends ArrayBufferLike = ArrayBuffer>
extends Omit<Uint8ClampedArray<TArrayBuffer>, "fill" | "set" | "sort" | "reverse" | "copyWithin" | "subarray"> {
subarray(begin?: number, end?: number): ReadonlyUint8ClampedArray<TArrayBuffer>;
readonly [index: number]: number;
}
/**
* A readonly version of {@linkcode https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Int16Array | Int16Array}
*
* @remarks
* Is to `Int16Array` what `ReadonlyArray` is to `Array`
*/
export interface ReadonlyInt16Array<TArrayBuffer extends ArrayBufferLike = ArrayBuffer>
extends Omit<Int16Array<TArrayBuffer>, "fill" | "set" | "sort" | "reverse" | "copyWithin" | "subarray"> {
subarray(begin?: number, end?: number): ReadonlyInt16Array<TArrayBuffer>;
readonly [index: number]: number;
}
/**
* A readonly version of {@linkcode https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Uint16Array | Uint16Array}
*
* @remarks
* Is to `Uint16Array` what `ReadonlyArray` is to `Array`
*/
export interface ReadonlyUint16Array<TArrayBuffer extends ArrayBufferLike = ArrayBuffer>
extends Omit<Uint16Array<TArrayBuffer>, "fill" | "set" | "sort" | "reverse" | "copyWithin" | "subarray"> {
subarray(begin?: number, end?: number): ReadonlyUint16Array<TArrayBuffer>;
readonly [index: number]: number;
}
/**
* A readonly version of {@linkcode https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Int32Array | Int32Array}
*
* @remarks
* Is to `Int32Array` what `ReadonlyArray` is to `Array`
*/
export interface ReadonlyInt32Array<TArrayBuffer extends ArrayBufferLike = ArrayBuffer>
extends Omit<Int32Array<TArrayBuffer>, "fill" | "set" | "sort" | "reverse" | "copyWithin" | "subarray"> {
subarray(begin?: number, end?: number): ReadonlyInt32Array<TArrayBuffer>;
readonly [index: number]: number;
}
/**
* A readonly version of {@linkcode https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Uint32Array | Uint32Array}
*
* @remarks
* Is to `Uint32Array` what `ReadonlyArray` is to `Array`
*/
export interface ReadonlyUint32Array<TArrayBuffer extends ArrayBufferLike = ArrayBuffer>
extends Omit<Uint32Array<TArrayBuffer>, "fill" | "set" | "sort" | "reverse" | "copyWithin" | "subarray"> {
subarray(begin?: number, end?: number): ReadonlyUint32Array<TArrayBuffer>;
readonly [index: number]: number;
}
/**
* A readonly version of {@linkcode https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Float32Array | Float32Array}
*
* @remarks
* Is to `Float32Array` what `ReadonlyArray` is to `Array`
*/
export interface ReadonlyFloat32Array<TArrayBuffer extends ArrayBufferLike = ArrayBuffer>
extends Omit<Float32Array<TArrayBuffer>, "fill" | "set" | "sort" | "reverse" | "copyWithin" | "subarray"> {
subarray(begin?: number, end?: number): ReadonlyFloat32Array<TArrayBuffer>;
readonly [index: number]: number;
}
/**
* A readonly version of {@linkcode https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Float64Array | Float64Array}
*
* @remarks
* Is to `Float64Array` what `ReadonlyArray` is to `Array`
*/
export interface ReadonlyFloat64Array<TArrayBuffer extends ArrayBufferLike = ArrayBuffer>
extends Omit<Float64Array, "fill" | "set" | "sort" | "reverse" | "copyWithin" | "subarray"> {
subarray(begin?: number, end?: number): ReadonlyFloat64Array<TArrayBuffer>;
readonly [index: number]: number;
}
/**
* A readonly version of {@linkcode https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/BigInt64Array | BigInt64Array}
*
* @remarks
* Is to `BigInt64Array` what `ReadonlyArray` is to `Array`
*/
export interface ReadonlyBigInt64Array<TArrayBuffer extends ArrayBufferLike = ArrayBuffer>
extends Omit<BigInt64Array, "fill" | "set" | "sort" | "reverse" | "copyWithin" | "subarray"> {
subarray(begin?: number, end?: number): ReadonlyBigInt64Array<TArrayBuffer>;
readonly [index: number]: bigint;
}
/**
* A readonly version of {@linkcode https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/BigUint64Array | BigUint64Array}
*
* @remarks
* Is to `BigUint64Array` what `ReadonlyArray` is to `Array`
*/
export interface ReadonlyBigUint64Array<TArrayBuffer extends ArrayBufferLike = ArrayBuffer>
extends Omit<BigUint64Array, "fill" | "set" | "sort" | "reverse" | "copyWithin" | "subarray"> {
subarray(begin?: number, end?: number): ReadonlyBigUint64Array<TArrayBuffer>;
readonly [index: number]: bigint;
}
export type ReadonlyTypedArray =
| ReadonlyInt8Array
| ReadonlyUint8Array
| ReadonlyUint8ClampedArray
| ReadonlyInt16Array
| ReadonlyUint16Array
| ReadonlyInt32Array
| ReadonlyUint32Array
| ReadonlyFloat32Array
| ReadonlyFloat64Array
| ReadonlyBigInt64Array
| ReadonlyBigUint64Array;
/**
* Either {@linkcode https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/BigInt64Array | BigInt64Array}
* or {@linkcode https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/BigInt64Array | BigUint64Array}
*/
export type BigIntArray = BigInt64Array | BigUint64Array;
/** Any {@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/TypedArray | TypedArray} whose elements are not `bigint`s */
export type NumberCompatibleTypedArray = Exclude<TypedArray, BigIntArray>;
/**
* A partial interface of `Uint8Array` where methods that return the array type have been modified to return a more specific type.
*
* @privateRemarks
* Excludes methods that return a new array (e.g. `subarray`, `slice`, `toReversed`, `toSorted`, `with`) as these cannot be resolved by typescript
* @internal
*/
interface PartialUint8Array<T extends number> extends Uint8Array {
at(index: number): T | undefined; // ES2022
entries(): ArrayIterator<[number, T]>;
fill(value: T, start?: number, end?: number): this;
find(predicate: (value: T, index: number, array: this) => boolean, thisArg?: any): T | undefined;
findLast(predicate: (value: number, index: number, array: this) => boolean, thisArg?: any): T | undefined;
forEach(callbackfn: (value: T, index: number, array: this) => void, thisArg?: any): void;
includes(searchElement: T, fromIndex?: number): boolean;
indexOf(searchElement: T, fromIndex?: number): number;
lastIndexOf(searchElement: T, fromIndex?: number): number;
reduce(callbackfn: (previousValue: T, currentValue: T, currentIndex: number, array: this) => T): T;
reduceRight(callbackfn: (previousValue: T, currentValue: T, currentIndex: number, array: this) => T): T;
set(array: ArrayLike<T>, offset?: number): void;
some(predicate: (value: T, index: number, obj: this) => boolean, thisArg?: any): boolean;
sort(compareFn?: (a: T, b: T) => number): this;
values(): ArrayIterator<T>;
[Symbol.iterator](): ArrayIterator<T>;
[index: number]: T;
}
/**
* A `Uint8Array` whose elements typescript considers to be of type `T`, allowing for type-safe iteration and access.
*
* @remarks
* Useful to leverage the benefits of `TypedArrays` without losing type information. Typescript will consider the elements to be of type `T` instead of just `number`.
* @typeParam T - The specific numeric type of the elements in the array.
*/
// @ts-expect-error - These methods _will_ error, as we are overriding the return type to be more specific
// in a way that makes typescript unhappy
export interface GenericUint8Array<T extends number> extends PartialUint8Array<T> {
// map(callbackfn: (value: T, index: number, array: this) => T, thisArg?: any): GenericUint8Array<T>;
// this method does not trigger a typescript error on its own, but if we add in `toReversed` it causes issues....
subarray(begin?: number, end?: number): GenericUint8Array<T>;
toReversed(): GenericUint8Array<T>;
toSorted(compareFn?: (a: T, b: T) => number): GenericUint8Array<T>;
filter(predicate: (value: T, index: number, array: this) => any, thisArg?: any): GenericUint8Array<T>;
}
/**
* A partial interface of `Uint16Array` where methods that return the array type have been modified to return a more specific type.
*
* @privateRemarks
* Excludes methods that return a new array (e.g. `subarray`, `slice`, `toReversed`, `toSorted`, `with`) as these cannot be resolved by typescript
* @internal
*/
interface PartialUint16Array<T extends number> extends Uint16Array {
at(index: number): T | undefined; // ES2022
entries(): ArrayIterator<[number, T]>;
fill(value: T, start?: number, end?: number): this;
find(predicate: (value: T, index: number, array: this) => boolean, thisArg?: any): T | undefined;
findLast(predicate: (value: number, index: number, array: this) => boolean, thisArg?: any): T | undefined;
forEach(callbackfn: (value: T, index: number, array: this) => void, thisArg?: any): void;
includes(searchElement: T, fromIndex?: number): boolean;
indexOf(searchElement: T, fromIndex?: number): number;
lastIndexOf(searchElement: T, fromIndex?: number): number;
reduce(callbackfn: (previousValue: T, currentValue: T, currentIndex: number, array: this) => T): T;
reduceRight(callbackfn: (previousValue: T, currentValue: T, currentIndex: number, array: this) => T): T;
set(array: ArrayLike<T>, offset?: number): void;
some(predicate: (value: T, index: number, obj: this) => boolean, thisArg?: any): boolean;
sort(compareFn?: (a: T, b: T) => number): this;
values(): ArrayIterator<T>;
[Symbol.iterator](): ArrayIterator<T>;
[index: number]: T;
}
/**
* A `Uint16Array` whose elements typescript considers to be of type `T`, allowing for type-safe iteration and access.
*
* @remarks
* Useful to leverage the benefits of `TypedArrays` without losing type information. Typescript will consider the elements to be of type `T` instead of just `number`.
* @typeParam T - The specific numeric type of the elements in the array.
*/
// @ts-expect-error - These methods _will_ error, as we are overriding the return type to be more specific
// in a way that makes typescript unhappy
export interface GenericUint16Array<T extends number> extends PartialUint16Array<T> {
// map(callbackfn: (value: T, index: number, array: this) => T, thisArg?: any): GenericUint16Array<T>;
// this method does not trigger a typescript error on its own, but if we add in `toReversed` it causes issues....
subarray(begin?: number, end?: number): GenericUint16Array<T>;
toReversed(): GenericUint16Array<T>;
toSorted(compareFn?: (a: T, b: T) => number): GenericUint16Array<T>;
filter(predicate: (value: T, index: number, array: this) => any, thisArg?: any): GenericUint16Array<T>;
}
/**
* A partial interface of `Uint32Array` where methods that return the array type have been modified to return a more specific type.
*
* @privateRemarks
* Excludes methods that return a new array (e.g. `subarray`, `slice`, `toReversed`, `toSorted`, `with`) as these cannot be resolved by typescript
* @internal
*/
interface PartialUint32Array<T extends number> extends Uint32Array {
at(index: number): T | undefined; // ES2022
entries(): ArrayIterator<[number, T]>;
fill(value: T, start?: number, end?: number): this;
find(predicate: (value: T, index: number, array: this) => boolean, thisArg?: any): T | undefined;
findLast(predicate: (value: number, index: number, array: this) => boolean, thisArg?: any): T | undefined;
forEach(callbackfn: (value: T, index: number, array: this) => void, thisArg?: any): void;
includes(searchElement: T, fromIndex?: number): boolean;
indexOf(searchElement: T, fromIndex?: number): number;
lastIndexOf(searchElement: T, fromIndex?: number): number;
reduce(callbackfn: (previousValue: T, currentValue: T, currentIndex: number, array: this) => T): T;
reduceRight(callbackfn: (previousValue: T, currentValue: T, currentIndex: number, array: this) => T): T;
set(array: ArrayLike<T>, offset?: number): void;
some(predicate: (value: T, index: number, obj: this) => boolean, thisArg?: any): boolean;
sort(compareFn?: (a: T, b: T) => number): this;
values(): ArrayIterator<T>;
[Symbol.iterator](): ArrayIterator<T>;
[index: number]: T;
}
/**
* A `Uint32Array` whose elements typescript considers to be of type `T`, allowing for type-safe iteration and access.
*
* @remarks
* Useful to leverage the benefits of `TypedArrays` without losing type information. Typescript will consider the elements to be of type `T` instead of just `number`.
* @typeParam T - The specific numeric type of the elements in the array.
*/
// @ts-expect-error - These methods _will_ error, as we are overriding the return type to be more specific
// in a way that makes typescript unhappy
export interface GenericUint32Array<T extends number> extends PartialUint32Array<T> {
// map(callbackfn: (value: T, index: number, array: this) => T, thisArg?: any): GenericUint32Array<T>;
// this method does not trigger a typescript error on its own, but if we add in `toReversed` it causes issues....
subarray(begin?: number, end?: number): GenericUint32Array<T>;
toReversed(): GenericUint32Array<T>;
toSorted(compareFn?: (a: T, b: T) => number): GenericUint32Array<T>;
filter(predicate: (value: T, index: number, array: this) => any, thisArg?: any): GenericUint32Array<T>;
}
/**
* A partial interface of `Int8Array` where methods that return the array type have been modified to return a more specific type.
*
* @privateRemarks
* Excludes methods that return a new array (e.g. `subarray`, `slice`, `toReversed`, `toSorted`, `with`) as these cannot be resolved by typescript
* @internal
*/
interface PartialInt8Array<T extends number> extends Int8Array {
at(index: number): T | undefined; // ES2022
entries(): ArrayIterator<[number, T]>;
fill(value: T, start?: number, end?: number): this;
find(predicate: (value: T, index: number, array: this) => boolean, thisArg?: any): T | undefined;
findLast(predicate: (value: number, index: number, array: this) => boolean, thisArg?: any): T | undefined;
forEach(callbackfn: (value: T, index: number, array: this) => void, thisArg?: any): void;
includes(searchElement: T, fromIndex?: number): boolean;
indexOf(searchElement: T, fromIndex?: number): number;
lastIndexOf(searchElement: T, fromIndex?: number): number;
reduce(callbackfn: (previousValue: T, currentValue: T, currentIndex: number, array: this) => T): T;
reduceRight(callbackfn: (previousValue: T, currentValue: T, currentIndex: number, array: this) => T): T;
set(array: ArrayLike<T>, offset?: number): void;
some(predicate: (value: T, index: number, obj: this) => boolean, thisArg?: any): boolean;
sort(compareFn?: (a: T, b: T) => number): this;
values(): ArrayIterator<T>;
[Symbol.iterator](): ArrayIterator<T>;
[index: number]: T;
}
/**
* A `Int8Array` whose elements typescript considers to be of type `T`, allowing for type-safe iteration and access.
*
* @remarks
* Useful to leverage the benefits of `TypedArrays` without losing type information. Typescript will consider the elements to be of type `T` instead of just `number`.
* @typeParam T - The specific numeric type of the elements in the array.
*/
// @ts-expect-error - These methods _will_ error, as we are overriding the return type to be more specific
// in a way that makes typescript unhappy
export interface GenericInt8Array<T extends number> extends PartialInt8Array<T> {
// map(callbackfn: (value: T, index: number, array: this) => T, thisArg?: any): GenericInt8Array<T>;
// this method does not trigger a typescript error on its own, but if we add in `toReversed` it causes issues....
subarray(begin?: number, end?: number): GenericInt8Array<T>;
toReversed(): GenericInt8Array<T>;
toSorted(compareFn?: (a: T, b: T) => number): GenericInt8Array<T>;
filter(predicate: (value: T, index: number, array: this) => any, thisArg?: any): GenericInt8Array<T>;
}
/**
* A partial interface of `Int16Array` where methods that return the array type have been modified to return a more specific type.
*
* @privateRemarks
* Excludes methods that return a new array (e.g. `subarray`, `slice`, `toReversed`, `toSorted`, `with`) as these cannot be resolved by typescript
* @internal
*/
interface PartialInt16Array<T extends number> extends Int16Array {
at(index: number): T | undefined; // ES2022
entries(): ArrayIterator<[number, T]>;
fill(value: T, start?: number, end?: number): this;
find(predicate: (value: T, index: number, array: this) => boolean, thisArg?: any): T | undefined;
findLast(predicate: (value: number, index: number, array: this) => boolean, thisArg?: any): T | undefined;
forEach(callbackfn: (value: T, index: number, array: this) => void, thisArg?: any): void;
includes(searchElement: T, fromIndex?: number): boolean;
indexOf(searchElement: T, fromIndex?: number): number;
lastIndexOf(searchElement: T, fromIndex?: number): number;
reduce(callbackfn: (previousValue: T, currentValue: T, currentIndex: number, array: this) => T): T;
reduceRight(callbackfn: (previousValue: T, currentValue: T, currentIndex: number, array: this) => T): T;
set(array: ArrayLike<T>, offset?: number): void;
some(predicate: (value: T, index: number, obj: this) => boolean, thisArg?: any): boolean;
sort(compareFn?: (a: T, b: T) => number): this;
values(): ArrayIterator<T>;
[Symbol.iterator](): ArrayIterator<T>;
[index: number]: T;
}
/**
* A `Int16Array` whose elements typescript considers to be of type `T`, allowing for type-safe iteration and access.
*
* @remarks
* Useful to leverage the benefits of `TypedArrays` without losing type information. Typescript will consider the elements to be of type `T` instead of just `number`.
* @typeParam T - The specific numeric type of the elements in the array.
*/
// @ts-expect-error - These methods _will_ error, as we are overriding the return type to be more specific
// in a way that makes typescript unhappy
export interface GenericInt16Array<T extends number> extends PartialInt16Array<T> {
// map(callbackfn: (value: T, index: number, array: this) => T, thisArg?: any): GenericInt16Array<T>;
// this method does not trigger a typescript error on its own, but if we add in `toReversed` it causes issues....
subarray(begin?: number, end?: number): GenericInt16Array<T>;
toReversed(): GenericInt16Array<T>;
toSorted(compareFn?: (a: T, b: T) => number): GenericInt16Array<T>;
filter(predicate: (value: T, index: number, array: this) => any, thisArg?: any): GenericInt16Array<T>;
}
/**
* A partial interface of `Int32Array` where methods that return the array type have been modified to return a more specific type.
*
* @privateRemarks
* Excludes methods that return a new array (e.g. `subarray`, `slice`, `toReversed`, `toSorted`, `with`) as these cannot be resolved by typescript
* @internal
*/
interface PartialInt32Array<T extends number> extends Int32Array {
at(index: number): T | undefined; // ES2022
entries(): ArrayIterator<[number, T]>;
fill(value: T, start?: number, end?: number): this;
find(predicate: (value: T, index: number, array: this) => boolean, thisArg?: any): T | undefined;
findLast(predicate: (value: number, index: number, array: this) => boolean, thisArg?: any): T | undefined;
forEach(callbackfn: (value: T, index: number, array: this) => void, thisArg?: any): void;
includes(searchElement: T, fromIndex?: number): boolean;
indexOf(searchElement: T, fromIndex?: number): number;
lastIndexOf(searchElement: T, fromIndex?: number): number;
reduce(callbackfn: (previousValue: T, currentValue: T, currentIndex: number, array: this) => T): T;
reduceRight(callbackfn: (previousValue: T, currentValue: T, currentIndex: number, array: this) => T): T;
set(array: ArrayLike<T>, offset?: number): void;
some(predicate: (value: T, index: number, obj: this) => boolean, thisArg?: any): boolean;
sort(compareFn?: (a: T, b: T) => number): this;
values(): ArrayIterator<T>;
[Symbol.iterator](): ArrayIterator<T>;
[index: number]: T;
}
/**
* A `Int32Array` whose elements typescript considers to be of type `T`, allowing for type-safe iteration and access.
*
* @remarks
* Useful to leverage the benefits of `TypedArrays` without losing type information. Typescript will consider the elements to be of type `T` instead of just `number`.
* @typeParam T - The specific numeric type of the elements in the array.
*/
// @ts-expect-error - These methods _will_ error, as we are overriding the return type to be more specific
// in a way that makes typescript unhappy
export interface GenericInt32Array<T extends number> extends PartialInt32Array<T> {
// map(callbackfn: (value: T, index: number, array: this) => T, thisArg?: any): GenericInt32Array<T>;
// this method does not trigger a typescript error on its own, but if we add in `toReversed` it causes issues....
subarray(begin?: number, end?: number): GenericInt32Array<T>;
toReversed(): GenericInt32Array<T>;
toSorted(compareFn?: (a: T, b: T) => number): GenericInt32Array<T>;
filter(predicate: (value: T, index: number, array: this) => any, thisArg?: any): GenericInt32Array<T>;
}
/**
* A readonly version of {@link GenericUint8Array} where elements are of type `T`.
* @typeParam T - The specific numeric type of the elements in the array.
* @remarks
* Is to `GenericUint8Array` what `ReadonlyArray` is to `Array`
*/
export interface ReadonlyGenericUint8Array<T extends number>
extends Omit<GenericUint8Array<T>, "fill" | "set" | "sort" | "reverse" | "copyWithin" | "subarray"> {
subarray(begin?: number, end?: number): ReadonlyGenericUint8Array<T>;
readonly [index: number]: T;
}
/**
* A readonly version of {@link GenericUint16Array} where elements are of type `T`.
* @typeParam T - The specific numeric type of the elements in the array.
* @remarks
* Is to `GenericUint16Array` what `ReadonlyArray` is to `Array`
*/
export interface ReadonlyGenericUint16Array<T extends number>
extends Omit<GenericUint16Array<T>, "fill" | "set" | "sort" | "reverse" | "copyWithin" | "subarray"> {
subarray(begin?: number, end?: number): ReadonlyGenericUint16Array<T>;
readonly [index: number]: T;
}
/**
* A readonly version of {@link GenericUint32Array} where elements are of type `T`.
* @typeParam T - The specific numeric type of the elements in the array.
* @remarks
* Is to `GenericUint32Array` what `ReadonlyArray` is to `Array`
*/
export interface ReadonlyGenericUint32Array<T extends number>
extends Omit<GenericUint32Array<T>, "fill" | "set" | "sort" | "reverse" | "copyWithin" | "subarray"> {
subarray(begin?: number, end?: number): ReadonlyGenericUint32Array<T>;
readonly [index: number]: T;
}
/**
* A readonly version of {@link GenericInt8Array} where elements are of type `T`.
* @typeParam T - The specific numeric type of the elements in the array.
* @remarks
* Is to `GenericInt8Array` what `ReadonlyArray` is to `Array`
*/
export interface ReadonlyGenericInt8Array<T extends number>
extends Omit<GenericInt8Array<T>, "fill" | "set" | "sort" | "reverse" | "copyWithin" | "subarray"> {
subarray(begin?: number, end?: number): ReadonlyGenericInt8Array<T>;
readonly [index: number]: T;
}
/**
* A readonly version of {@link GenericInt16Array} where elements are of type `T`.
* @typeParam T - The specific numeric type of the elements in the array.
* @remarks
* Is to `GenericInt16Array` what `ReadonlyArray` is to `Array`
*/
export interface ReadonlyGenericInt16Array<T extends number>
extends Omit<GenericInt16Array<T>, "fill" | "set" | "sort" | "reverse" | "copyWithin" | "subarray"> {
subarray(begin?: number, end?: number): ReadonlyGenericInt16Array<T>;
readonly [index: number]: T;
}
/**
* A readonly version of {@link GenericInt32Array} where elements are of type `T`.
* @typeParam T - The specific numeric type of the elements in the array.
* @remarks
* Is to `GenericInt32Array` what `ReadonlyArray` is to `Array`
*/
export interface ReadonlyGenericInt32Array<T extends number>
extends Omit<GenericInt32Array<T>, "fill" | "set" | "sort" | "reverse" | "copyWithin" | "subarray"> {
subarray(begin?: number, end?: number): ReadonlyGenericInt32Array<T>;
readonly [index: number]: T;
}
/**
* A union type of all `GenericTypedArray`s
*/
export type ReadonlyGenericArray<T extends number = number> =
| ReadonlyGenericUint8Array<T>
| ReadonlyGenericUint16Array<T>
| ReadonlyGenericUint32Array<T>
| ReadonlyGenericInt8Array<T>
| ReadonlyGenericInt16Array<T>
| ReadonlyGenericInt32Array<T>;

View File

@ -866,7 +866,7 @@ export class BattleScene extends SceneBase {
gender?: Gender,
shiny?: boolean,
variant?: Variant,
ivs?: Uint8Array,
ivs?: number[],
nature?: Nature,
dataSource?: Pokemon | PokemonData,
postProcess?: (playerPokemon: PlayerPokemon) => void,

View File

@ -16,6 +16,7 @@ import type { AttackMoveResult } from "#types/attack-move-result";
import type { IllusionData } from "#types/illusion-data";
import type { TurnMove } from "#types/turn-move";
import type { CoerceNullPropertiesToUndefined } from "#types/type-helpers";
import { setTypedArray } from "#utils/common";
import { getPokemonSpeciesForm } from "#utils/pokemon-utils";
/**
@ -129,7 +130,7 @@ export class PokemonSummonData {
public passiveAbility: AbilityId | undefined;
public gender: Gender | undefined;
public fusionGender: Gender | undefined;
public stats: number[] = [0, 0, 0, 0, 0, 0];
public stats: Uint32Array = new Uint32Array(6);
public moveset: PokemonMove[] | null;
// If not initialized this value will not be populated from save data.
@ -165,6 +166,11 @@ export class PokemonSummonData {
continue;
}
if (key === "stats") {
setTypedArray(this.stats, source.stats);
continue;
}
if (key === "illusion" && typeof value === "object") {
// Make a copy so as not to mutate provided value
const illusionData = {
@ -221,8 +227,10 @@ export class PokemonSummonData {
// We coerce null to undefined in the type, as the for loop below replaces `null` with `undefined`
...(this as Omit<
CoerceNullPropertiesToUndefined<PokemonSummonData>,
"speciesForm" | "fusionSpeciesForm" | "illusion"
"speciesForm" | "fusionSpeciesForm" | "illusion" | "stats"
>),
// TypedArrays do not serialize to JSON as an array.
stats: Array.from(this.stats),
speciesForm: speciesForm == null ? undefined : { id: speciesForm.speciesId, formIdx: speciesForm.formIndex },
fusionSpeciesForm:
fusionSpeciesForm == null

View File

@ -168,6 +168,7 @@ import {
rgbaToInt,
rgbHexToRgba,
rgbToHsv,
subArray,
toDmgValue,
} from "#utils/common";
import { getEnumValues } from "#utils/enums";
@ -203,7 +204,7 @@ export abstract class Pokemon extends Phaser.GameObjects.Container {
public levelExp: number;
public gender: Gender;
public hp: number;
public stats: number[];
public stats = new Uint32Array(6).fill(1);
public ivs: Uint8Array;
public nature: Nature;
public moveset: PokemonMove[];
@ -311,7 +312,7 @@ export abstract class Pokemon extends Phaser.GameObjects.Container {
gender?: Gender,
shiny?: boolean,
variant?: Variant,
ivs?: Uint8Array,
ivs?: Uint8Array | number[],
nature?: Nature,
dataSource?: Pokemon | PokemonData,
) {
@ -345,9 +346,8 @@ export abstract class Pokemon extends Phaser.GameObjects.Container {
if (dataSource) {
this.id = dataSource.id;
this.hp = dataSource.hp;
this.stats = dataSource.stats;
this.ivs = new Uint8Array(dataSource.ivs);
this.stats.set(subArray(dataSource.stats, 6));
this.ivs.set(subArray(dataSource.ivs, 6));
this.passive = !!dataSource.passive;
if (this.variant === undefined) {
this.variant = 0;
@ -386,7 +386,7 @@ export abstract class Pokemon extends Phaser.GameObjects.Container {
this.stellarTypesBoosted = dataSource.stellarTypesBoosted ?? [];
} else {
this.id = randSeedInt(4294967296);
this.ivs = new Uint8Array(ivs || getIvsFromId(this.id));
this.ivs.set(subArray(ivs ?? getIvsFromId(this.id), 6));
if (this.gender === undefined) {
this.gender = this.species.generateGender();
@ -1320,7 +1320,7 @@ export abstract class Pokemon extends Phaser.GameObjects.Container {
* @param bypassSummonData - Whether to prefer actual stats (`true`) or in-battle overridden stats (`false`); default `true`
* @returns The numeric values of this {@linkcode Pokemon}'s stats as an array.
*/
getStats(bypassSummonData = true): number[] {
getStats(bypassSummonData = true): ArrayLike<number> {
if (!bypassSummonData) {
// Only grab summon data stats if nonzero
return this.summonData.stats.map((s, i) => s || this.stats[i]);
@ -1552,10 +1552,6 @@ export abstract class Pokemon extends Phaser.GameObjects.Container {
}
calculateStats(): void {
if (!this.stats) {
this.stats = [0, 0, 0, 0, 0, 0];
}
// Get and manipulate base stats
const baseStats = this.calculateBaseStats();
// Using base stats, calculate and store stats one by one
@ -1588,7 +1584,7 @@ export abstract class Pokemon extends Phaser.GameObjects.Container {
globalScene.applyModifier(PokemonIncrementingStatModifier, this.isPlayer(), this, s, statHolder);
}
statHolder.value = Phaser.Math.Clamp(statHolder.value, 1, Number.MAX_SAFE_INTEGER);
statHolder.value = Phaser.Math.Clamp(statHolder.value, 1, 0xffffffff);
this.setStat(s, statHolder.value);
}
@ -5705,7 +5701,7 @@ export class PlayerPokemon extends Pokemon {
gender?: Gender,
shiny?: boolean,
variant?: Variant,
ivs?: Uint8Array,
ivs?: Uint8Array | number[],
nature?: Nature,
dataSource?: Pokemon | PokemonData,
) {

View File

@ -99,8 +99,8 @@ export class PokemonData {
this.levelExp = source.levelExp;
this.gender = source.gender;
this.hp = source.hp;
this.stats = source.stats;
this.ivs = source.ivs;
this.stats = Array.from(source.stats);
this.ivs = Array.from(source.ivs);
// TODO: Can't we move some of this verification stuff to an upgrade script?
this.nature = source.nature ?? Nature.HARDY;
@ -162,7 +162,7 @@ export class PokemonData {
this.gender,
this.shiny,
this.variant,
this.ivs,
new Uint8Array(this.ivs.slice(0, 6)),
this.nature,
this,
playerPokemon => {

View File

@ -1,6 +1,13 @@
import { pokerogueApi } from "#api/pokerogue-api";
import { MoneyFormat } from "#enums/money-format";
import type { Variant } from "#sprites/variant";
import type {
BigIntArray,
NumberCompatibleTypedArray,
ReadonlyGenericArray,
ReadonlyTypedArray,
TypedArray,
} from "#types/typed-arrays";
import i18next from "i18next";
export type nil = null | undefined;
@ -176,15 +183,15 @@ export function getPlayTimeString(totalSeconds: number): string {
* @param id 32-bit number
* @returns An array of six numbers corresponding to 5-bit chunks from {@linkcode id}
*/
export function getIvsFromId(id: number): number[] {
return [
export function getIvsFromId(id: number): Uint8Array {
return Uint8Array.of(
(id & 0x3e000000) >>> 25,
(id & 0x01f00000) >>> 20,
(id & 0x000f8000) >>> 15,
(id & 0x00007c00) >>> 10,
(id & 0x000003e0) >>> 5,
id & 0x0000001f,
];
);
}
export function formatLargeNumber(count: number, threshold: number): string {
@ -515,3 +522,86 @@ export function coerceArray<T>(input: T): T extends any[] ? T : [T];
export function coerceArray<T>(input: T): T | [T] {
return Array.isArray(input) ? input : [input];
}
/**
* Type guard to check if an input is a typed array, defined as an `ArrayBufferView` that is not a `DataView`.
*/
export function isTypedArray(input: unknown): input is TypedArray {
return ArrayBuffer.isView(input) && !(input instanceof DataView);
}
/**
* Get a subarray view of the first `n` elements of an array-like object, to use
* with constructing a new one, with minimal allocaitons.
*
* @remarks
* This is primarily useful for setting elements of a `TypedArray` using its `set` method.
*
* @privateRemarks
* Note that if this is used with a tuple type, typescript will improperly set
* the return type to be a tuple of the same length as input.
*
* @param arr - The array-like object to take elements from
* @param n - The maximum number of elements to take. If
* @returns An array-like object whose `length` property is guaranteed to be <= `n`
*
* @typeParam T - The element type of the array-like object
* @typeParam A - The type of the array-like object
*
* @example
* ```
* const arr = new Uint8Array(3);
* const other = [1, 2, 3, 4, 5];
* // Using arr.set(other) would throw, as other.length > arr.length
* arr.set(subArray(other, arr.length));
*
* @throws {TypeError}
* If `arr` is not an array or typed array (though typescript should prevent this)
*/
export function subArray<const A extends TypedArray | readonly unknown[]>(arr: A, n: number): typeof arr {
if (arr.length <= n) {
return arr;
}
const len = Math.min(arr.length, n);
if (Array.isArray(arr)) {
// The only path with a new allocation
return arr.slice(0, len) as typeof arr;
}
if (isTypedArray(arr)) {
return arr.subarray(0, len) as typeof arr;
}
throw new TypeError("Expecting an array or typed array");
}
/**
* Store multiple values in the typed array, from input values from a specified array, without
* the possibility of a `RangeError` being thrown.
*
* @remarks
* Almost equivalent to calling `target.set(source, offset)`, though ensures that
* `RangeError` can never be thrown by clamping the number of elements taken from `source`
* to the available space in `target` starting at `offset`.
*
* @param target - The typed array to set values in
* @param source - The array-like object to take values from
* @param offset - The offset in `target` to start writing values at. Defaults to `0`
*
* @see {@linkcode https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/TypedArray/set | TypedArray#set}
*/
export function setTypedArray<const T extends ReadonlyTypedArray | ReadonlyGenericArray>(
target: T,
source: T extends BigIntArray
? Readonly<BigIntArray> | readonly bigint[]
: Readonly<NumberCompatibleTypedArray> | readonly number[],
offset = 0,
): void {
if (offset < 0 || offset >= target.length) {
return;
}
// @ts-expect-error - TS can't link the conditional type of source to the conditional type of target
// in the body here, despite the fact that the function signature guarantees it.
target.set(subArray(source, target.length - offset), offset);
}

122
test/utils/common.test.ts Normal file
View File

@ -0,0 +1,122 @@
import { setTypedArray, subArray } from "#utils/common";
import { describe, expect, it } from "vitest";
/**
* Unit tests for the utility methods in `src/utils/common.ts`
* @module
*/
describe("subArray", () => {
it("returns the same array if length <= n (plain array)", () => {
const arr = [1, 2, 3];
const result = subArray(arr, 5);
expect(result).toBe(arr);
expect(result).toEqual([1, 2, 3]);
});
it("returns a sliced array if length > n (plain array)", () => {
const arr = [1, 2, 3, 4, 5];
const result = subArray(arr, 3);
expect(result).not.toBe(arr);
expect(result).toEqual([1, 2, 3]);
});
it("returns the same typed array if length <= n", () => {
const arr = new Uint8Array([1, 2, 3]);
const result = subArray(arr, 5);
expect(result).toBe(arr);
expect(Array.from(result)).toEqual([1, 2, 3]);
});
it("returns a subarray if length > n (typed array)", () => {
const arr = new Uint8Array([1, 2, 3, 4, 5]);
const result = subArray(arr, 2);
expect(result).not.toBe(arr);
expect(Array.from(result)).toEqual([1, 2]);
});
it("returns empty array if n is 0 (plain array)", () => {
const arr = [1, 2, 3];
const result = subArray(arr, 0);
expect(result).toEqual([]);
});
it("returns empty typed array if n is 0", () => {
const arr = new Uint8Array([1, 2, 3]);
const result = subArray(arr, 0);
expect(Array.from(result)).toEqual([]);
});
it("throws TypeError for non-array-like input", () => {
// @ts-expect-error
expect(() => subArray({ length: 4 }, 2)).toThrow(TypeError);
});
});
describe("setTypedArray", () => {
it("sets values from source to target with no offset (fits exactly)", () => {
const target = new Uint8Array(3);
const source = [1, 2, 3];
setTypedArray(target, source);
expect(Array.from(target)).toEqual([1, 2, 3]);
});
it("sets values from source to target with offset", () => {
const target = new Uint8Array([0, 0, 0, 0, 0]);
const source = [9, 8];
setTypedArray(target, source, 2);
expect(Array.from(target)).toEqual([0, 0, 9, 8, 0]);
});
it("clamps source if it would overflow target", () => {
const target = new Uint8Array(4);
const source = [1, 2, 3, 4, 5, 6];
setTypedArray(target, source, 2);
expect(Array.from(target)).toEqual([0, 0, 1, 2]);
});
it("does nothing if offset < 0", () => {
const target = new Uint8Array([1, 2, 3]);
const source = [4, 5, 6];
setTypedArray(target, source, -1);
expect(Array.from(target)).toEqual([1, 2, 3]);
});
it("does nothing if offset >= target.length", () => {
const target = new Uint8Array([1, 2, 3]);
const source = [4, 5, 6];
setTypedArray(target, source, 3);
expect(Array.from(target)).toEqual([1, 2, 3]);
});
it("does nothing if source is empty", () => {
const target = new Uint8Array([1, 2, 3]);
const source: number[] = [];
setTypedArray(target, source, 1);
expect(Array.from(target)).toEqual([1, 2, 3]);
});
it("works with typed array as source", () => {
const target = new Uint8Array(4);
const source = new Uint8Array([7, 8, 9]);
setTypedArray(target, source, 1);
expect(Array.from(target)).toEqual([0, 7, 8, 9]);
});
it("clamps source typed array if it would overflow target", () => {
const target = new Uint8Array(3);
const source = new Uint8Array([1, 2, 3, 4, 5]);
setTypedArray(target, source, 1);
expect(Array.from(target)).toEqual([0, 1, 2]);
});
it("works with BigUint64Array and bigint[]", () => {
if (typeof BigUint64Array !== "undefined") {
const target = new BigUint64Array(3);
const source = [1n, 2n, 3n, 4n];
setTypedArray(target, source, 1);
expect(Array.from(target)).toEqual([0n, 1n, 2n]);
}
});
});