UNPKG

@colyseus/schema

Version:

Binary state serializer with delta encoding for games

182 lines (148 loc) 5.76 kB
import { Class, Property, File, getCommentHeader, getInheritanceTree, Context, Interface } from "../types.js"; import { GenerateOptions } from "../api.js"; export const name = "TypeScript"; const typeMaps: { [key: string]: string } = { "string": "string", "number": "number", "boolean": "boolean", "int8": "number", "uint8": "number", "int16": "number", "uint16": "number", "int32": "number", "uint32": "number", "int64": "number", "uint64": "number", "float32": "number", "float64": "number", } const COMMON_IMPORTS = `import { Schema, type, ArraySchema, MapSchema, SetSchema, DataChange } from '@colyseus/schema';`; const distinct = (value: string, index: number, self: string[]) => self.indexOf(value) === index; /** * Generate individual files for each class/interface */ export function generate (context: Context, options: GenerateOptions): File[] { return [ ...context.classes.map(structure => ({ name: structure.name + ".ts", content: generateClass(structure, options.namespace, context.classes) })), ...context.interfaces.map(structure => ({ name: structure.name + ".ts", content: generateInterface(structure, options.namespace, context.classes), })) ]; } /** * Generate a single bundled file containing all classes and interfaces */ export function renderBundle(context: Context, options: GenerateOptions): File { const fileName = options.namespace ? `${options.namespace}.ts` : "schema.ts"; // Collect all class bodies const classBodies = context.classes.map(klass => generateClassBody(klass)); // Collect all interface bodies const interfaceBodies = context.interfaces.map(iface => generateInterfaceBody(iface)); const content = `${getCommentHeader()} ${COMMON_IMPORTS} ${classBodies.join("\n\n")} ${interfaceBodies.length > 0 ? "\n" + interfaceBodies.join("\n\n") : ""}`; return { name: fileName, content }; } /** * Generate just the class body (without imports) for bundling */ function generateClassBody(klass: Class): string { return `export class ${klass.name} extends ${klass.extends} { ${klass.properties.map(prop => ` ${generateProperty(prop)}`).join("\n")} }`; } /** * Generate just the interface body (without imports) for bundling */ function generateInterfaceBody(iface: Interface): string { return `export interface ${iface.name} { ${iface.properties.map(prop => ` ${prop.name}: ${prop.type};`).join("\n")} }`; } /** * Generate a complete class file with imports (for individual file mode) */ function generateClass(klass: Class, namespace: string, allClasses: Class[]) { const allRefs: Property[] = []; klass.properties.forEach(property => { let type = property.type; // keep all refs list if ((type === "ref" || type === "array" || type === "map" || type === "set")) { allRefs.push(property); } }); const localImports = allRefs. filter(ref => ref.childType && typeMaps[ref.childType] === undefined). map(ref => ref.childType). concat(getInheritanceTree(klass, allClasses, false).map(klass => klass.name)). filter(distinct). map(childType => `import { ${childType} } from './${childType}'`). join("\n"); return `${getCommentHeader()} ${COMMON_IMPORTS} ${localImports} ${generateClassBody(klass)} `; } function generateProperty(prop: Property) { let langType: string; let initializer = ""; let typeArgs: string; if (prop.childType) { const isUpcaseFirst = prop.childType.match(/^[A-Z]/); if (isUpcaseFirst) { typeArgs += `, ${prop.childType}`; } else { typeArgs += `, "${prop.childType}"`; } if(prop.type === "ref") { langType = `${prop.childType}`; initializer = `new ${prop.childType}()`; typeArgs = `${prop.childType}`; } else if(prop.type === "array") { langType = (isUpcaseFirst) ? `ArraySchema<${prop.childType}>` : `ArraySchema<${typeMaps[prop.childType]}>`; initializer = `new ${langType}()`; typeArgs = (isUpcaseFirst) ? `[ ${prop.childType} ]` : `[ "${prop.childType}" ]`; } else if(prop.type === "map") { langType = (isUpcaseFirst) ? `MapSchema<${prop.childType}>` : `MapSchema<${typeMaps[prop.childType]}>`; initializer = `new ${langType}()`; typeArgs = (isUpcaseFirst) ? `{ map: ${prop.childType} }` : `{ map: "${prop.childType}" }`; } else if (prop.type === "set") { langType = (isUpcaseFirst) ? `SetSchema<${prop.childType}>` : `SetSchema<${typeMaps[prop.childType]}>`; initializer = `new ${langType}()`; typeArgs = (isUpcaseFirst) ? `{ set: ${prop.childType} }` : `{ set: "${prop.childType}" }`; } } else { langType = typeMaps[prop.type]; typeArgs = `"${prop.type}"`; } // TS1263: "Declarations with initializers cannot also have definite assignment assertions" const definiteAssertion = initializer ? "" : "!"; return `@type(${typeArgs}) public ${prop.name}${definiteAssertion}: ${langType}${(initializer) ? ` = ${initializer}` : ""};` } /** * Generate a complete interface file with header (for individual file mode) */ function generateInterface(structure: Interface, namespace: string, allClasses: Class[]) { return `${getCommentHeader()} ${generateInterfaceBody(structure)} `; }