UNPKG

@colyseus/schema

Version:

Binary state serializer with delta encoding for games

1,421 lines (1,377 loc) 79 kB
#!/usr/bin/env node 'use strict'; var fs = require('fs'); var path = require('path'); var ts$1 = require('typescript'); var _documentCurrentScript = typeof document !== 'undefined' ? document.currentScript : null; function _interopNamespaceDefault(e) { var n = Object.create(null); if (e) { Object.keys(e).forEach(function (k) { if (k !== 'default') { var d = Object.getOwnPropertyDescriptor(e, k); Object.defineProperty(n, k, d.get ? d : { enumerable: true, get: function () { return e[k]; } }); } }); } n.default = e; return Object.freeze(n); } var fs__namespace = /*#__PURE__*/_interopNamespaceDefault(fs); var path__namespace = /*#__PURE__*/_interopNamespaceDefault(path); var ts__namespace = /*#__PURE__*/_interopNamespaceDefault(ts$1); /** * @author Ethan Davis * https://github.com/ethanent/gar */ var argv = (sargs) => { let props = {}; let lones = []; const convertIfApplicable = (value) => (isNaN(value) ? (value.toString().toLowerCase() === 'true' ? true : (value.toString().toLowerCase() === 'false' ? false : value)) : Number(value)); const removeStartHyphens = (value) => value.replace(/^\-+/g, ''); for (let i = 0; i < sargs.length; i++) { const equalsIndex = sargs[i].indexOf('='); const isNextRefProp = sargs[i].charAt(0) === '-' && sargs.length - 1 >= i + 1 && sargs[i + 1].indexOf('=') === -1 && sargs[i + 1].charAt(0) !== '-'; const argName = equalsIndex === -1 ? removeStartHyphens(sargs[i]) : removeStartHyphens(sargs[i].slice(0, equalsIndex)); if (equalsIndex !== -1) { props[argName] = convertIfApplicable(sargs[i].slice(equalsIndex + 1)); } else if (isNextRefProp) { props[argName] = convertIfApplicable(sargs[i + 1]); i++; } else if (sargs[i].charAt(0) === '-') { if (sargs[i].charAt(1) === '-') { props[argName] = true; } else { for (let b = 0; b < argName.length; b++) { props[argName.charAt(b)] = true; } } } else { lones.push(convertIfApplicable(argName)); } } return Object.assign(props, { '_': lones }); }; if (typeof (__dirname) === "undefined") { global.__dirname = path__namespace.dirname(new URL((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('cli.cjs', document.baseURI).href))).pathname); } const VERSION = JSON.parse(fs__namespace.readFileSync(__dirname + "/../../package.json").toString()).version; const COMMENT_HEADER = ` THIS FILE HAS BEEN GENERATED AUTOMATICALLY DO NOT CHANGE IT MANUALLY UNLESS YOU KNOW WHAT YOU'RE DOING GENERATED USING @colyseus/schema ${VERSION} `; function getCommentHeader(singleLineComment = "//") { return `${COMMENT_HEADER.split("\n").map(line => `${singleLineComment} ${line}`).join("\n")}`; } class Context { classes = []; interfaces = []; enums = []; getStructures() { return { classes: this.classes.filter(klass => { if (this.isSchemaClass(klass)) { return true; } else { let parentClass = klass; while (parentClass = this.getParentClass(parentClass)) { if (this.isSchemaClass(parentClass)) { return true; } } } return false; }), interfaces: this.interfaces, enums: this.enums, }; } addStructure(structure) { if (structure.context === this) { return; } // skip if already added. structure.context = this; if (structure instanceof Class) { this.classes.push(structure); } else if (structure instanceof Interface) { this.interfaces.push(structure); } else if (structure instanceof Enum) { this.enums.push(structure); } } getParentClass(klass) { return this.classes.find(c => c.name === klass.extends); } isSchemaClass(klass) { let isSchema = false; let currentClass = klass; while (!isSchema && currentClass) { // // TODO: ideally we should check for actual @colyseus/schema module // reference rather than arbitrary strings. // isSchema = (currentClass.extends === "Schema" || currentClass.extends === "schema.Schema" || currentClass.extends === "Schema.Schema"); // // When extending from `schema.Schema`, it is required to // normalize as "Schema" for code generation. // if (currentClass === klass && isSchema) { klass.extends = "Schema"; } currentClass = this.getParentClass(currentClass); } return isSchema; } } class Interface { context; name; properties = []; addProperty(property) { if (property.type.indexOf("[]") >= 0) { // is array! property.childType = property.type.match(/([^\[]+)/i)[1]; property.type = "array"; this.properties.push(property); } else { this.properties.push(property); } } } class Class { context; name; properties = []; extends; addProperty(property) { property.index = this.properties.length; this.properties.push(property); } postProcessing() { /** * Ensure the proprierties `index` are correct using inheritance */ let parentKlass = this; while (parentKlass && (parentKlass = this.context.classes.find(k => k.name === parentKlass.extends))) { this.properties.forEach(prop => { prop.index += parentKlass.properties.length; }); } } } class Enum { context; name; properties = []; addProperty(property) { this.properties.push(property); } } class Property { index; name; type; childType; deprecated; } function getInheritanceTree(klass, allClasses, includeSelf = true) { let currentClass = klass; let inheritanceTree = []; if (includeSelf) { inheritanceTree.push(currentClass); } while (currentClass.extends !== "Schema") { currentClass = allClasses.find(klass => klass.name == currentClass.extends); inheritanceTree.push(currentClass); } return inheritanceTree; } let currentStructure; let currentProperty; let globalContext; function defineProperty(property, initializer) { if (ts__namespace.isIdentifier(initializer)) { property.type = "ref"; property.childType = initializer.text; } else if (initializer.kind == ts__namespace.SyntaxKind.ObjectLiteralExpression) { property.type = initializer.properties[0].name.text; property.childType = initializer.properties[0].initializer.text; } else if (initializer.kind == ts__namespace.SyntaxKind.ArrayLiteralExpression) { property.type = "array"; property.childType = initializer.elements[0].text; } else { property.type = initializer.text; } } function inspectNode(node, context, decoratorName) { switch (node.kind) { case ts__namespace.SyntaxKind.ImportClause: const specifier = node.parent.moduleSpecifier; if (specifier && specifier.text.startsWith('.')) { const currentDir = path__namespace.dirname(node.getSourceFile().fileName); const pathToImport = path__namespace.resolve(currentDir, specifier.text); parseFiles([pathToImport], decoratorName, globalContext); } break; case ts__namespace.SyntaxKind.ClassDeclaration: currentStructure = new Class(); const heritageClauses = node.heritageClauses; if (heritageClauses && heritageClauses.length > 0) { currentStructure.extends = heritageClauses[0].types[0].expression.getText(); } context.addStructure(currentStructure); break; case ts__namespace.SyntaxKind.InterfaceDeclaration: // // Only generate Interfaces if it has "Message" on its name. // Example: MyMessage // const interfaceName = node.name.escapedText.toString(); if (interfaceName.indexOf("Message") !== -1) { currentStructure = new Interface(); currentStructure.name = interfaceName; context.addStructure(currentStructure); } break; case ts__namespace.SyntaxKind.EnumDeclaration: const enumName = node.name.escapedText.toString(); currentStructure = new Enum(); currentStructure.name = enumName; context.addStructure(currentStructure); break; case ts__namespace.SyntaxKind.ExtendsKeyword: // console.log(node.getText()); break; case ts__namespace.SyntaxKind.PropertySignature: if (currentStructure instanceof Interface) { const parent = node.parent; // Only process direct children of InterfaceDeclaration, skip TypeLiterals if (!ts__namespace.isInterfaceDeclaration(parent)) { break; } // Skip if property if for a another interface than the one we're interested in. if (currentStructure.name !== parent.name.escapedText.toString()) { break; } // define a property of an interface const property = new Property(); property.name = node.name.escapedText.toString(); property.type = node.type.getText(); currentStructure.addProperty(property); } break; case ts__namespace.SyntaxKind.Identifier: if (node.getText() === "deprecated" && node.parent.kind !== ts__namespace.SyntaxKind.ImportSpecifier) { currentProperty = new Property(); currentProperty.deprecated = true; break; } if (node.getText() === decoratorName) { const prop = node.parent?.parent?.parent; const propDecorator = getDecorators(prop); const hasExpression = prop?.expression?.arguments; const hasDecorator = (propDecorator?.length > 0); /** * neither a `@type()` decorator or `type()` call. skip. */ if (!hasDecorator && !hasExpression) { break; } // using as decorator if (propDecorator) { /** * Calling `@type()` as decorator */ const typeDecorator = propDecorator.find((decorator => { return decorator.expression.expression.escapedText === decoratorName; })).expression; const property = currentProperty || new Property(); property.name = prop.name.escapedText; currentStructure.addProperty(property); const typeArgument = typeDecorator.arguments[0]; defineProperty(property, typeArgument); } else if (prop.expression.arguments?.[1] && prop.expression.expression.arguments?.[0]) { /** * Calling `type()` as a regular method */ const property = currentProperty || new Property(); property.name = prop.expression.arguments[1].text; currentStructure.addProperty(property); const typeArgument = prop.expression.expression.arguments[0]; defineProperty(property, typeArgument); } } else if (node.getText() === "setFields" && (node.parent.kind === ts__namespace.SyntaxKind.CallExpression || node.parent.kind === ts__namespace.SyntaxKind.PropertyAccessExpression)) { /** * Metadata.setFields(klassName, { ... }) */ const callExpression = (node.parent.kind === ts__namespace.SyntaxKind.PropertyAccessExpression) ? node.parent.parent : node.parent; /** * Skip if @codegen-ignore comment is found before the call expression * TODO: currently, if @codegen-ignore is on the file, it will skip all the setFields calls. */ const sourceFile = node.getSourceFile(); const fullText = sourceFile.getFullText(); const nodeStart = callExpression.getFullStart(); const textBeforeNode = fullText.substring(0, nodeStart); if (textBeforeNode.includes('@codegen-ignore')) { break; } if (callExpression.kind !== ts__namespace.SyntaxKind.CallExpression) { break; } const classNameNode = callExpression.arguments[0]; const className = ts__namespace.isClassExpression(classNameNode) ? classNameNode.name?.escapedText.toString() : classNameNode.getText(); // skip if no className is provided if (!className) { break; } if (currentStructure?.name !== className) { currentStructure = new Class(); } context.addStructure(currentStructure); currentStructure.extends = "Schema"; // force extends to Schema currentStructure.name = className; const types = callExpression.arguments[1]; for (let i = 0; i < types.properties.length; i++) { const prop = types.properties[i]; const property = currentProperty || new Property(); property.name = prop.name.escapedText; currentStructure.addProperty(property); defineProperty(property, prop.initializer); } } else if (node.getText() === "defineTypes" && (node.parent.kind === ts__namespace.SyntaxKind.CallExpression || node.parent.kind === ts__namespace.SyntaxKind.PropertyAccessExpression)) { /** * JavaScript source file (`.js`) * Using `defineTypes()` */ const callExpression = (node.parent.kind === ts__namespace.SyntaxKind.PropertyAccessExpression) ? node.parent.parent : node.parent; if (callExpression.kind !== ts__namespace.SyntaxKind.CallExpression) { break; } const className = callExpression.arguments[0].getText(); currentStructure.name = className; const types = callExpression.arguments[1]; for (let i = 0; i < types.properties.length; i++) { const prop = types.properties[i]; const property = currentProperty || new Property(); property.name = prop.name.escapedText; currentStructure.addProperty(property); defineProperty(property, prop.initializer); } } if (node.parent.kind === ts__namespace.SyntaxKind.ClassDeclaration) { currentStructure.name = node.getText(); } currentProperty = undefined; break; case ts__namespace.SyntaxKind.CallExpression: /** * Defining schema via `schema.schema({ ... })` * - schema.schema({}) * - schema({}) * - ClassName.extends({}) */ if (((node.expression?.getText() === "schema.schema" || node.expression?.getText() === "schema") || (node.expression?.getText().indexOf(".extends") !== -1)) && node.arguments[0].kind === ts__namespace.SyntaxKind.ObjectLiteralExpression) { const callExpression = node; let className = callExpression.arguments[1]?.getText(); if (!className && callExpression.parent.kind === ts__namespace.SyntaxKind.VariableDeclaration) { className = callExpression.parent.name?.getText(); } // skip if no className is provided if (!className) { break; } if (currentStructure?.name !== className) { currentStructure = new Class(); context.addStructure(currentStructure); } if (node.expression?.getText().indexOf(".extends") !== -1) { // if it's using `.extends({})` const extendsClass = node.expression?.expression?.escapedText; // skip if no extendsClass is provided if (!extendsClass) { break; } currentStructure.extends = extendsClass; } else { // if it's using `schema({})` currentStructure.extends = "Schema"; // force extends to Schema } currentStructure.name = className; const types = callExpression.arguments[0]; for (let i = 0; i < types.properties.length; i++) { const prop = types.properties[i]; const property = currentProperty || new Property(); property.name = prop.name.escapedText; currentStructure.addProperty(property); defineProperty(property, prop.initializer); } } break; case ts__namespace.SyntaxKind.EnumMember: if (currentStructure instanceof Enum) { const initializer = node.initializer?.text; const name = node.getFirstToken().getText(); const property = currentProperty || new Property(); property.name = name; if (initializer !== undefined) { property.type = initializer; } currentStructure.addProperty(property); currentProperty = undefined; } break; } ts__namespace.forEachChild(node, (n) => inspectNode(n, context, decoratorName)); } let parsedFiles; function parseFiles(fileNames, decoratorName = "type", context = new Context()) { /** * Re-set globalContext for each test case */ if (globalContext !== context) { parsedFiles = {}; globalContext = context; } fileNames.forEach((fileName) => { let sourceFile; let sourceFileName; const fileNameAlternatives = []; if (!fileName.endsWith(".ts") && !fileName.endsWith(".js") && !fileName.endsWith(".mjs")) { fileNameAlternatives.push(`${fileName}.ts`); fileNameAlternatives.push(`${fileName}/index.ts`); } else if (fileName.endsWith(".js")) { // Handle .js extensions by also trying .ts (ESM imports often use .js extension) fileNameAlternatives.push(fileName); fileNameAlternatives.push(fileName.replace(/\.js$/, ".ts")); } else { fileNameAlternatives.push(fileName); } for (let i = 0; i < fileNameAlternatives.length; i++) { try { sourceFileName = path__namespace.resolve(fileNameAlternatives[i]); if (parsedFiles[sourceFileName]) { break; } sourceFile = ts__namespace.createSourceFile(sourceFileName, fs.readFileSync(sourceFileName).toString(), ts__namespace.ScriptTarget.Latest, true); parsedFiles[sourceFileName] = true; break; } catch (e) { // console.log(`${fileNameAlternatives[i]} => ${e.message}`); } } if (sourceFile) { inspectNode(sourceFile, context, decoratorName); } }); return context.getStructures(); } /** * TypeScript 4.8+ has introduced a change on how to access decorators. * - https://github.com/microsoft/TypeScript/pull/49089 * - https://devblogs.microsoft.com/typescript/announcing-typescript-4-8/#decorators-are-placed-on-modifiers-on-typescripts-syntax-trees */ function getDecorators(node) { if (node == undefined) { return undefined; } // TypeScript 4.7 and below // @ts-ignore if (node.decorators) { return node.decorators; } // TypeScript 4.8 and above // @ts-ignore if (ts__namespace.canHaveDecorators && ts__namespace.canHaveDecorators(node)) { // @ts-ignore const decorators = ts__namespace.getDecorators(node); return decorators ? Array.from(decorators) : undefined; } // @ts-ignore return node.modifiers?.filter(ts__namespace.isDecorator); } const name$8 = "Unity/C#"; const typeMaps$8 = { "string": "string", "number": "float", "boolean": "bool", "int8": "sbyte", "uint8": "byte", "int16": "short", "uint16": "ushort", "int32": "int", "uint32": "uint", "int64": "long", "uint64": "ulong", "float32": "float", "float64": "double", }; const COMMON_IMPORTS$5 = `using Colyseus.Schema; #if UNITY_5_3_OR_NEWER using UnityEngine.Scripting; #endif`; /** * C# Code Generator */ const capitalize$1 = (s) => { if (typeof s !== 'string') return ''; return s.charAt(0).toUpperCase() + s.slice(1); }; /** * Generate individual files for each class/interface/enum */ function generate$9(context, options) { // enrich typeMaps with enums context.enums.forEach((structure) => { typeMaps$8[structure.name] = structure.name; }); return [ ...context.classes.map(structure => ({ name: `${structure.name}.cs`, content: generateClass$8(structure, options.namespace) })), ...context.interfaces.map(structure => ({ name: `${structure.name}.cs`, content: generateInterface$1(structure, options.namespace), })), ...context.enums.filter(structure => structure.name !== 'OPERATION').map((structure) => ({ name: `${structure.name}.cs`, content: generateEnum$1(structure, options.namespace), })), ]; } /** * Generate a single bundled file containing all classes, interfaces, and enums */ function renderBundle$8(context, options) { const fileName = options.namespace ? `${options.namespace}.cs` : "Schema.cs"; const indent = options.namespace ? "\t" : ""; // enrich typeMaps with enums context.enums.forEach((structure) => { typeMaps$8[structure.name] = structure.name; }); // Collect all bodies const classBodies = context.classes.map(klass => generateClassBody$8(klass, indent)); const interfaceBodies = context.interfaces.map(iface => generateInterfaceBody$1(iface, indent)); const enumBodies = context.enums .filter(structure => structure.name !== 'OPERATION') .map(e => generateEnumBody$1(e, indent)); const allBodies = [...classBodies, ...interfaceBodies, ...enumBodies].join("\n\n"); const content = `${getCommentHeader()} ${COMMON_IMPORTS$5} ${options.namespace ? `\nnamespace ${options.namespace} {\n` : ""} ${allBodies} ${options.namespace ? "}" : ""}`; return { name: fileName, content }; } /** * Generate just the class body (without imports/namespace) for bundling */ function generateClassBody$8(klass, indent = "") { return `${indent}public partial class ${klass.name} : ${klass.extends} { #if UNITY_5_3_OR_NEWER [Preserve] #endif public ${klass.name}() { } ${klass.properties.map((prop) => generateProperty$4(prop, indent)).join("\n\n")} ${indent}}`; } /** * Generate a complete class file with imports/namespace (for individual file mode) */ function generateClass$8(klass, namespace) { const indent = (namespace) ? "\t" : ""; return `${getCommentHeader()} ${COMMON_IMPORTS$5} ${namespace ? `\nnamespace ${namespace} {` : ""} ${generateClassBody$8(klass, indent)} ${namespace ? "}" : ""} `; } /** * Check if all enum members resolve to non-negative integers, * allowing emission as a native C# `enum` (which only supports integral types). */ function canUseNativeEnum(_enum) { return _enum.properties.every((prop) => { if (!prop.type) return true; const n = Number(prop.type); return Number.isInteger(n) && n >= 0; }); } /** * Generate just the enum body (without imports/namespace) for bundling */ function generateEnumBody$1(_enum, indent = "") { if (canUseNativeEnum(_enum)) { const members = _enum.properties .map((prop, i) => { const value = prop.type ? Number(prop.type) : i; return `${indent}\t${prop.name} = ${value},`; }) .join("\n"); return `${indent}public enum ${_enum.name} : int { ${members} ${indent}}`; } return `${indent}public struct ${_enum.name} { ${_enum.properties .map((prop) => { let dataType = "int"; let value; if (prop.type) { if (isNaN(Number(prop.type))) { value = `"${prop.type}"`; dataType = "string"; } else { value = Number(prop.type); dataType = Number.isInteger(value) ? 'int' : 'float'; } } else { value = _enum.properties.indexOf(prop); } return `${indent}\tpublic const ${dataType} ${prop.name} = ${value};`; }) .join("\n")} ${indent}}`; } /** * Generate a complete enum file with imports/namespace (for individual file mode) */ function generateEnum$1(_enum, namespace) { const indent = namespace ? "\t" : ""; return `${getCommentHeader()} ${namespace ? `\nnamespace ${namespace} {` : ""} ${generateEnumBody$1(_enum, indent)} ${namespace ? "}" : ""}`; } function generateProperty$4(prop, indent = "") { let typeArgs = `"${prop.type}"`; let property = "public"; let langType; let initializer = ""; if (prop.childType) { const isUpcaseFirst = prop.childType.match(/^[A-Z]/); langType = getType(prop); typeArgs += `, typeof(${langType})`; if (!isUpcaseFirst) { typeArgs += `, "${prop.childType}"`; } initializer = `null`; } else { langType = getType(prop); initializer = `default(${langType})`; } property += ` ${langType} ${prop.name}`; let ret = (prop.deprecated) ? `\t\t[System.Obsolete("field '${prop.name}' is deprecated.", true)]\n` : ''; return ret + `\t${indent}[Type(${prop.index}, ${typeArgs})] \t${indent}${property} = ${initializer};`; } /** * Generate just the interface body (without imports/namespace) for bundling */ function generateInterfaceBody$1(struct, indent = "") { return `${indent}public class ${struct.name} { ${struct.properties.map(prop => `\t${indent}public ${getType(prop)} ${prop.name};`).join("\n")} ${indent}}`; } /** * Generate a complete interface file with imports/namespace (for individual file mode) */ function generateInterface$1(struct, namespace) { const indent = (namespace) ? "\t" : ""; return `${getCommentHeader()} using Colyseus.Schema; ${namespace ? `\nnamespace ${namespace} {` : ""} ${generateInterfaceBody$1(struct, indent)} ${namespace ? "}" : ""} `; } function getChildType(prop) { return typeMaps$8[prop.childType]; } function getType(prop) { if (prop.childType) { const isUpcaseFirst = prop.childType.match(/^[A-Z]/); let type; if (prop.type === "ref") { type = (isUpcaseFirst) ? prop.childType : getChildType(prop); } else { const containerClass = capitalize$1(prop.type); type = (isUpcaseFirst) ? `${containerClass}Schema<${prop.childType}>` : `${containerClass}Schema<${getChildType(prop)}>`; } return type; } else { return (prop.type === "array") ? `${typeMaps$8[prop.childType] || prop.childType}[]` : typeMaps$8[prop.type]; } } var csharp = /*#__PURE__*/Object.freeze({ __proto__: null, generate: generate$9, name: name$8, renderBundle: renderBundle$8 }); const name$7 = "C++"; const typeMaps$7 = { "string": "string", "number": "varint_t", "boolean": "bool", "int8": "int8_t", "uint8": "uint8_t", "int16": "int16_t", "uint16": "uint16_t", "int32": "int32_t", "uint32": "uint32_t", "int64": "int64_t", "uint64": "uint64_t", "float32": "float32_t", "float64": "float64_t", }; const typeInitializer$2 = { "string": '""', "number": "0", "boolean": "false", "int8": "0", "uint8": "0", "int16": "0", "uint16": "0", "int32": "0", "uint32": "0", "int64": "0", "uint64": "0", "float32": "0", "float64": "0", }; const COMMON_INCLUDES$1 = `#include "schema.h" #include <typeinfo> #include <typeindex> using namespace colyseus::schema;`; /** * C++ Code Generator */ const capitalize = (s) => { if (typeof s !== 'string') return ''; return s.charAt(0).toUpperCase() + s.slice(1); }; const distinct$5 = (value, index, self) => self.indexOf(value) === index; /** * Generate individual files for each class */ function generate$8(context, options) { return context.classes.map(klass => ({ name: klass.name + ".hpp", content: generateClass$7(klass, options.namespace, context.classes) })); } /** * Generate a single bundled header file containing all classes */ function renderBundle$7(context, options) { const fileName = options.namespace ? `${options.namespace}.hpp` : "schema.hpp"; const guardName = `__SCHEMA_CODEGEN_${(options.namespace || "SCHEMA").toUpperCase()}_H__`; const classBodies = context.classes.map(klass => generateClassBody$7(klass, context.classes, options.namespace)); const content = `${getCommentHeader()} #ifndef ${guardName} #define ${guardName} 1 ${COMMON_INCLUDES$1} ${options.namespace ? `namespace ${options.namespace} {\n` : ""} ${classBodies.join("\n\n")} ${options.namespace ? "}" : ""} #endif `; return { name: fileName, content }; } /** * Generate just the class body (without includes/guards) for bundling */ function generateClassBody$7(klass, allClasses, namespace) { const propertiesPerType = {}; const allRefs = []; klass.properties.forEach(property => { let type = property.type; if (!propertiesPerType[type]) { propertiesPerType[type] = []; } propertiesPerType[type].push(property); // keep all refs list if ((type === "ref" || type === "array" || type === "map")) { allRefs.push(property); } }); const allProperties = getAllProperties$1(klass, allClasses); const createInstanceMethod = (allRefs.length === 0) ? "" : `\tinline Schema* createInstance(std::type_index type) { \t\t${generateFieldIfElseChain(allRefs, (property) => `type == typeid(${property.childType})`, (property) => `return new ${property.childType}();`, (property) => typeMaps$7[property.childType] === undefined)} \t\treturn ${klass.extends}::createInstance(type); \t}`; return `class ${klass.name} : public ${klass.extends} { public: ${klass.properties.map(prop => generateProperty$3(prop)).join("\n")} \t${klass.name}() { \t\tthis->_indexes = ${generateAllIndexes(allProperties)}; \t\tthis->_types = ${generateAllTypes(allProperties)}; \t\tthis->_childPrimitiveTypes = ${generateAllChildPrimitiveTypes(allProperties)}; \t\tthis->_childSchemaTypes = ${generateAllChildSchemaTypes(allProperties)}; \t} \tvirtual ~${klass.name}() { \t\t${generateDestructors(allProperties).join("\n\t\t")} \t} protected: ${Object.keys(propertiesPerType).map(type => generateGettersAndSetters(klass, type, propertiesPerType[type])). join("\n")} ${createInstanceMethod} };`; } /** * Generate a complete class file with includes/guards (for individual file mode) */ function generateClass$7(klass, namespace, allClasses) { const allRefs = []; klass.properties.forEach(property => { let type = property.type; // keep all refs list if ((type === "ref" || type === "array" || type === "map")) { allRefs.push(property); } }); const localIncludes = allRefs. filter(ref => ref.childType && typeMaps$7[ref.childType] === undefined). map(ref => ref.childType). concat(getInheritanceTree(klass, allClasses, false).map(klass => klass.name)). filter(distinct$5). map(childType => `#include "${childType}.hpp"`). join("\n"); return `${getCommentHeader()} #ifndef __SCHEMA_CODEGEN_${klass.name.toUpperCase()}_H__ #define __SCHEMA_CODEGEN_${klass.name.toUpperCase()}_H__ 1 ${COMMON_INCLUDES$1} ${localIncludes} ${namespace ? `namespace ${namespace} {` : ""} ${generateClassBody$7(klass, allClasses)} ${namespace ? "}" : ""} #endif `; } function generateProperty$3(prop) { let property = ""; let langType; let initializer = ""; let isPropPointer = ""; if (prop.childType) { const isUpcaseFirst = prop.childType.match(/^[A-Z]/); if (prop.type === "ref") { langType = `${prop.childType}`; initializer = `new ${prop.childType}()`; } else if (prop.type === "array") { langType = (isUpcaseFirst) ? `ArraySchema<${prop.childType}*>` : `ArraySchema<${typeMaps$7[prop.childType]}>`; initializer = `new ${langType}()`; } else if (prop.type === "map") { langType = (isUpcaseFirst) ? `MapSchema<${prop.childType}*>` : `MapSchema<${typeMaps$7[prop.childType]}>`; initializer = `new ${langType}()`; } isPropPointer = "*"; } else { langType = typeMaps$7[prop.type]; initializer = typeInitializer$2[prop.type]; } property += ` ${langType} ${isPropPointer}${prop.name}`; return `\t${property} = ${initializer};`; } function generateGettersAndSetters(klass, type, properties) { let langType = typeMaps$7[type]; let typeCast = ""; const getMethodName = `get${capitalize(type)}`; const setMethodName = `set${capitalize(type)}`; if (type === "ref") { langType = "Schema*"; } else if (type === "array") { langType = `ArraySchema<char*> *`; typeCast = `(ArraySchema<char*> *)`; } else if (type === "map") { langType = `MapSchema<char*> *`; typeCast = `(MapSchema<char*> *)`; } return `\tinline ${langType} ${getMethodName}(const string &field) \t{ \t\t${generateFieldIfElseChain(properties, (property) => `field == "${property.name}"`, (property) => `return ${typeCast}this->${property.name};`)} \t\treturn ${klass.extends}::${getMethodName}(field); \t} \tinline void ${setMethodName}(const string &field, ${langType} value) \t{ \t\t${generateFieldIfElseChain(properties, (property) => `field == "${property.name}"`, (property) => { const isSchemaType = (typeMaps$7[property.childType] === undefined); if (type === "ref") { langType = `${property.childType}*`; typeCast = (isSchemaType) ? `(${property.childType}*)` : `/* bug? */`; } else if (type === "array") { typeCast = (isSchemaType) ? `(ArraySchema<${property.childType}*> *)` : `(ArraySchema<${typeMaps$7[property.childType]}> *)`; } else if (type === "map") { typeCast = (isSchemaType) ? `(MapSchema<${property.childType}*> *)` : `(MapSchema<${typeMaps$7[property.childType]}> *)`; } return `this->${property.name} = ${typeCast}value;\n\t\t\treturn;`; })} \t\treturn ${klass.extends}::${setMethodName}(field, value); \t}`; } function generateFieldIfElseChain(properties, ifCallback, callback, filter = (_) => true) { let chain = ""; const uniqueChecks = []; properties.filter(filter).forEach((property, i) => { const check = ifCallback(property); if (uniqueChecks.indexOf(check) === -1) { uniqueChecks.push(check); } else { return; } if (i === 0) { chain += "if "; } else { chain += " else if "; } chain += `(${check}) \t\t{ \t\t\t${callback(property)}\n \t\t}`; }); return chain; } function generateAllIndexes(properties) { return `{${properties.map((property, i) => `{${i}, "${property.name}"}`).join(", ")}}`; } function generateAllTypes(properties) { return `{${properties.map((property, i) => `{${i}, "${property.type}"}`).join(", ")}}`; } function generateAllChildSchemaTypes(properties) { return `{${properties.map((property, i) => { if (property.childType && typeMaps$7[property.childType] === undefined) { return `{${i}, typeid(${property.childType})}`; } else { return null; } }).filter(r => r !== null).join(", ")}}`; } function generateAllChildPrimitiveTypes(properties) { return `{${properties.map((property, i) => { if (typeMaps$7[property.childType] !== undefined) { return `{${i}, "${property.childType}"}`; } else { return null; } }).filter(r => r !== null).join(", ")}}`; } function generateDestructors(properties) { return properties.map((property, i) => { if (property.childType) { return `delete this->${property.name};`; } else { return null; } }).filter(r => r !== null); } function getAllProperties$1(klass, allClasses) { let properties = []; getInheritanceTree(klass, allClasses).reverse().forEach((klass) => { properties = properties.concat(klass.properties); }); return properties; } var cpp = /*#__PURE__*/Object.freeze({ __proto__: null, generate: generate$8, name: name$7, renderBundle: renderBundle$7 }); const name$6 = "Haxe"; const typeMaps$6 = { "string": "String", "number": "Dynamic", "boolean": "Bool", "int8": "Int", "uint8": "UInt", "int16": "Int", "uint16": "UInt", "int32": "Int", "uint32": "UInt", "int64": "Int", "uint64": "UInt", "float32": "Float", "float64": "Float", }; const typeInitializer$1 = { "string": '""', "number": "0", "boolean": "false", "int8": "0", "uint8": "0", "int16": "0", "uint16": "0", "int32": "0", "uint32": "0", "int64": "0", "uint64": "0", "float32": "0", "float64": "0", }; const COMMON_IMPORTS$4 = `import io.colyseus.serializer.schema.Schema; import io.colyseus.serializer.schema.types.*;`; /** * Generate individual files for each class */ function generate$7(context, options) { return context.classes.map(klass => ({ name: klass.name + ".hx", content: generateClass$6(klass, options.namespace, context.classes) })); } /** * Generate a single bundled file containing all classes */ function renderBundle$6(context, options) { const fileName = options.namespace ? `${options.namespace}.hx` : "Schema.hx"; const classBodies = context.classes.map(klass => generateClassBody$6(klass)); const content = `${getCommentHeader()} ${options.namespace ? `package ${options.namespace};` : ""} ${COMMON_IMPORTS$4} ${classBodies.join("\n\n")} `; return { name: fileName, content }; } /** * Generate just the class body (without package/imports) for bundling */ function generateClassBody$6(klass) { return `class ${klass.name} extends ${klass.extends} { ${klass.properties.map(prop => generateProperty$2(prop)).join("\n")} }`; } /** * Generate a complete class file with package/imports (for individual file mode) */ function generateClass$6(klass, namespace, allClasses) { return `${getCommentHeader()} ${namespace ? `package ${namespace};` : ""} ${COMMON_IMPORTS$4} ${generateClassBody$6(klass)} `; } function generateProperty$2(prop) { let langType; let initializer = ""; let typeArgs = `"${prop.type}"`; 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}()`; } else if (prop.type === "array") { langType = (isUpcaseFirst) ? `ArraySchema<${prop.childType}>` : `ArraySchema<${typeMaps$6[prop.childType]}>`; initializer = `new ${langType}()`; } else if (prop.type === "map") { langType = (isUpcaseFirst) ? `MapSchema<${prop.childType}>` : `MapSchema<${typeMaps$6[prop.childType]}>`; initializer = `new ${langType}()`; } } else { langType = typeMaps$6[prop.type]; initializer = typeInitializer$1[prop.type]; } // TODO: remove initializer. The callbacks at the Haxe decoder side have a // "FIXME" comment about this on Decoder.hx return `\t@:type(${typeArgs})\n\tpublic var ${prop.name}: ${langType} = ${initializer};\n`; // return `\t@:type(${typeArgs})\n\tpublic var ${prop.name}: ${langType};\n` } var haxe = /*#__PURE__*/Object.freeze({ __proto__: null, generate: generate$7, name: name$6, renderBundle: renderBundle$6 }); const name$5 = "TypeScript"; const typeMaps$5 = { "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$3 = `import { Schema, type, ArraySchema, MapSchema, SetSchema, DataChange } from '@colyseus/schema';`; const distinct$4 = (value, index, self) => self.indexOf(value) === index; /** * Generate individual files for each class/interface */ function generate$6(context, options) { return [ ...context.classes.map(structure => ({ name: structure.name + ".ts", content: generateClass$5(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 */ function renderBundle$5(context, options) { const fileName = options.namespace ? `${options.namespace}.ts` : "schema.ts"; // Collect all class bodies const classBodies = context.classes.map(klass => generateClassBody$5(klass)); // Collect all interface bodies const interfaceBodies = context.interfaces.map(iface => generateInterfaceBody(iface)); const content = `${getCommentHeader()} ${COMMON_IMPORTS$3} ${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$5(klass) { return `export class ${klass.name} extends ${klass.extends} { ${klass.properties.map(prop => ` ${generateProperty$1(prop)}`).join("\n")} }`; } /** * Generate just the interface body (without imports) for bundling */ function generateInterfaceBody(iface) { 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$5(klass, namespace, allClasses) { const allRefs = []; 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$5[ref.childType] === undefined). map(ref => ref.childType). concat(getInheritanceTree(klass, allClasses, false).map(klass => klass.name)). filter(distinct$4). map(childType => `import { ${childType} } from './${childType}'`). join("\n"); return `${getCommentHeader()} ${COMMON_IMPORTS$3} ${localImports} ${generateClassBody$5(klass)} `; } function generateProperty$1(prop) { let langType; let initializer = ""; let typeArgs; 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$5[prop.childType]}>`; initializer = `new ${langType}()`; typeArgs = (isUpcaseFirst) ? `[ ${prop.childType} ]` : `[ "${prop.childType}" ]`; } else if (prop.type === "map") { langType = (isUpcaseFirst) ? `MapSchema<${prop.childType}>` : `MapSchema<${typeMaps$5[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$5[prop.childType]}>`; initializer = `new ${langType}()`; typeArgs = (isUpcaseFirst) ? `{ set: ${prop.childType} }` : `{ set: "${prop.childType}" }`; } } else { langType = typeMaps$5[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, namespace, allClasses) { return `${getCommentHeader()} ${generateInterfaceBody(structure)} `; } var ts = /*#__PURE__*/Object.freeze({ __proto__: null, generate: generate$6, name: name$5, renderBundle: renderBundle$5 }); const name$4 = "JavaScript"; const typeMaps$4 = { "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$2 = `const schema = require("@colyseus/schema"); const Schema = schema.Schema; const type = schema.type;`; const distinct$3 = (value, index, self) => self.indexOf(value) === index; /** * Generate individual files for each class */ function generate$5(context, options) { return context.classes.map(klass => ({ name: klass.name + ".js", content: generateClass$4(klass, options.namespace, context.classes) })); } /** * Generate a single bundled file containing all classes */ function renderBundle$4(context, options) { const fileName = options.namespace ? `${options.namespace}.js` : "schema.js"; const classBodies = context.classes.map(klass => generateClassBody$4(klass)); const classExports = context.classes.map(klass => ` ${klass.name},`).join("\n"); const content = `${getCommentHeader()} ${COMMON_IMPORTS$2} ${classBodies.join("\n\n")} module.exports = { ${c