Cache class definitions for codec primitives (#4855)

* Cache class definitions for codec primitives

* Adjustments for linting

* clearCache

* cleaner test description

* inline assign

* Adjust failing

* Pass definition via Options
This commit is contained in:
Jaco
2022-05-26 16:59:32 +03:00
committed by GitHub
parent 4825d5466d
commit 71a8ff55d6
8 changed files with 157 additions and 31 deletions
+21 -6
View File
@@ -9,6 +9,15 @@ import { compactFromU8a, compactToU8a, isU8a } from '@polkadot/util';
import { typeToConstructor } from '../utils';
interface Options<T> {
definition?: CodecClass<T>;
setDefinition?: (d: CodecClass<T>) => CodecClass<T>;
}
function noopSetDefinition <T> (d: CodecClass<T>): CodecClass<T> {
return d;
}
/**
* @name Compact
* @description
@@ -28,9 +37,9 @@ export class Compact<T extends INumber> implements ICompact<T> {
readonly #raw: T;
constructor (registry: Registry, Type: CodecClass<T> | string, value: Compact<T> | AnyNumber = 0) {
constructor (registry: Registry, Type: CodecClass<T> | string, value: Compact<T> | AnyNumber = 0, { definition, setDefinition = noopSetDefinition }: Options<T> = {}) {
this.registry = registry;
this.#Type = typeToConstructor(registry, Type);
this.#Type = definition || setDefinition(typeToConstructor(registry, Type));
const [raw, decodedLength] = Compact.decodeCompact<T>(registry, this.#Type, value);
@@ -38,10 +47,16 @@ export class Compact<T extends INumber> implements ICompact<T> {
this.#raw = raw;
}
public static with<T extends INumber> (Type: CodecClass<T> | string): CodecClass<Compact<T>> {
return class extends Compact<T> {
constructor (registry: Registry, value?: Compact<T> | AnyNumber) {
super(registry, Type, value);
public static with<O extends INumber> (Type: CodecClass<O> | string): CodecClass<Compact<O>> {
let definition: CodecClass<O> | undefined;
// eslint-disable-next-line no-return-assign
const setDefinition = <T> (d: CodecClass<T>) =>
(definition = d as unknown as CodecClass<O>) as unknown as CodecClass<T>;
return class extends Compact<O> {
constructor (registry: Registry, value?: Compact<O> | AnyNumber) {
super(registry, Type, value, { definition, setDefinition });
}
};
}
+25 -4
View File
@@ -14,6 +14,12 @@ export interface EnumCodecClass<T = Codec> {
new(registry: Registry, value?: any, index?: number): T;
}
interface Definition {
def: TypesDef;
isBasic: boolean;
isIndexed: boolean;
}
interface EntryDef {
Type: CodecClass;
index: number;
@@ -26,6 +32,15 @@ interface Decoded {
value: Codec;
}
interface Options {
definition?: Definition;
setDefinition?: (d: Definition) => Definition;
}
function noopSetDefinition (d: Definition): Definition {
return d;
}
function isRustEnum (def: Record<string, string | CodecClass> | Record<string, number>): def is Record<string, string | CodecClass> {
const defValues = Object.values(def);
@@ -38,7 +53,7 @@ function isRustEnum (def: Record<string, string | CodecClass> | Record<string, n
return true;
}
function extractDef (registry: Registry, _def: Record<string, string | CodecClass> | Record<string, number> | string[]): { def: TypesDef; isBasic: boolean; isIndexed: boolean } {
function extractDef (registry: Registry, _def: Record<string, string | CodecClass> | Record<string, number> | string[]): Definition {
const def: TypesDef = {};
let isBasic: boolean;
let isIndexed: boolean;
@@ -163,8 +178,8 @@ export class Enum implements IEnum {
readonly #raw: Codec;
constructor (registry: Registry, Types: Record<string, string | CodecClass> | Record<string, number> | string[], value?: unknown, index?: number) {
const { def, isBasic, isIndexed } = extractDef(registry, Types);
constructor (registry: Registry, Types: Record<string, string | CodecClass> | Record<string, number> | string[], value?: unknown, index?: number, { definition, setDefinition = noopSetDefinition }: Options = {}) {
const { def, isBasic, isIndexed } = definition || setDefinition(extractDef(registry, Types));
// shortcut isU8a as used in SCALE decoding
const decoded = isU8a(value) && value.length && !isNumber(index)
@@ -198,9 +213,15 @@ export class Enum implements IEnum {
isKeys[i] = `is${name}`;
}
let definition: Definition | undefined;
// eslint-disable-next-line no-return-assign
const setDefinition = (d: Definition) =>
definition = d;
return class extends Enum {
constructor (registry: Registry, value?: unknown, index?: number) {
super(registry, Types, value, index);
super(registry, Types, value, index, { definition, setDefinition });
objectProperties(this, isKeys, (_, i) => this.type === keys[i]);
objectProperties(this, asKeys, (k, i): Codec => {
+20 -3
View File
@@ -9,6 +9,15 @@ import { assert, isCodec, isNull, isU8a, isUndefined, u8aToHex } from '@polkadot
import { typeToConstructor } from '../utils';
import { Null } from './Null';
interface Options<T> {
definition?: CodecClass<T>;
setDefinition?: (d: CodecClass<T>) => CodecClass<T>;
}
function noopSetDefinition <T extends Codec> (d: CodecClass<T>): CodecClass<T> {
return d;
}
class None extends Null {
/**
* @description Returns the base runtime type name for this instance
@@ -60,8 +69,8 @@ export class Option<T extends Codec> implements IOption<T> {
readonly #raw: T;
constructor (registry: Registry, typeName: CodecClass<T> | string, value?: unknown) {
const Type = typeToConstructor(registry, typeName);
constructor (registry: Registry, typeName: CodecClass<T> | string, value?: unknown, { definition, setDefinition = noopSetDefinition }: Options<T> = {}) {
const Type = definition || setDefinition(typeToConstructor(registry, typeName));
const decoded = isU8a(value) && value.length && !isCodec(value)
? value[0] === 0
? new None(registry)
@@ -78,9 +87,17 @@ export class Option<T extends Codec> implements IOption<T> {
}
public static with<O extends Codec> (Type: CodecClass<O> | string): CodecClass<Option<O>> {
let definition: CodecClass<O> | undefined;
const setDefinition = <T> (d: CodecClass<T>): CodecClass<T> => {
definition = d as unknown as CodecClass<O>;
return d;
};
return class extends Option<O> {
constructor (registry: Registry, value?: unknown) {
super(registry, Type, value);
super(registry, Type, value, { definition, setDefinition });
}
};
}
+23 -7
View File
@@ -8,7 +8,7 @@ import { isFunction, isHex, isString, isU8a, stringify, u8aConcat, u8aToU8a } fr
import { AbstractArray } from '../abstract/AbstractArray';
import { decodeU8a, mapToTypeMap, typeToConstructor } from '../utils';
type TupleCodecClasss = CodecClass[] | {
type TupleCodecClass = CodecClass[] | {
[index: string]: CodecClass;
};
@@ -18,8 +18,17 @@ type TupleTypes = TupleType[] | {
[index: string]: CodecClass | string;
};
interface Options {
definition?: TupleCodecClass;
setDefinition?: (d: TupleCodecClass) => TupleCodecClass;
}
function noopSetDefinition (d: TupleCodecClass): TupleCodecClass {
return d;
}
/** @internal */
function decodeTuple (registry: Registry, Classes: TupleCodecClasss, value?: Exclude<AnyTupleValue, Uint8Array>): [Codec[], number] {
function decodeTuple (registry: Registry, Classes: TupleCodecClass, value?: Exclude<AnyTupleValue, Uint8Array>): [Codec[], number] {
if (isU8a(value) || isHex(value)) {
return decodeU8a(registry, u8aToU8a(value), Classes);
}
@@ -53,14 +62,15 @@ function decodeTuple (registry: Registry, Classes: TupleCodecClasss, value?: Exc
* own type. It extends the base JS `Array` object.
*/
export class Tuple extends AbstractArray<Codec> implements ITuple<Codec[]> {
#Types: TupleCodecClasss;
#Types: TupleCodecClass;
constructor (registry: Registry, Types: TupleTypes | TupleType, value?: AnyTupleValue) {
const Classes = Array.isArray(Types)
constructor (registry: Registry, Types: TupleTypes | TupleType, value?: AnyTupleValue, { definition, setDefinition = noopSetDefinition }: Options = {}) {
const Classes = definition || setDefinition(Array.isArray(Types)
? Types.map((t) => typeToConstructor(registry, t))
: isFunction(Types) || isString(Types)
? [typeToConstructor(registry, Types)]
: mapToTypeMap(registry, Types);
: mapToTypeMap(registry, Types)
);
const [values, decodedLength] = isU8a(value)
? decodeU8a(registry, value, Classes)
: decodeTuple(registry, Classes, value);
@@ -71,9 +81,15 @@ export class Tuple extends AbstractArray<Codec> implements ITuple<Codec[]> {
}
public static with (Types: TupleTypes | TupleType): CodecClass<Tuple> {
let definition: TupleCodecClass | undefined;
// eslint-disable-next-line no-return-assign
const setDefinition = (d: TupleCodecClass) =>
definition = d;
return class extends Tuple {
constructor (registry: Registry, value?: AnyTupleValue) {
super(registry, Types, value);
super(registry, Types, value, { definition, setDefinition });
}
};
}
+18 -3
View File
@@ -13,6 +13,15 @@ const MAX_LENGTH = 64 * 1024;
const l = logger('Vec');
interface Options<T> {
definition?: CodecClass<T>;
setDefinition?: (d: CodecClass<T>) => CodecClass<T>;
}
function noopSetDefinition <T extends Codec> (d: CodecClass<T>): CodecClass<T> {
return d;
}
export function decodeVec<T extends Codec> (registry: Registry, Type: CodecClass<T>, value: Uint8Array | HexString | unknown[], length = -1): [T[], number, number] {
if (Array.isArray(value)) {
const result = new Array<T>(value.length);
@@ -59,8 +68,8 @@ export function decodeVec<T extends Codec> (registry: Registry, Type: CodecClass
export class Vec<T extends Codec> extends AbstractArray<T> {
#Type: CodecClass<T>;
constructor (registry: Registry, Type: CodecClass<T> | string, value: Uint8Array | HexString | unknown[] = []) {
const Clazz = typeToConstructor<T>(registry, Type);
constructor (registry: Registry, Type: CodecClass<T> | string, value: Uint8Array | HexString | unknown[] = [], { definition, setDefinition = noopSetDefinition }: Options<T> = {}) {
const Clazz = definition || setDefinition(typeToConstructor<T>(registry, Type));
const [values, decodedLength] = decodeVec(registry, Clazz, value);
super(registry, values, decodedLength);
@@ -69,9 +78,15 @@ export class Vec<T extends Codec> extends AbstractArray<T> {
}
public static with<O extends Codec> (Type: CodecClass<O> | string): CodecClass<Vec<O>> {
let definition: CodecClass<O> | undefined;
// eslint-disable-next-line no-return-assign
const setDefinition = <T> (d: CodecClass<T>) =>
(definition = d as unknown as CodecClass<O>) as unknown as CodecClass<T>;
return class extends Vec<O> {
constructor (registry: Registry, value?: any[]) {
super(registry, Type, value);
super(registry, Type, value, { definition, setDefinition });
}
};
}
+18 -3
View File
@@ -10,6 +10,15 @@ import { AbstractArray } from '../abstract/AbstractArray';
import { decodeU8aVec, typeToConstructor } from '../utils';
import { decodeVec } from './Vec';
interface Options<T> {
definition?: CodecClass<T>;
setDefinition?: (d: CodecClass<T>) => CodecClass<T>;
}
function noopSetDefinition <T extends Codec> (d: CodecClass<T>): CodecClass<T> {
return d;
}
/** @internal */
function decodeVecFixed<T extends Codec> (registry: Registry, value: HexString | unknown[], Type: CodecClass<T>, length: number): [T[], number, number] {
const [values, decodedLength, decodedLengthNoOffset] = decodeVec(registry, Type, value, length);
@@ -31,8 +40,8 @@ function decodeVecFixed<T extends Codec> (registry: Registry, value: HexString |
export class VecFixed<T extends Codec> extends AbstractArray<T> {
#Type: CodecClass<T>;
constructor (registry: Registry, Type: CodecClass<T> | string, length: number, value: Uint8Array | HexString | unknown[] = [] as unknown[]) {
const Clazz = typeToConstructor<T>(registry, Type);
constructor (registry: Registry, Type: CodecClass<T> | string, length: number, value: Uint8Array | HexString | unknown[] = [] as unknown[], { definition, setDefinition = noopSetDefinition }: Options<T> = {}) {
const Clazz = definition || setDefinition(typeToConstructor<T>(registry, Type));
const [values,, decodedLengthNoOffset] = isU8a(value)
? decodeU8aVec(registry, value, 0, Clazz, length)
: decodeVecFixed(registry, value, Clazz, length);
@@ -43,9 +52,15 @@ export class VecFixed<T extends Codec> extends AbstractArray<T> {
}
public static with<O extends Codec> (Type: CodecClass<O> | string, length: number): CodecClass<VecFixed<O>> {
let definition: CodecClass<O> | undefined;
// eslint-disable-next-line no-return-assign
const setDefinition = <T> (d: CodecClass<T>) =>
(definition = d as unknown as CodecClass<O>) as unknown as CodecClass<T>;
return class extends VecFixed<O> {
constructor (registry: Registry, value?: any[]) {
super(registry, Type, length, value);
super(registry, Type, length, value, { definition, setDefinition });
}
};
}
+18 -3
View File
@@ -10,6 +10,15 @@ import { compareMap, decodeU8a, mapToTypeMap, typesToMap } from '../utils';
type TypesDef<T = Codec> = Record<string, string | CodecClass<T>>;
interface Options {
definition?: CodecClassDef;
setDefinition?: (d: CodecClassDef) => CodecClassDef;
}
function noopSetDefinition (d: CodecClassDef): CodecClassDef {
return d;
}
/** @internal */
function decodeStructFromObject (registry: Registry, Types: CodecClassDef, value: any, jsonMap: Map<string, string>): [Iterable<[string, Codec]>, number] {
let jsonObj: Record<string, unknown> | undefined;
@@ -100,8 +109,8 @@ export class Struct<
readonly #Types: CodecClassDef;
constructor (registry: Registry, Types: S, value?: V | Map<unknown, unknown> | unknown[] | HexString | null, jsonMap = new Map<string, string>()) {
const typeMap = mapToTypeMap(registry, Types);
constructor (registry: Registry, Types: S, value?: V | Map<unknown, unknown> | unknown[] | HexString | null, jsonMap = new Map<string, string>(), { definition, setDefinition = noopSetDefinition }: Options = {}) {
const typeMap = definition || setDefinition(mapToTypeMap(registry, Types));
const [decoded, decodedLength] = isU8a(value)
? decodeU8a<Codec, [string, Codec]>(registry, value, typeMap, true)
: isHex(value)
@@ -121,9 +130,15 @@ export class Struct<
public static with<S extends TypesDef> (Types: S, jsonMap?: Map<string, string>): CodecClass<Struct<S>> {
const keys = Object.keys(Types);
let definition: CodecClassDef | undefined;
// eslint-disable-next-line no-return-assign
const setDefinition = (d: CodecClassDef) =>
definition = d;
return class extends Struct<S> {
constructor (registry: Registry, value?: unknown) {
super(registry, Types, value as HexString, jsonMap);
super(registry, Types, value as HexString, jsonMap, { definition, setDefinition });
objectProperties(this, keys, (k) => this.get(k));
}
+14 -2
View File
@@ -205,7 +205,8 @@ describe('createType', (): void => {
expect(balu32.bitLength()).toEqual(32);
});
it('allows for re-registration of a type (affecting derives)', (): void => {
it('allows for re-registration of a type (derived types)', (): void => {
registry.clearCache();
registry.register({
Balance: 'u128',
TestComplex: {
@@ -224,7 +225,18 @@ describe('createType', (): void => {
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access,@typescript-eslint/no-unsafe-call
expect(cmpDef.balance.bitLength()).toEqual(128);
registry.register({ Balance: 'u32' });
registry.clearCache();
registry.register({
Balance: 'u32',
TestComplex: {
balance: 'Balance',
// eslint-disable-next-line sort-keys
accountId: 'AccountId',
log: '(u64, u32)',
// eslint-disable-next-line sort-keys
fromSrml: 'Gas'
}
});
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
const cmpu32: any = registry.createType('TestComplex');