@colyseus/schema
Version:
Binary state serializer with delta encoding for games
1,421 lines (1,377 loc) • 79 kB
JavaScript
#!/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