UNPKG

@tamgl/colyseus-schema

Version:

Binary state serializer with delta encoding for games

239 lines (188 loc) 9.11 kB
import { type, PrimitiveType } from "./annotations"; import { TypeContext } from "./types/TypeContext"; import { Metadata } from "./Metadata"; import { ArraySchema } from "./types/custom/ArraySchema"; import { Iterator } from "./encoding/decode"; import { Encoder } from "./encoder/Encoder"; import { Decoder } from "./decoder/Decoder"; import { Schema } from "./Schema"; /** * Reflection */ export class ReflectionField extends Schema { @type("string") name: string; @type("string") type: string; @type("number") referencedType: number; } export class ReflectionType extends Schema { @type("number") id: number; @type("number") extendsId: number; @type([ ReflectionField ]) fields = new ArraySchema<ReflectionField>(); } export class Reflection extends Schema { @type([ReflectionType]) types: ArraySchema<ReflectionType> = new ArraySchema<ReflectionType>(); @type("number") rootType: number; /** * Encodes the TypeContext of an Encoder into a buffer. * * @param encoder Encoder instance * @param it * @returns */ static encode(encoder: Encoder, it: Iterator = { offset: 0 }) { const context = encoder.context; const reflection = new Reflection(); const reflectionEncoder = new Encoder(reflection); // rootType is usually the first schema passed to the Encoder // (unless it inherits from another schema) const rootType = context.schemas.get(encoder.state.constructor); if (rootType > 0) { reflection.rootType = rootType; } const includedTypeIds = new Set<number>(); const pendingReflectionTypes: { [typeid: number]: ReflectionType[] } = {}; // add type to reflection in a way that respects inheritance // (parent types should be added before their children) const addType = (type: ReflectionType) => { if (type.extendsId === undefined || includedTypeIds.has(type.extendsId)) { includedTypeIds.add(type.id); reflection.types.push(type); const deps = pendingReflectionTypes[type.id]; if (deps !== undefined) { delete pendingReflectionTypes[type.id]; deps.forEach((childType) => addType(childType)); } } else { if (pendingReflectionTypes[type.extendsId] === undefined) { pendingReflectionTypes[type.extendsId] = []; } pendingReflectionTypes[type.extendsId].push(type); } }; context.schemas.forEach((typeid, klass) => { const type = new ReflectionType(); type.id = Number(typeid); // support inheritance const inheritFrom = Object.getPrototypeOf(klass); if (inheritFrom !== Schema) { type.extendsId = context.schemas.get(inheritFrom); } const metadata = klass[Symbol.metadata]; // // FIXME: this is a workaround for inherited types without additional fields // if metadata is the same reference as the parent class - it means the class has no own metadata // if (metadata !== inheritFrom[Symbol.metadata]) { for (const fieldIndex in metadata) { const index = Number(fieldIndex); const fieldName = metadata[index].name; // skip fields from parent classes if (!Object.prototype.hasOwnProperty.call(metadata, fieldName)) { continue; } const reflectionField = new ReflectionField(); reflectionField.name = fieldName; let fieldType: string; const field = metadata[index]; if (typeof (field.type) === "string") { fieldType = field.type; } else { let childTypeSchema: typeof Schema; // // TODO: refactor below. // if (Schema.is(field.type)) { fieldType = "ref"; childTypeSchema = field.type as typeof Schema; } else { fieldType = Object.keys(field.type)[0]; if (typeof (field.type[fieldType]) === "string") { fieldType += ":" + field.type[fieldType]; // array:string } else { childTypeSchema = field.type[fieldType]; } } reflectionField.referencedType = (childTypeSchema) ? context.getTypeId(childTypeSchema) : -1; } reflectionField.type = fieldType; type.fields.push(reflectionField); } } addType(type); }); // in case there are types that were not added due to inheritance for (const typeid in pendingReflectionTypes) { pendingReflectionTypes[typeid].forEach((type) => reflection.types.push(type)) } const buf = reflectionEncoder.encodeAll(it); return Buffer.from(buf, 0, it.offset); } /** * Decodes the TypeContext from a buffer into a Decoder instance. * * @param bytes Reflection.encode() output * @param it * @returns Decoder instance */ static decode<T extends Schema = Schema>(bytes: Buffer, it?: Iterator): Decoder<T> { const reflection = new Reflection(); const reflectionDecoder = new Decoder(reflection); reflectionDecoder.decode(bytes, it); const typeContext = new TypeContext(); // 1st pass, initialize metadata + inheritance reflection.types.forEach((reflectionType) => { const parentClass: typeof Schema = typeContext.get(reflectionType.extendsId) ?? Schema; const schema: typeof Schema = class _ extends parentClass {}; // register for inheritance support TypeContext.register(schema); // // for inheritance support // Metadata.initialize(schema); typeContext.add(schema, reflectionType.id); }, {}); // define fields const addFields = (metadata: Metadata, reflectionType: ReflectionType, parentFieldIndex: number) => { reflectionType.fields.forEach((field, i) => { const fieldIndex = parentFieldIndex + i; if (field.referencedType !== undefined) { let fieldType = field.type; let refType: PrimitiveType = typeContext.get(field.referencedType); // map or array of primitive type (-1) if (!refType) { const typeInfo = field.type.split(":"); fieldType = typeInfo[0]; refType = typeInfo[1] as PrimitiveType; // string } if (fieldType === "ref") { Metadata.addField(metadata, fieldIndex, field.name, refType); } else { Metadata.addField(metadata, fieldIndex, field.name, { [fieldType]: refType }); } } else { Metadata.addField(metadata, fieldIndex, field.name, field.type as PrimitiveType); } }); }; // 2nd pass, set fields reflection.types.forEach((reflectionType) => { const schema = typeContext.get(reflectionType.id); // for inheritance support const metadata = Metadata.initialize(schema); const inheritedTypes: ReflectionType[] = []; let parentType: ReflectionType = reflectionType; do { inheritedTypes.push(parentType); parentType = reflection.types.find((t) => t.id === parentType.extendsId); } while (parentType); let parentFieldIndex = 0; inheritedTypes.reverse().forEach((reflectionType) => { // add fields from all inherited classes // TODO: refactor this to avoid adding fields from parent classes addFields(metadata, reflectionType, parentFieldIndex); parentFieldIndex += reflectionType.fields.length; }); }); const state: T = new (typeContext.get(reflection.rootType || 0) as unknown as any)(); return new Decoder<T>(state, typeContext); } }