mirror of
https://github.com/yume-chan/ya-webadb.git
synced 2025-10-04 18:29:23 +02:00
refactor(struct): split into separate package
This commit is contained in:
parent
03182c77c3
commit
c2e167b647
10 changed files with 75 additions and 3 deletions
792
packages/struct/src/struct.ts
Normal file
792
packages/struct/src/struct.ts
Normal file
|
@ -0,0 +1,792 @@
|
|||
const BackingField = Symbol('BackingField');
|
||||
|
||||
export namespace StructField {
|
||||
export const enum Type {
|
||||
Number,
|
||||
FixedLengthArray,
|
||||
VariableLengthArray,
|
||||
}
|
||||
|
||||
export interface BaseOptions {
|
||||
|
||||
}
|
||||
|
||||
export interface Base<TOptions extends BaseOptions = BaseOptions> {
|
||||
type: Type;
|
||||
|
||||
name: PropertyKey;
|
||||
|
||||
options: TOptions;
|
||||
}
|
||||
|
||||
export type Parser<TField extends Any> = (options: {
|
||||
field: TField;
|
||||
object: any;
|
||||
options: StructOptions;
|
||||
reader: StructReader;
|
||||
}) => Promise<void>;
|
||||
|
||||
export type Initializer<TField extends Any> = (options: {
|
||||
field: TField;
|
||||
init: any;
|
||||
object: any;
|
||||
options: StructOptions;
|
||||
writer: StructWriter;
|
||||
}) => void;
|
||||
|
||||
export type Writer<TField extends Any> = (options: {
|
||||
dataView: DataView;
|
||||
field: TField;
|
||||
object: any;
|
||||
offset: number;
|
||||
options: StructOptions;
|
||||
writer: StructWriter;
|
||||
}) => void;
|
||||
|
||||
export interface Methods<TField extends Any> {
|
||||
type: Type;
|
||||
|
||||
getLength(options: {
|
||||
field: TField;
|
||||
options: StructOptions;
|
||||
}): number;
|
||||
|
||||
parse: Parser<TField>;
|
||||
|
||||
initialize?: Initializer<TField>;
|
||||
|
||||
getVariableLength?(options: {
|
||||
field: TField,
|
||||
object: any,
|
||||
options: StructOptions,
|
||||
writer: StructWriter,
|
||||
}): number;
|
||||
|
||||
write: Writer<TField>;
|
||||
}
|
||||
|
||||
const registry: Record<number, Methods<any>> = {};
|
||||
|
||||
export function getType(type: Type): Methods<Any> {
|
||||
return registry[type as number];
|
||||
}
|
||||
|
||||
export function registerType<TField extends Any, TMethods extends Methods<TField>>(
|
||||
_field: TField,
|
||||
methods: TMethods
|
||||
): void {
|
||||
registry[methods.type as number] = methods;
|
||||
}
|
||||
|
||||
export namespace Number {
|
||||
export type TypeScriptType = number;
|
||||
|
||||
export const enum SubType {
|
||||
Int32,
|
||||
Uint32,
|
||||
}
|
||||
|
||||
export const SizeMap: Record<SubType, number> = {
|
||||
[SubType.Int32]: 4,
|
||||
[SubType.Uint32]: 4,
|
||||
};
|
||||
|
||||
export const DataViewGetterMap = {
|
||||
[SubType.Int32]: 'getInt32',
|
||||
[SubType.Uint32]: 'getUint32',
|
||||
} as const;
|
||||
|
||||
export const DataViewSetterMap = {
|
||||
[SubType.Int32]: 'setInt32',
|
||||
[SubType.Uint32]: 'setUint32',
|
||||
} as const;
|
||||
}
|
||||
|
||||
registerType(undefined as unknown as Number, {
|
||||
type: Type.Number,
|
||||
|
||||
getLength({ field }) {
|
||||
return Number.SizeMap[field.subType];
|
||||
},
|
||||
|
||||
async parse({ field, object, options, reader }) {
|
||||
const buffer = await reader.read(Number.SizeMap[field.subType]);
|
||||
const view = new DataView(buffer);
|
||||
object[field.name] = view[Number.DataViewGetterMap[field.subType]](
|
||||
0,
|
||||
options.littleEndian
|
||||
);
|
||||
},
|
||||
|
||||
write({ dataView, field, object, offset, options }) {
|
||||
dataView[Number.DataViewSetterMap[field.subType]](
|
||||
offset,
|
||||
object[field.name],
|
||||
options.littleEndian
|
||||
);
|
||||
},
|
||||
});
|
||||
|
||||
export interface Number<TOptions extends BaseOptions = BaseOptions> extends Base<TOptions> {
|
||||
type: Type.Number;
|
||||
|
||||
subType: Number.SubType;
|
||||
}
|
||||
|
||||
export namespace Array {
|
||||
export const enum SubType {
|
||||
ArrayBuffer,
|
||||
String,
|
||||
}
|
||||
|
||||
export type TypeScriptType<TType extends SubType = SubType> =
|
||||
TType extends SubType.ArrayBuffer ? ArrayBuffer :
|
||||
TType extends SubType.String ? string :
|
||||
ArrayBuffer | string;
|
||||
|
||||
export interface BackingField {
|
||||
buffer?: ArrayBuffer;
|
||||
|
||||
string?: string;
|
||||
}
|
||||
|
||||
export function getBackingField(object: any, name: PropertyKey): BackingField {
|
||||
return object[BackingField][name];
|
||||
}
|
||||
|
||||
export function setBackingField(object: any, name: PropertyKey, value: BackingField): void {
|
||||
object[BackingField][name] = value;
|
||||
}
|
||||
|
||||
export function initialize(object: any, field: Array, value: BackingField): void {
|
||||
switch (field.subType) {
|
||||
case StructField.Array.SubType.ArrayBuffer:
|
||||
Object.defineProperty(object, field.name, {
|
||||
configurable: true,
|
||||
enumerable: true,
|
||||
get(): ArrayBuffer {
|
||||
return getBackingField(object, field.name).buffer!;
|
||||
},
|
||||
set(buffer: ArrayBuffer) {
|
||||
setBackingField(object, field.name, { buffer });
|
||||
},
|
||||
});
|
||||
break;
|
||||
case StructField.Array.SubType.String:
|
||||
Object.defineProperty(object, field.name, {
|
||||
configurable: true,
|
||||
enumerable: true,
|
||||
get(): string {
|
||||
return getBackingField(object, field.name).string!;
|
||||
},
|
||||
set(string: string) {
|
||||
setBackingField(object, field.name, { string });
|
||||
},
|
||||
});
|
||||
break;
|
||||
default:
|
||||
throw new Error('Unknown type');
|
||||
}
|
||||
setBackingField(object, field.name, value);
|
||||
}
|
||||
}
|
||||
|
||||
export interface Array<
|
||||
TType extends Array.SubType = Array.SubType,
|
||||
TOptions extends BaseOptions = BaseOptions
|
||||
> extends Base<TOptions> {
|
||||
subType: TType;
|
||||
}
|
||||
|
||||
export namespace FixedLengthArray {
|
||||
export interface Options extends BaseOptions {
|
||||
length: number;
|
||||
}
|
||||
}
|
||||
|
||||
registerType(undefined as unknown as FixedLengthArray, {
|
||||
type: Type.FixedLengthArray,
|
||||
|
||||
getLength({ field }) {
|
||||
return field.options.length;
|
||||
},
|
||||
|
||||
async parse({ field, object, reader }) {
|
||||
const value: Array.BackingField = {
|
||||
buffer: await reader.read(field.options.length),
|
||||
};
|
||||
|
||||
switch (field.subType) {
|
||||
case Array.SubType.ArrayBuffer:
|
||||
break;
|
||||
case Array.SubType.String:
|
||||
value.string = reader.decodeUtf8(value.buffer!);
|
||||
break;
|
||||
default:
|
||||
throw new Error('Unknown type');
|
||||
}
|
||||
|
||||
Array.initialize(object, field, value);
|
||||
},
|
||||
|
||||
initialize({ field, init, object }) {
|
||||
Array.initialize(object, field, {});
|
||||
object[field.name] = init[field.name];
|
||||
},
|
||||
|
||||
write({ dataView, field, object, offset, writer }) {
|
||||
const backingField = Array.getBackingField(object, field.name);
|
||||
backingField.buffer ??=
|
||||
writer.encodeUtf8(backingField.string!);
|
||||
|
||||
new Uint8Array(dataView.buffer).set(
|
||||
new Uint8Array(backingField.buffer),
|
||||
offset
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
export interface FixedLengthArray<
|
||||
TType extends Array.SubType = Array.SubType,
|
||||
TOptions extends FixedLengthArray.Options = FixedLengthArray.Options
|
||||
> extends Array<TType, TOptions> {
|
||||
type: Type.FixedLengthArray;
|
||||
|
||||
options: TOptions;
|
||||
}
|
||||
|
||||
export namespace VariableLengthArray {
|
||||
export type TypeScriptTypeCanBeUndefined<
|
||||
TEmptyBehavior extends EmptyBehavior = EmptyBehavior
|
||||
> =
|
||||
TEmptyBehavior extends EmptyBehavior.Empty ? never :
|
||||
undefined;
|
||||
|
||||
export type TypeScriptType<
|
||||
TType extends Array.SubType = Array.SubType,
|
||||
TEmptyBehavior extends EmptyBehavior = EmptyBehavior
|
||||
> =
|
||||
Array.TypeScriptType<TType> |
|
||||
TypeScriptTypeCanBeUndefined<TEmptyBehavior>;
|
||||
|
||||
export const enum EmptyBehavior {
|
||||
Undefined,
|
||||
Empty,
|
||||
}
|
||||
|
||||
export type KeyOfType<TObject, TProperty> =
|
||||
{
|
||||
[TKey in keyof TObject]:
|
||||
TObject[TKey] extends TProperty ? TKey : never
|
||||
}[keyof TObject];
|
||||
|
||||
export interface Options<
|
||||
TObject = object,
|
||||
TLengthField extends KeyOfType<TObject, number> = any,
|
||||
TEmptyBehavior extends EmptyBehavior = EmptyBehavior
|
||||
> extends BaseOptions {
|
||||
lengthField: TLengthField;
|
||||
|
||||
emptyBehavior?: TEmptyBehavior;
|
||||
}
|
||||
|
||||
export function getLengthBackingField(object: any, field: VariableLengthArray): number | undefined {
|
||||
return object[BackingField][field.options.lengthField];
|
||||
}
|
||||
|
||||
export function setLengthBackingField(
|
||||
object: any,
|
||||
field: VariableLengthArray,
|
||||
value: number | undefined
|
||||
) {
|
||||
object[BackingField][field.options.lengthField] = value;
|
||||
}
|
||||
|
||||
export function initialize(
|
||||
object: any,
|
||||
field: VariableLengthArray,
|
||||
value: Array.BackingField,
|
||||
writer: StructWriter,
|
||||
): void {
|
||||
Array.initialize(object, field, value);
|
||||
const descriptor = Object.getOwnPropertyDescriptor(object, field.name)!;
|
||||
delete object[field.name];
|
||||
|
||||
switch (field.subType) {
|
||||
case Array.SubType.ArrayBuffer:
|
||||
Object.defineProperty(object, field.name, {
|
||||
...descriptor,
|
||||
set(buffer: ArrayBuffer | undefined) {
|
||||
descriptor.set!.call(object, buffer);
|
||||
setLengthBackingField(object, field, buffer?.byteLength ?? 0);
|
||||
},
|
||||
});
|
||||
|
||||
delete object[field.options.lengthField];
|
||||
Object.defineProperty(object, field.options.lengthField, {
|
||||
configurable: true,
|
||||
enumerable: true,
|
||||
get() {
|
||||
return getLengthBackingField(object, field);
|
||||
}
|
||||
});
|
||||
break;
|
||||
case Array.SubType.String:
|
||||
Object.defineProperty(object, field.name, {
|
||||
...descriptor,
|
||||
set(string: string | undefined) {
|
||||
descriptor.set!.call(object, string);
|
||||
if (string) {
|
||||
setLengthBackingField(object, field, undefined);
|
||||
} else {
|
||||
setLengthBackingField(object, field, 0);
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
delete object[field.options.lengthField];
|
||||
Object.defineProperty(object, field.options.lengthField, {
|
||||
configurable: true,
|
||||
enumerable: true,
|
||||
get() {
|
||||
let value = getLengthBackingField(object, field);
|
||||
if (value === undefined) {
|
||||
const backingField = Array.getBackingField(object, field.name);
|
||||
const buffer = writer.encodeUtf8(backingField.string!);
|
||||
backingField.buffer = buffer;
|
||||
|
||||
value = buffer.byteLength;
|
||||
setLengthBackingField(object, field, value);
|
||||
}
|
||||
return value;
|
||||
}
|
||||
});
|
||||
break;
|
||||
default:
|
||||
throw new Error('Unknown type');
|
||||
}
|
||||
Array.setBackingField(object, field.name, value);
|
||||
if (value.buffer) {
|
||||
setLengthBackingField(object, field, value.buffer.byteLength);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
registerType(undefined as unknown as VariableLengthArray, {
|
||||
type: Type.VariableLengthArray,
|
||||
|
||||
getLength() { return 0; },
|
||||
|
||||
async parse({ field, object, reader }) {
|
||||
const value: Array.BackingField = {};
|
||||
const length = object[field.options.lengthField];
|
||||
if (length === 0) {
|
||||
if (field.options.emptyBehavior === VariableLengthArray.EmptyBehavior.Empty) {
|
||||
value.buffer = new ArrayBuffer(0);
|
||||
value.string = '';
|
||||
}
|
||||
|
||||
VariableLengthArray.initialize(object, field, value, reader);
|
||||
return;
|
||||
}
|
||||
|
||||
value.buffer = await reader.read(length);
|
||||
switch (field.subType) {
|
||||
case Array.SubType.ArrayBuffer:
|
||||
break;
|
||||
case Array.SubType.String:
|
||||
value.string = reader.decodeUtf8(value.buffer);
|
||||
break;
|
||||
default:
|
||||
throw new Error('Unknown type');
|
||||
}
|
||||
VariableLengthArray.initialize(object, field, value, reader);
|
||||
},
|
||||
|
||||
initialize({ field, init, object, writer }) {
|
||||
VariableLengthArray.initialize(object, field, {}, writer);
|
||||
object[field.name] = init[field.name];
|
||||
},
|
||||
|
||||
getVariableLength({ field, object }) {
|
||||
return object[field.options.lengthField];
|
||||
},
|
||||
|
||||
write({ dataView, field, object, offset }) {
|
||||
const backingField = Array.getBackingField(object, field.name);
|
||||
new Uint8Array(dataView.buffer).set(
|
||||
new Uint8Array(backingField.buffer!),
|
||||
offset
|
||||
);
|
||||
},
|
||||
});
|
||||
|
||||
export interface VariableLengthArray<
|
||||
TType extends Array.SubType = Array.SubType,
|
||||
TObject = object,
|
||||
TLengthField extends VariableLengthArray.KeyOfType<TObject, number> = any,
|
||||
TEmptyBehavior extends VariableLengthArray.EmptyBehavior = VariableLengthArray.EmptyBehavior,
|
||||
TOptions extends VariableLengthArray.Options<TObject, TLengthField, TEmptyBehavior> = VariableLengthArray.Options<TObject, TLengthField, TEmptyBehavior>
|
||||
> extends Array<TType, TOptions> {
|
||||
type: Type.VariableLengthArray;
|
||||
|
||||
options: TOptions;
|
||||
}
|
||||
|
||||
export type Any =
|
||||
Number |
|
||||
FixedLengthArray |
|
||||
VariableLengthArray;
|
||||
}
|
||||
|
||||
export interface StructWriter {
|
||||
encodeUtf8(input: string): ArrayBuffer;
|
||||
}
|
||||
|
||||
export interface StructReader extends StructWriter {
|
||||
decodeUtf8(buffer: ArrayBuffer): string;
|
||||
|
||||
read(length: number): Promise<ArrayBuffer>;
|
||||
}
|
||||
|
||||
export type StructValueType<T extends Struct<unknown, unknown, unknown>> =
|
||||
T extends { parse(reader: StructReader): Promise<infer R>; } ? R : never;
|
||||
|
||||
export type StructInitType<T extends Struct<unknown, unknown, unknown>> =
|
||||
T extends { create(value: infer R, ...args: any): any; } ? R : never;
|
||||
|
||||
export interface StructOptions {
|
||||
littleEndian: boolean;
|
||||
}
|
||||
|
||||
export const StructDefaultOptions: Readonly<StructOptions> = {
|
||||
littleEndian: false,
|
||||
};
|
||||
|
||||
interface ArrayInitializer<TObject, TAfterParsed, TInit> {
|
||||
<
|
||||
TName extends PropertyKey,
|
||||
TType extends StructField.Array.SubType,
|
||||
TTypeScriptType = StructField.Array.TypeScriptType<TType>
|
||||
>(
|
||||
name: TName,
|
||||
type: TType,
|
||||
options: StructField.FixedLengthArray.Options,
|
||||
typescriptType?: () => TTypeScriptType,
|
||||
): Struct<
|
||||
TObject & Record<TName, TTypeScriptType>,
|
||||
TAfterParsed,
|
||||
TInit & Record<TName, TTypeScriptType>
|
||||
>;
|
||||
|
||||
<
|
||||
TName extends PropertyKey,
|
||||
TType extends StructField.Array.SubType,
|
||||
TLengthField extends StructField.VariableLengthArray.KeyOfType<TInit, number>,
|
||||
TEmptyBehavior extends StructField.VariableLengthArray.EmptyBehavior,
|
||||
TTypeScriptType = StructField.VariableLengthArray.TypeScriptType<TType, TEmptyBehavior>
|
||||
>(
|
||||
name: TName,
|
||||
type: TType,
|
||||
options: StructField.VariableLengthArray.Options<TInit, TLengthField, TEmptyBehavior>,
|
||||
typescriptType?: () => TTypeScriptType,
|
||||
): Struct<
|
||||
TObject & Record<TName, TTypeScriptType>,
|
||||
TAfterParsed,
|
||||
Omit<TInit, TLengthField> & Record<TName, TTypeScriptType>
|
||||
>;
|
||||
}
|
||||
|
||||
interface ArrayTypeInitializer<
|
||||
TObject,
|
||||
TAfterParsed,
|
||||
TInit,
|
||||
TType extends StructField.Array.SubType
|
||||
> {
|
||||
<
|
||||
TName extends PropertyKey,
|
||||
TTypeScriptType = StructField.Array.TypeScriptType<TType>
|
||||
>(
|
||||
name: TName,
|
||||
options: StructField.FixedLengthArray.Options,
|
||||
typescriptType?: () => TTypeScriptType,
|
||||
): Struct<
|
||||
TObject & Record<TName, TTypeScriptType>,
|
||||
TAfterParsed,
|
||||
TInit & Record<TName, TTypeScriptType>
|
||||
>;
|
||||
|
||||
<
|
||||
TName extends PropertyKey,
|
||||
TLengthField extends StructField.VariableLengthArray.KeyOfType<TInit, number>,
|
||||
TEmptyBehavior extends StructField.VariableLengthArray.EmptyBehavior,
|
||||
TTypeScriptType = StructField.VariableLengthArray.TypeScriptType<TType, TEmptyBehavior>
|
||||
>(
|
||||
name: TName,
|
||||
options: StructField.VariableLengthArray.Options<TInit, TLengthField, TEmptyBehavior>,
|
||||
_typescriptType?: () => TTypeScriptType,
|
||||
): Struct<
|
||||
TObject & Record<TName, TTypeScriptType>,
|
||||
TAfterParsed,
|
||||
Omit<TInit, TLengthField> & Record<TName, TTypeScriptType>
|
||||
>;
|
||||
}
|
||||
|
||||
export type StructAfterParsed<TObject, TResult> =
|
||||
(this: TObject, object: TObject) => TResult;
|
||||
|
||||
export default class Struct<TObject = {}, TAfterParsed = undefined, TInit = {}> {
|
||||
public readonly options: Readonly<StructOptions>;
|
||||
|
||||
private _size = 0;
|
||||
public get size() { return this._size; }
|
||||
|
||||
private fields: StructField.Any[] = [];
|
||||
|
||||
private _extra: PropertyDescriptorMap = {};
|
||||
|
||||
private _afterParsed?: StructAfterParsed<any, any>;
|
||||
|
||||
public constructor(options: Partial<StructOptions> = StructDefaultOptions) {
|
||||
this.options = { ...StructDefaultOptions, ...options };
|
||||
}
|
||||
|
||||
private clone(): Struct<any, any, any> {
|
||||
const result = new Struct<any, any, any>(this.options);
|
||||
result.fields = this.fields.slice();
|
||||
result._size = this._size;
|
||||
result._extra = this._extra;
|
||||
result._afterParsed = this._afterParsed;
|
||||
return result;
|
||||
}
|
||||
|
||||
private number<
|
||||
TName extends PropertyKey,
|
||||
TTypeScriptType = StructField.Number.TypeScriptType
|
||||
>(
|
||||
name: TName,
|
||||
type: StructField.Number.SubType,
|
||||
options: StructField.BaseOptions = {},
|
||||
_typescriptType?: () => TTypeScriptType,
|
||||
): Struct<
|
||||
TObject & Record<TName, TTypeScriptType>,
|
||||
TAfterParsed,
|
||||
TInit & Record<TName, TTypeScriptType>
|
||||
> {
|
||||
const result = this.clone();
|
||||
result.fields.push({
|
||||
type: StructField.Type.Number,
|
||||
name,
|
||||
subType: type,
|
||||
options,
|
||||
});
|
||||
result._size += StructField.Number.SizeMap[type];
|
||||
return result;
|
||||
}
|
||||
|
||||
public int32<
|
||||
TName extends PropertyKey,
|
||||
TTypeScriptType = StructField.Number.TypeScriptType
|
||||
>(
|
||||
name: TName,
|
||||
options?: StructField.BaseOptions,
|
||||
_typescriptType?: () => TTypeScriptType,
|
||||
): Struct<
|
||||
TObject & Record<TName, TTypeScriptType>,
|
||||
TAfterParsed,
|
||||
TInit & Record<TName, TTypeScriptType>
|
||||
> {
|
||||
return this.number(
|
||||
name,
|
||||
StructField.Number.SubType.Int32,
|
||||
options,
|
||||
_typescriptType
|
||||
);
|
||||
}
|
||||
|
||||
public uint32<
|
||||
TName extends PropertyKey,
|
||||
TTypeScriptType = StructField.Number.TypeScriptType
|
||||
>(
|
||||
name: TName,
|
||||
options?: StructField.BaseOptions,
|
||||
_typescriptType?: () => TTypeScriptType,
|
||||
): Struct<
|
||||
TObject & Record<TName, TTypeScriptType>,
|
||||
TAfterParsed,
|
||||
TInit & Record<TName, TTypeScriptType>
|
||||
> {
|
||||
return this.number(
|
||||
name,
|
||||
StructField.Number.SubType.Uint32,
|
||||
options,
|
||||
_typescriptType
|
||||
);
|
||||
}
|
||||
|
||||
private array: ArrayInitializer<TObject, TAfterParsed, TInit> = (
|
||||
name: PropertyKey,
|
||||
type: StructField.Array.SubType,
|
||||
options: StructField.FixedLengthArray.Options | StructField.VariableLengthArray.Options
|
||||
): Struct<any, any, any> => {
|
||||
const result = this.clone();
|
||||
if ('length' in options) {
|
||||
result.fields.push({
|
||||
type: StructField.Type.FixedLengthArray,
|
||||
name,
|
||||
subType: type,
|
||||
options: options,
|
||||
});
|
||||
result._size += options.length;
|
||||
} else {
|
||||
result.fields.push({
|
||||
type: StructField.Type.VariableLengthArray,
|
||||
name,
|
||||
subType: type,
|
||||
options: options,
|
||||
});
|
||||
}
|
||||
return result;
|
||||
};
|
||||
|
||||
public arrayBuffer: ArrayTypeInitializer<
|
||||
TObject,
|
||||
TAfterParsed,
|
||||
TInit,
|
||||
StructField.Array.SubType.ArrayBuffer
|
||||
> = (
|
||||
name: PropertyKey,
|
||||
options: any
|
||||
) => {
|
||||
return this.array(name, StructField.Array.SubType.ArrayBuffer, options);
|
||||
};
|
||||
|
||||
public string: ArrayTypeInitializer<
|
||||
TObject,
|
||||
TAfterParsed,
|
||||
TInit,
|
||||
StructField.Array.SubType.String
|
||||
> = (
|
||||
name: PropertyKey,
|
||||
options: any
|
||||
) => {
|
||||
return this.array(name, StructField.Array.SubType.String, options);
|
||||
};
|
||||
|
||||
public extra<U extends object>(
|
||||
value: U & ThisType<Omit<TObject, keyof U> & U>
|
||||
): Struct<Omit<TObject, keyof U> & U, TAfterParsed, TInit> {
|
||||
const result = this.clone();
|
||||
result._extra = { ...result._extra, ...Object.getOwnPropertyDescriptors(value) };
|
||||
return result;
|
||||
}
|
||||
|
||||
public afterParsed(
|
||||
callback: StructAfterParsed<TObject, never>
|
||||
): Struct<TObject, never, TInit>;
|
||||
public afterParsed(
|
||||
callback?: StructAfterParsed<TObject, void>
|
||||
): Struct<TObject, undefined, TInit>;
|
||||
public afterParsed<TResult>(
|
||||
callback?: StructAfterParsed<TObject, TResult>
|
||||
): Struct<TObject, TResult, TInit>;
|
||||
public afterParsed(
|
||||
callback?: StructAfterParsed<TObject, any>
|
||||
): Struct<any, any, any> {
|
||||
const result = this.clone();
|
||||
result._afterParsed = callback;
|
||||
return result;
|
||||
}
|
||||
|
||||
public create(init: TInit, writer: StructWriter): TObject {
|
||||
const object: any = {
|
||||
[BackingField]: {},
|
||||
};
|
||||
|
||||
for (const field of this.fields) {
|
||||
const type = StructField.getType(field.type);
|
||||
if (type.initialize) {
|
||||
type.initialize({
|
||||
field,
|
||||
init,
|
||||
object,
|
||||
options: this.options,
|
||||
writer,
|
||||
});
|
||||
} else {
|
||||
object[field.name] = (init as any)[field.name];
|
||||
}
|
||||
}
|
||||
|
||||
Object.defineProperties(object, this._extra);
|
||||
return object;
|
||||
}
|
||||
|
||||
public async parse(
|
||||
reader: StructReader
|
||||
): Promise<TAfterParsed extends undefined ? TObject : TAfterParsed> {
|
||||
const object: any = {
|
||||
[BackingField]: {},
|
||||
};
|
||||
|
||||
for (const field of this.fields) {
|
||||
await StructField.getType(field.type).parse({
|
||||
reader,
|
||||
field,
|
||||
object,
|
||||
options: this.options,
|
||||
});
|
||||
}
|
||||
|
||||
Object.defineProperties(object, this._extra);
|
||||
|
||||
if (this._afterParsed) {
|
||||
const result = this._afterParsed.call(object, object);
|
||||
if (result) {
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
return object;
|
||||
}
|
||||
|
||||
public toBuffer(init: TInit, writer: StructWriter): ArrayBuffer {
|
||||
const object = this.create(init, writer) as any;
|
||||
|
||||
let size = this._size;
|
||||
let fieldSize: number[] = [];
|
||||
for (let i = 0; i < this.fields.length; i++) {
|
||||
const field = this.fields[i];
|
||||
const type = StructField.getType(field.type);
|
||||
if (type.getVariableLength) {
|
||||
fieldSize[i] = type.getVariableLength({
|
||||
writer,
|
||||
field,
|
||||
object,
|
||||
options: this.options,
|
||||
});
|
||||
size += fieldSize[i];
|
||||
} else {
|
||||
fieldSize[i] = type.getLength({ field, options: this.options });
|
||||
}
|
||||
}
|
||||
|
||||
const buffer = new ArrayBuffer(size);
|
||||
const dataView = new DataView(buffer);
|
||||
let offset = 0;
|
||||
for (let i = 0; i < this.fields.length; i++) {
|
||||
const field = this.fields[i];
|
||||
const type = StructField.getType(field.type);
|
||||
type.write({
|
||||
dataView,
|
||||
field,
|
||||
object,
|
||||
offset,
|
||||
options: this.options,
|
||||
writer,
|
||||
});
|
||||
offset += fieldSize[i];
|
||||
}
|
||||
return buffer;
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue