rc-js-util
Version:
A collection of TS and C++ utilities to help writing performant and correct applications, achieved through strict typing and (removable) invariant checking.
277 lines (250 loc) • 9.43 kB
text/typescript
import { type TFullSetTypedArrayCtor } from "../array/typed-array/t-typed-array-ctor.js";
import type { IJsUtilBindings } from "../web-assembly/i-js-util-bindings.js";
import { type IEmscriptenWrapper } from "../web-assembly/emscripten/i-emscripten-wrapper.js";
import type { IManagedObject, IPointer } from "../lifecycle/manged-resources.js";
import { type ISharedBufferView, SharedBufferView } from "../web-assembly/shared-memory/shared-buffer-view.js";
import { _Debug } from "../debug/_debug.js";
import { numberGetHexString } from "../number/impl/number-get-hex-string.js";
/**
* @public
* Matches ENumberIdentifier in `RTTI.hpp`.
*/
export enum ENumberIdentifier
{
U8 = 0, // used for indexing
U16,
U32,
U64, // this is not generally supported...
I8,
I16,
I32,
I64, // this is not generally supported...
F32,
F64,
}
/**
* @public
* Given a typed array constructor, get the identifier which matches up with ENumberIdentifier in `RTTI.hpp`.
*/
export function getNumberIdentifier(ctor: TFullSetTypedArrayCtor): ENumberIdentifier
{
_BUILD.DEBUG && _Debug.assert(numberIdentifierMapping.has(ctor), "expected to find ctor...");
return numberIdentifierMapping.get(ctor)!;
}
const numberIdentifierMapping = new Map<TFullSetTypedArrayCtor, ENumberIdentifier>([
[Uint8Array as TFullSetTypedArrayCtor, ENumberIdentifier.U8],
[Uint8ClampedArray as TFullSetTypedArrayCtor, ENumberIdentifier.U8],
[Uint16Array as TFullSetTypedArrayCtor, ENumberIdentifier.U16],
[Uint32Array as TFullSetTypedArrayCtor, ENumberIdentifier.U32],
[BigUint64Array as TFullSetTypedArrayCtor, ENumberIdentifier.U64],
[Int8Array as TFullSetTypedArrayCtor, ENumberIdentifier.I8],
[Int16Array as TFullSetTypedArrayCtor, ENumberIdentifier.I16],
[Int32Array as TFullSetTypedArrayCtor, ENumberIdentifier.I32],
[BigInt64Array as TFullSetTypedArrayCtor, ENumberIdentifier.I64],
[Float32Array as TFullSetTypedArrayCtor, ENumberIdentifier.F32],
[Float64Array as TFullSetTypedArrayCtor, ENumberIdentifier.F64],
]);
/**
* @public
* Mirrors the C++ class of the same name. This can be used as a key to get a unique number which can be matched on the C++ side.
*/
export class StableId
{
public constructor
(
public readonly name: string,
)
{
}
}
/**
* @public
* Mirrors the C++ class of the same name. Each category can have multiple kinds of specializations.
*
* @remarks There should only be one instance of a conceptual class (which should have a unique name)
*/
export class IdCategory extends StableId
{
}
/**
* @public
* Mirrors the C++ class of the same name. A specialization of a conceptual category. E.g. a number (category) which is a float 32 (specialization).
*
* @remarks There should only be one instance of a conceptual class (which should have a unique name)
*/
export class IdSpecialization extends StableId
{
public constructor
(
public readonly category: IdCategory,
specializationName: string,
)
{
super(specializationName);
}
}
/**
* @public
* Supported types of number across C++ and JavaScript.
*/
export const numberCategory = new IdCategory("JSU_NUMBER");
/**
* @public
* A buffer that can be shared with C++.
*/
export const bufferCategory = new IdCategory("JSU_BUFFER");
/**
* @public
* Given a Typed array constructor, get back the associated {@link StableId}.
*/
export function getNumberSpecialization(ctor: TFullSetTypedArrayCtor): IdSpecialization
{
return numberSpecializationMapping.get(ctor)!;
}
/**
* @public
* {@link IdSpecialization} for all supported number types.
*/
export const numberSpecializations = new class NumberSpecializations
{
public readonly u8 = new IdSpecialization(numberCategory, "JSU_U8");
public readonly u16 = new IdSpecialization(numberCategory, "JSU_U16");
public readonly u32 = new IdSpecialization(numberCategory, "JSU_U32");
public readonly u64 = new IdSpecialization(numberCategory, "JSU_U64");
public readonly i8 = new IdSpecialization(numberCategory, "JSU_I8");
public readonly i16 = new IdSpecialization(numberCategory, "JSU_I16");
public readonly i32 = new IdSpecialization(numberCategory, "JSU_I32");
public readonly i64 = new IdSpecialization(numberCategory, "JSU_I64");
public readonly f32 = new IdSpecialization(numberCategory, "JSU_F32");
public readonly f64 = new IdSpecialization(numberCategory, "JSU_F64");
}();
const numberSpecializationMapping = new Map<TFullSetTypedArrayCtor, IdSpecialization>([
[Uint8Array as TFullSetTypedArrayCtor, numberSpecializations.u8],
[Uint16Array as TFullSetTypedArrayCtor, numberSpecializations.u16],
[Uint32Array as TFullSetTypedArrayCtor, numberSpecializations.u32],
[BigUint64Array as TFullSetTypedArrayCtor, numberSpecializations.u64],
[Int8Array as TFullSetTypedArrayCtor, numberSpecializations.i8],
[Int16Array as TFullSetTypedArrayCtor, numberSpecializations.i16],
[Int32Array as TFullSetTypedArrayCtor, numberSpecializations.i32],
[BigInt64Array as TFullSetTypedArrayCtor, numberSpecializations.i64],
[Float32Array as TFullSetTypedArrayCtor, numberSpecializations.f32],
[Float64Array as TFullSetTypedArrayCtor, numberSpecializations.f64],
]);
/**
* @internal
*/
export interface ITypedArrayConstructors
{
f64: Function;
f32: Function;
i64: Function | null; // indicates not supported
u64: Function | null; // indicates not supported
u32: Function;
i32: Function;
u16: Function;
i16: Function;
u8c: Function;
u8: Function;
i8: Function;
}
/**
* @internal
*/
export function populateTypedArrayConstructorMap(constructors: ITypedArrayConstructors): Map<TFullSetTypedArrayCtor, Function>
{
const m = new Map<TFullSetTypedArrayCtor, Function>();
m.set(Float64Array, constructors.f64);
m.set(Float32Array, constructors.f32);
if (constructors.i64 != null)
{
m.set(BigInt64Array, constructors.i64);
}
if (constructors.u64 != null)
{
m.set(BigUint64Array, constructors.u64);
}
m.set(Int32Array, constructors.i32);
m.set(Uint32Array, constructors.u32);
m.set(Int16Array, constructors.i16);
m.set(Uint16Array, constructors.u16);
m.set(Int8Array, constructors.i8);
m.set(Uint8Array, constructors.u8);
m.set(Uint8ClampedArray, constructors.u8c);
return m;
}
/**
* @public
* Provides the number which matches up with the C++ end, given a {@link StableId}.
*/
export interface IStableStore
{
initialize(): void;
getId(key: IdSpecialization | IdCategory): number;
setSpecializations(sharedObjectHandle: IManagedObject & IPointer, specializations: readonly IdSpecialization[]): void;
hasId(sharedObjectHandle: IManagedObject & IPointer, specialization: IdSpecialization): boolean;
}
/**
* @public
* {@inheritDoc IStableStore}
*/
export class StableIdStore implements IStableStore
{
public constructor
(
private readonly wrapper: IEmscriptenWrapper<IJsUtilBindings>,
)
{
}
public initialize(): void
{
this.idBuffer = new SharedBufferView(
this.wrapper,
this.wrapper.rootNode,
Uint16Array,
this.wrapper.instance._jsUtilGetRuntimeMappingAddress(),
32 * Uint16Array.BYTES_PER_ELEMENT
);
}
public getId(key: IdSpecialization | IdCategory): number
{
let id = this.ids.get(key);
if (id == null)
{
const namePtr = this.wrapper.instance.stringToNewUTF8(key.name);
if (_BUILD.DEBUG)
{
_Debug.verboseLog(["WASM", "MEMORY", "ALLOCATIONS"], `StableIdStore: created temporary string at ${numberGetHexString(namePtr)} (${key.name}).`);
}
id = this.wrapper.instance._jsUtilGetRuntimeMappingId(namePtr);
this.ids.set(key, id);
this.wrapper.instance._jsUtilFree(namePtr);
}
return id;
}
public setSpecializations(sharedObjectHandle: IManagedObject & IPointer, specializations: readonly IdSpecialization[]): void
{
_BUILD.DEBUG && _Debug.assert(this.idBuffer != null, "attempted to set ids before initialization happened...");
_BUILD.DEBUG && _Debug.assert(specializations.length <= 32, "a maximum of 32 specializations can be added at one time");
const idBuffer = this.idBuffer!.getArray(); // [[category, specialization], ...]
for (let i = 0, iEnd = specializations.length; i < iEnd; i++)
{
const specialization = specializations[i];
idBuffer[i * 2] = this.getId(specialization);
idBuffer[i * 2 + 1] = this.getId(specialization.category);
}
this.wrapper.instance._jsUtilAddRuntimeMappings(specializations.length, sharedObjectHandle.pointer);
}
public hasId(sharedObjectHandle: IManagedObject & IPointer, specialization: IdSpecialization): boolean
{
return Boolean(this.wrapper.instance._jsUtilHasRuntimeMappingId(
sharedObjectHandle.pointer,
this.getId(specialization.category),
this.getId(specialization)
));
}
private ids: Map<IdSpecialization | IdCategory, number> = new Map();
/**
* @internal
*/
public idBuffer: ISharedBufferView<Uint16ArrayConstructor> | null = null;
}