@colyseus/schema 4.0.13 → 4.0.14
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +31 -0
- package/build/codegen/api.d.ts +2 -0
- package/build/codegen/cli.cjs +934 -177
- package/build/codegen/cli.cjs.map +1 -1
- package/build/codegen/languages/c.d.ts +11 -0
- package/build/codegen/languages/cpp.d.ts +8 -0
- package/build/codegen/languages/csharp.d.ts +8 -0
- package/build/codegen/languages/gdscript.d.ts +14 -0
- package/build/codegen/languages/haxe.d.ts +8 -0
- package/build/codegen/languages/java.d.ts +11 -1
- package/build/codegen/languages/js.d.ts +8 -0
- package/build/codegen/languages/lua.d.ts +8 -0
- package/build/codegen/languages/ts.d.ts +8 -0
- package/build/codegen/types.d.ts +14 -0
- package/package.json +1 -1
- package/src/codegen/api.ts +26 -15
- package/src/codegen/cli.ts +9 -15
- package/src/codegen/languages/c.ts +282 -0
- package/src/codegen/languages/cpp.ts +74 -22
- package/src/codegen/languages/csharp.ts +87 -19
- package/src/codegen/languages/gdscript.ts +219 -0
- package/src/codegen/languages/haxe.ts +41 -5
- package/src/codegen/languages/java.ts +45 -4
- package/src/codegen/languages/js.ts +62 -20
- package/src/codegen/languages/lua.ts +60 -18
- package/src/codegen/languages/ts.ts +65 -14
- package/src/codegen/types.ts +15 -0
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { File, Context } from "../types.js";
|
|
2
|
+
import { GenerateOptions } from "../api.js";
|
|
3
|
+
export declare const name = "C";
|
|
4
|
+
/**
|
|
5
|
+
* Generate individual files for each class
|
|
6
|
+
*/
|
|
7
|
+
export declare function generate(context: Context, options: GenerateOptions): File[];
|
|
8
|
+
/**
|
|
9
|
+
* Generate a single bundled header file containing all classes
|
|
10
|
+
*/
|
|
11
|
+
export declare function renderBundle(context: Context, options: GenerateOptions): File;
|
|
@@ -1,3 +1,11 @@
|
|
|
1
1
|
import { File, Context } from "../types.js";
|
|
2
2
|
import { GenerateOptions } from "../api.js";
|
|
3
|
+
export declare const name = "C++";
|
|
4
|
+
/**
|
|
5
|
+
* Generate individual files for each class
|
|
6
|
+
*/
|
|
3
7
|
export declare function generate(context: Context, options: GenerateOptions): File[];
|
|
8
|
+
/**
|
|
9
|
+
* Generate a single bundled header file containing all classes
|
|
10
|
+
*/
|
|
11
|
+
export declare function renderBundle(context: Context, options: GenerateOptions): File;
|
|
@@ -1,4 +1,12 @@
|
|
|
1
1
|
import { File } from "../types.js";
|
|
2
2
|
import { GenerateOptions } from "../api.js";
|
|
3
3
|
import { Context } from "../types.js";
|
|
4
|
+
export declare const name = "Unity/C#";
|
|
5
|
+
/**
|
|
6
|
+
* Generate individual files for each class/interface/enum
|
|
7
|
+
*/
|
|
4
8
|
export declare function generate(context: Context, options: GenerateOptions): File[];
|
|
9
|
+
/**
|
|
10
|
+
* Generate a single bundled file containing all classes, interfaces, and enums
|
|
11
|
+
*/
|
|
12
|
+
export declare function renderBundle(context: Context, options: GenerateOptions): File;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { File, Context } from "../types.js";
|
|
2
|
+
import { GenerateOptions } from "../api.js";
|
|
3
|
+
export declare const name = "GDScript";
|
|
4
|
+
/**
|
|
5
|
+
* GDScript Code Generator
|
|
6
|
+
*/
|
|
7
|
+
/**
|
|
8
|
+
* Generate individual files for each class
|
|
9
|
+
*/
|
|
10
|
+
export declare function generate(context: Context, options: GenerateOptions): File[];
|
|
11
|
+
/**
|
|
12
|
+
* Generate a single bundled file containing all classes and enums
|
|
13
|
+
*/
|
|
14
|
+
export declare function renderBundle(context: Context, options: GenerateOptions): File;
|
|
@@ -1,3 +1,11 @@
|
|
|
1
1
|
import { File, Context } from "../types.js";
|
|
2
2
|
import { GenerateOptions } from "../api.js";
|
|
3
|
+
export declare const name = "Haxe";
|
|
4
|
+
/**
|
|
5
|
+
* Generate individual files for each class
|
|
6
|
+
*/
|
|
3
7
|
export declare function generate(context: Context, options: GenerateOptions): File[];
|
|
8
|
+
/**
|
|
9
|
+
* Generate a single bundled file containing all classes
|
|
10
|
+
*/
|
|
11
|
+
export declare function renderBundle(context: Context, options: GenerateOptions): File;
|
|
@@ -1,6 +1,16 @@
|
|
|
1
1
|
import { File, Context } from "../types.js";
|
|
2
2
|
import { GenerateOptions } from "../api.js";
|
|
3
|
+
export declare const name = "Java";
|
|
3
4
|
/**
|
|
4
|
-
*
|
|
5
|
+
* Java Code Generator
|
|
6
|
+
*/
|
|
7
|
+
/**
|
|
8
|
+
* Generate individual files for each class
|
|
5
9
|
*/
|
|
6
10
|
export declare function generate(context: Context, options: GenerateOptions): File[];
|
|
11
|
+
/**
|
|
12
|
+
* Generate a single bundled file containing all classes
|
|
13
|
+
* Note: Java typically requires one public class per file, so bundled mode
|
|
14
|
+
* generates all classes in a single file with package-private visibility
|
|
15
|
+
*/
|
|
16
|
+
export declare function renderBundle(context: Context, options: GenerateOptions): File;
|
|
@@ -1,3 +1,11 @@
|
|
|
1
1
|
import { File, Context } from "../types.js";
|
|
2
2
|
import { GenerateOptions } from "../api.js";
|
|
3
|
+
export declare const name = "JavaScript";
|
|
4
|
+
/**
|
|
5
|
+
* Generate individual files for each class
|
|
6
|
+
*/
|
|
3
7
|
export declare function generate(context: Context, options: GenerateOptions): File[];
|
|
8
|
+
/**
|
|
9
|
+
* Generate a single bundled file containing all classes
|
|
10
|
+
*/
|
|
11
|
+
export declare function renderBundle(context: Context, options: GenerateOptions): File;
|
|
@@ -1,3 +1,11 @@
|
|
|
1
1
|
import { File, Context } from "../types.js";
|
|
2
2
|
import { GenerateOptions } from "../api.js";
|
|
3
|
+
export declare const name = "LUA";
|
|
4
|
+
/**
|
|
5
|
+
* Generate individual files for each class
|
|
6
|
+
*/
|
|
3
7
|
export declare function generate(context: Context, options: GenerateOptions): File[];
|
|
8
|
+
/**
|
|
9
|
+
* Generate a single bundled file containing all classes
|
|
10
|
+
*/
|
|
11
|
+
export declare function renderBundle(context: Context, options: GenerateOptions): File;
|
|
@@ -1,3 +1,11 @@
|
|
|
1
1
|
import { File, Context } from "../types.js";
|
|
2
2
|
import { GenerateOptions } from "../api.js";
|
|
3
|
+
export declare const name = "TypeScript";
|
|
4
|
+
/**
|
|
5
|
+
* Generate individual files for each class/interface
|
|
6
|
+
*/
|
|
3
7
|
export declare function generate(context: Context, options: GenerateOptions): File[];
|
|
8
|
+
/**
|
|
9
|
+
* Generate a single bundled file containing all classes and interfaces
|
|
10
|
+
*/
|
|
11
|
+
export declare function renderBundle(context: Context, options: GenerateOptions): File;
|
package/build/codegen/types.d.ts
CHANGED
|
@@ -49,4 +49,18 @@ export interface File {
|
|
|
49
49
|
name: string;
|
|
50
50
|
content: string;
|
|
51
51
|
}
|
|
52
|
+
/**
|
|
53
|
+
* Structured file representation for code generation.
|
|
54
|
+
* Separates imports, local references, and body content to enable
|
|
55
|
+
* clean bundling without string parsing.
|
|
56
|
+
*/
|
|
57
|
+
export interface GeneratedFile {
|
|
58
|
+
name: string;
|
|
59
|
+
/** External imports (e.g., "@colyseus/schema", "Colyseus.Schema") */
|
|
60
|
+
imports: string[];
|
|
61
|
+
/** References to other generated classes (used for imports in non-bundle mode) */
|
|
62
|
+
localRefs: string[];
|
|
63
|
+
/** The class/interface/enum definition body (without imports or namespace wrapper) */
|
|
64
|
+
body: string;
|
|
65
|
+
}
|
|
52
66
|
export declare function getInheritanceTree(klass: Class, allClasses: Class[], includeSelf?: boolean): Class[];
|
package/package.json
CHANGED
package/src/codegen/api.ts
CHANGED
|
@@ -5,21 +5,24 @@ import { File } from "./types.js";
|
|
|
5
5
|
import { parseFiles } from "./parser.js";
|
|
6
6
|
|
|
7
7
|
// Statically import all language generators (for bundling)
|
|
8
|
-
import
|
|
9
|
-
import
|
|
10
|
-
import
|
|
11
|
-
import
|
|
12
|
-
import
|
|
13
|
-
import
|
|
14
|
-
import
|
|
8
|
+
import * as csharp from "./languages/csharp.js";
|
|
9
|
+
import * as cpp from "./languages/cpp.js";
|
|
10
|
+
import * as haxe from "./languages/haxe.js";
|
|
11
|
+
import * as ts from "./languages/ts.js";
|
|
12
|
+
import * as js from "./languages/js.js";
|
|
13
|
+
import * as java from "./languages/java.js";
|
|
14
|
+
import * as lua from "./languages/lua.js";
|
|
15
|
+
import * as c from "./languages/c.js";
|
|
16
|
+
import * as gdscript from "./languages/gdscript.js";
|
|
15
17
|
|
|
16
|
-
const generators: Record<string,
|
|
18
|
+
export const generators: Record<string, any> = { csharp, cpp, haxe, ts, js, java, lua, c, gdscript, };
|
|
17
19
|
|
|
18
20
|
export interface GenerateOptions {
|
|
19
21
|
files: string[],
|
|
20
22
|
output: string;
|
|
21
23
|
decorator?: string;
|
|
22
24
|
namespace?: string;
|
|
25
|
+
bundle?: boolean;
|
|
23
26
|
}
|
|
24
27
|
|
|
25
28
|
export function generate(targetId: string, options: GenerateOptions) {
|
|
@@ -54,13 +57,21 @@ export function generate(targetId: string, options: GenerateOptions) {
|
|
|
54
57
|
// Post-process classes before generating
|
|
55
58
|
structures.classes.forEach(klass => klass.postProcessing());
|
|
56
59
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
const outputPath = path.resolve(options.output,
|
|
61
|
-
fs.writeFileSync(outputPath,
|
|
62
|
-
console.log("generated:",
|
|
63
|
-
}
|
|
60
|
+
if (options.bundle && generator.renderBundle) {
|
|
61
|
+
// Bundle mode: generate all classes/interfaces/enums into a single file
|
|
62
|
+
const bundled = generator.renderBundle(structures, options);
|
|
63
|
+
const outputPath = path.resolve(options.output, bundled.name);
|
|
64
|
+
fs.writeFileSync(outputPath, bundled.content);
|
|
65
|
+
console.log("generated (bundled):", bundled.name);
|
|
66
|
+
} else {
|
|
67
|
+
// Standard mode: write individual files
|
|
68
|
+
const generatedFiles = generator.generate(structures, options);
|
|
69
|
+
generatedFiles.forEach((file: File) => {
|
|
70
|
+
const outputPath = path.resolve(options.output, file.name);
|
|
71
|
+
fs.writeFileSync(outputPath, file.content);
|
|
72
|
+
console.log("generated:", file.name);
|
|
73
|
+
});
|
|
74
|
+
}
|
|
64
75
|
}
|
|
65
76
|
|
|
66
77
|
function recursiveFiles(dir: string): string[] {
|
package/src/codegen/cli.ts
CHANGED
|
@@ -1,15 +1,5 @@
|
|
|
1
1
|
import argv from "./argv.js";
|
|
2
|
-
import { generate } from "./api.js";
|
|
3
|
-
|
|
4
|
-
const supportedTargets: Record<string, string> = {
|
|
5
|
-
csharp: 'generate for C#/Unity',
|
|
6
|
-
cpp: 'generate for C++',
|
|
7
|
-
haxe: 'generate for Haxe',
|
|
8
|
-
ts: 'generate for TypeScript',
|
|
9
|
-
js: 'generate for JavaScript',
|
|
10
|
-
java: 'generate for Java',
|
|
11
|
-
lua: 'generate for LUA',
|
|
12
|
-
}
|
|
2
|
+
import { generate, generators } from "./api.js";
|
|
13
3
|
|
|
14
4
|
function displayHelp() {
|
|
15
5
|
console.log(`\nschema-codegen [path/to/Schema.ts]
|
|
@@ -19,10 +9,13 @@ Usage (C#/Unity)
|
|
|
19
9
|
|
|
20
10
|
Valid options:
|
|
21
11
|
--output: the output directory for generated client-side schema files
|
|
12
|
+
--bundle: bundle all generated files into a single file
|
|
13
|
+
|
|
14
|
+
Generators:
|
|
22
15
|
${Object.
|
|
23
|
-
keys(
|
|
16
|
+
keys(generators).
|
|
24
17
|
map((targetId) => (
|
|
25
|
-
` --${targetId}: ${
|
|
18
|
+
` --${targetId}: generate for ${generators[targetId].name}`
|
|
26
19
|
)).
|
|
27
20
|
join("\n")}
|
|
28
21
|
|
|
@@ -38,7 +31,7 @@ if (args.help) {
|
|
|
38
31
|
}
|
|
39
32
|
|
|
40
33
|
let targetId: string;
|
|
41
|
-
for (let target in
|
|
34
|
+
for (let target in generators) {
|
|
42
35
|
if (args[target]) {
|
|
43
36
|
targetId = target;
|
|
44
37
|
}
|
|
@@ -55,7 +48,8 @@ try {
|
|
|
55
48
|
files: args._,
|
|
56
49
|
decorator: args.decorator,
|
|
57
50
|
output: args.output,
|
|
58
|
-
namespace: args.namespace
|
|
51
|
+
namespace: args.namespace,
|
|
52
|
+
bundle: args.bundle
|
|
59
53
|
});
|
|
60
54
|
|
|
61
55
|
} catch (e) {
|
|
@@ -0,0 +1,282 @@
|
|
|
1
|
+
import { Class, Property, File, getCommentHeader, getInheritanceTree, Context } from "../types.js";
|
|
2
|
+
import { GenerateOptions } from "../api.js";
|
|
3
|
+
|
|
4
|
+
export const name = "C";
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Type mappings for C
|
|
8
|
+
*/
|
|
9
|
+
const typeMaps: { [key: string]: string } = {
|
|
10
|
+
"string": "char*",
|
|
11
|
+
"number": "double",
|
|
12
|
+
"boolean": "bool",
|
|
13
|
+
"int8": "int8_t",
|
|
14
|
+
"uint8": "uint8_t",
|
|
15
|
+
"int16": "int16_t",
|
|
16
|
+
"uint16": "uint16_t",
|
|
17
|
+
"int32": "int32_t",
|
|
18
|
+
"uint32": "uint32_t",
|
|
19
|
+
"int64": "int64_t",
|
|
20
|
+
"uint64": "uint64_t",
|
|
21
|
+
"float32": "float",
|
|
22
|
+
"float64": "double",
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Colyseus field type enum mappings
|
|
27
|
+
*/
|
|
28
|
+
const fieldTypeMaps: { [key: string]: string } = {
|
|
29
|
+
"string": "COLYSEUS_FIELD_STRING",
|
|
30
|
+
"number": "COLYSEUS_FIELD_NUMBER",
|
|
31
|
+
"boolean": "COLYSEUS_FIELD_BOOLEAN",
|
|
32
|
+
"int8": "COLYSEUS_FIELD_INT8",
|
|
33
|
+
"uint8": "COLYSEUS_FIELD_UINT8",
|
|
34
|
+
"int16": "COLYSEUS_FIELD_INT16",
|
|
35
|
+
"uint16": "COLYSEUS_FIELD_UINT16",
|
|
36
|
+
"int32": "COLYSEUS_FIELD_INT32",
|
|
37
|
+
"uint32": "COLYSEUS_FIELD_UINT32",
|
|
38
|
+
"int64": "COLYSEUS_FIELD_INT64",
|
|
39
|
+
"uint64": "COLYSEUS_FIELD_UINT64",
|
|
40
|
+
"float32": "COLYSEUS_FIELD_FLOAT32",
|
|
41
|
+
"float64": "COLYSEUS_FIELD_FLOAT64",
|
|
42
|
+
"ref": "COLYSEUS_FIELD_REF",
|
|
43
|
+
"array": "COLYSEUS_FIELD_ARRAY",
|
|
44
|
+
"map": "COLYSEUS_FIELD_MAP",
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
const COMMON_INCLUDES = `#include "colyseus/schema/types.h"
|
|
48
|
+
#include "colyseus/schema/collections.h"
|
|
49
|
+
#include <stdlib.h>
|
|
50
|
+
#include <stddef.h>
|
|
51
|
+
#include <stdbool.h>`;
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Native C Code Generator
|
|
55
|
+
*/
|
|
56
|
+
|
|
57
|
+
const toSnakeCase = (s: string) => {
|
|
58
|
+
return s.replace(/([A-Z])/g, (match, p1, offset) =>
|
|
59
|
+
(offset > 0 ? '_' : '') + p1.toLowerCase()
|
|
60
|
+
);
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
const distinct = (value: string, index: number, self: string[]) =>
|
|
64
|
+
self.indexOf(value) === index;
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Generate individual files for each class
|
|
68
|
+
*/
|
|
69
|
+
export function generate(context: Context, options: GenerateOptions): File[] {
|
|
70
|
+
return context.classes.map(klass => ({
|
|
71
|
+
name: toSnakeCase(klass.name) + ".h",
|
|
72
|
+
content: generateClass(klass, options.namespace, context.classes)
|
|
73
|
+
}));
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Generate a single bundled header file containing all classes
|
|
78
|
+
*/
|
|
79
|
+
export function renderBundle(context: Context, options: GenerateOptions): File {
|
|
80
|
+
const fileName = options.namespace ? `${toSnakeCase(options.namespace)}.h` : "schema.h";
|
|
81
|
+
const guardName = `__SCHEMA_CODEGEN_${(options.namespace || "SCHEMA").toUpperCase()}_H__`;
|
|
82
|
+
|
|
83
|
+
const classBodies = context.classes.map(klass =>
|
|
84
|
+
generateClassBody(klass, context.classes)
|
|
85
|
+
).join("\n\n");
|
|
86
|
+
|
|
87
|
+
const content = `${getCommentHeader()}
|
|
88
|
+
#ifndef ${guardName}
|
|
89
|
+
#define ${guardName} 1
|
|
90
|
+
|
|
91
|
+
${COMMON_INCLUDES}
|
|
92
|
+
|
|
93
|
+
${classBodies}
|
|
94
|
+
|
|
95
|
+
#endif
|
|
96
|
+
`;
|
|
97
|
+
|
|
98
|
+
return { name: fileName, content };
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Generate just the class body (without guards/includes) for bundling
|
|
103
|
+
*/
|
|
104
|
+
function generateClassBody(klass: Class, allClasses: Class[]): string {
|
|
105
|
+
const snakeName = toSnakeCase(klass.name);
|
|
106
|
+
const typeName = `${snakeName}_t`;
|
|
107
|
+
const allProperties = getAllProperties(klass, allClasses);
|
|
108
|
+
|
|
109
|
+
return `${generateTypedef(klass, typeName, allClasses)}
|
|
110
|
+
|
|
111
|
+
${generateFieldsArray(klass, typeName, snakeName, allProperties)}
|
|
112
|
+
|
|
113
|
+
${generateCreateFunction(snakeName, typeName)}
|
|
114
|
+
|
|
115
|
+
${generateDestroyFunction(klass, snakeName, typeName, allProperties)}
|
|
116
|
+
|
|
117
|
+
${generateVtable(klass, snakeName, typeName, allProperties)}`;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Generate a complete class file with guards/includes (for individual file mode)
|
|
122
|
+
*/
|
|
123
|
+
function generateClass(klass: Class, namespace: string, allClasses: Class[]) {
|
|
124
|
+
const snakeName = toSnakeCase(klass.name);
|
|
125
|
+
const typeName = `${snakeName}_t`;
|
|
126
|
+
const guardName = `__SCHEMA_CODEGEN_${klass.name.toUpperCase()}_H__`;
|
|
127
|
+
|
|
128
|
+
const allRefs: Property[] = [];
|
|
129
|
+
|
|
130
|
+
klass.properties.forEach(property => {
|
|
131
|
+
if (property.type === "ref" || property.type === "array" || property.type === "map") {
|
|
132
|
+
allRefs.push(property);
|
|
133
|
+
}
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
// Generate includes for referenced schema types
|
|
137
|
+
const refIncludes = allRefs
|
|
138
|
+
.filter(ref => ref.childType && typeMaps[ref.childType] === undefined)
|
|
139
|
+
.map(ref => ref.childType)
|
|
140
|
+
.concat(getInheritanceTree(klass, allClasses, false).map(k => k.name))
|
|
141
|
+
.filter(distinct)
|
|
142
|
+
.map(childType => `#include "${toSnakeCase(childType)}.h"`)
|
|
143
|
+
.join("\n");
|
|
144
|
+
|
|
145
|
+
return `${getCommentHeader()}
|
|
146
|
+
#ifndef ${guardName}
|
|
147
|
+
#define ${guardName} 1
|
|
148
|
+
|
|
149
|
+
${COMMON_INCLUDES}
|
|
150
|
+
${refIncludes ? `\n${refIncludes}\n` : ""}
|
|
151
|
+
${generateClassBody(klass, allClasses)}
|
|
152
|
+
|
|
153
|
+
#endif
|
|
154
|
+
`;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
function generateTypedef(klass: Class, typeName: string, allClasses: Class[]) {
|
|
158
|
+
const allProperties = getAllProperties(klass, allClasses);
|
|
159
|
+
|
|
160
|
+
const fields = allProperties.map(prop => {
|
|
161
|
+
const cType = getCType(prop);
|
|
162
|
+
return ` ${cType} ${prop.name};`;
|
|
163
|
+
}).join("\n");
|
|
164
|
+
|
|
165
|
+
return `typedef struct {
|
|
166
|
+
colyseus_schema_t __base;
|
|
167
|
+
${fields}
|
|
168
|
+
} ${typeName};`;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
function getCType(prop: Property): string {
|
|
172
|
+
if (prop.type === "ref") {
|
|
173
|
+
return `${toSnakeCase(prop.childType)}_t*`;
|
|
174
|
+
} else if (prop.type === "array") {
|
|
175
|
+
if (typeMaps[prop.childType]) {
|
|
176
|
+
return `colyseus_array_schema_t*`;
|
|
177
|
+
} else {
|
|
178
|
+
return `colyseus_array_schema_t*`;
|
|
179
|
+
}
|
|
180
|
+
} else if (prop.type === "map") {
|
|
181
|
+
if (typeMaps[prop.childType]) {
|
|
182
|
+
return `colyseus_map_schema_t*`;
|
|
183
|
+
} else {
|
|
184
|
+
return `colyseus_map_schema_t*`;
|
|
185
|
+
}
|
|
186
|
+
} else {
|
|
187
|
+
return typeMaps[prop.type] || `${toSnakeCase(prop.type)}_t*`;
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
function getFieldType(prop: Property): string {
|
|
192
|
+
return fieldTypeMaps[prop.type] || "COLYSEUS_FIELD_REF";
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
function getFieldTypeString(prop: Property): string {
|
|
196
|
+
// Always return the type itself (ref, array, map, string, number, etc.)
|
|
197
|
+
return prop.type;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
function generateFieldsArray(klass: Class, typeName: string, snakeName: string, allProperties: Property[]) {
|
|
201
|
+
if (allProperties.length === 0) {
|
|
202
|
+
return `static const colyseus_field_t ${snakeName}_fields[] = {};`;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
const fields = allProperties.map((prop, i) => {
|
|
206
|
+
const fieldType = getFieldType(prop);
|
|
207
|
+
const typeString = getFieldTypeString(prop);
|
|
208
|
+
|
|
209
|
+
let vtableRef = "NULL";
|
|
210
|
+
|
|
211
|
+
if (prop.type === "ref" && prop.childType && !typeMaps[prop.childType]) {
|
|
212
|
+
const childSnake = toSnakeCase(prop.childType);
|
|
213
|
+
vtableRef = `&${childSnake}_vtable`;
|
|
214
|
+
} else if ((prop.type === "array" || prop.type === "map") && prop.childType && !typeMaps[prop.childType]) {
|
|
215
|
+
const childSnake = toSnakeCase(prop.childType);
|
|
216
|
+
vtableRef = `&${childSnake}_vtable`;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
return ` {${prop.index}, "${prop.name}", ${fieldType}, "${typeString}", offsetof(${typeName}, ${prop.name}), ${vtableRef}, NULL}`;
|
|
220
|
+
}).join(",\n");
|
|
221
|
+
|
|
222
|
+
return `static const colyseus_field_t ${snakeName}_fields[] = {
|
|
223
|
+
${fields}
|
|
224
|
+
};`;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
function generateCreateFunction(snakeName: string, typeName: string) {
|
|
228
|
+
return `static ${typeName}* ${snakeName}_create(void) {
|
|
229
|
+
${typeName}* instance = calloc(1, sizeof(${typeName}));
|
|
230
|
+
return instance;
|
|
231
|
+
}`;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
function generateDestroyFunction(klass: Class, snakeName: string, typeName: string, allProperties: Property[]) {
|
|
235
|
+
const freeStatements: string[] = [];
|
|
236
|
+
|
|
237
|
+
allProperties.forEach(prop => {
|
|
238
|
+
if (prop.type === "string") {
|
|
239
|
+
freeStatements.push(` if (instance->${prop.name}) free(instance->${prop.name});`);
|
|
240
|
+
} else if (prop.type === "ref") {
|
|
241
|
+
if (typeMaps[prop.childType]) {
|
|
242
|
+
freeStatements.push(` if (instance->${prop.name}) free(instance->${prop.name});`);
|
|
243
|
+
} else {
|
|
244
|
+
const childSnake = toSnakeCase(prop.childType);
|
|
245
|
+
freeStatements.push(` if (instance->${prop.name}) ${childSnake}_destroy((colyseus_schema_t*)instance->${prop.name});`);
|
|
246
|
+
}
|
|
247
|
+
} else if (prop.type === "array" || prop.type === "map") {
|
|
248
|
+
// arrays and maps are scheduled for destruction at the decoder level
|
|
249
|
+
// freeStatements.push(` if (instance->${prop.name}) colyseus_${prop.type}_destroy(instance->${prop.name});`);
|
|
250
|
+
}
|
|
251
|
+
});
|
|
252
|
+
|
|
253
|
+
const freeCode = freeStatements.length > 0 ? freeStatements.join("\n") + "\n" : "";
|
|
254
|
+
|
|
255
|
+
return `static void ${snakeName}_destroy(colyseus_schema_t* schema) {
|
|
256
|
+
${typeName}* instance = (${typeName}*)schema;
|
|
257
|
+
${freeCode} free(instance);
|
|
258
|
+
}`;
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
function generateVtable(klass: Class, snakeName: string, typeName: string, allProperties: Property[]) {
|
|
262
|
+
const fieldCount = allProperties.length;
|
|
263
|
+
|
|
264
|
+
return `static const colyseus_schema_vtable_t ${snakeName}_vtable = {
|
|
265
|
+
"${klass.name}",
|
|
266
|
+
sizeof(${typeName}),
|
|
267
|
+
(colyseus_schema_t* (*)(void))${snakeName}_create,
|
|
268
|
+
${snakeName}_destroy,
|
|
269
|
+
${snakeName}_fields,
|
|
270
|
+
${fieldCount}
|
|
271
|
+
};`;
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
function getAllProperties(klass: Class, allClasses: Class[]) {
|
|
275
|
+
let properties: Property[] = [];
|
|
276
|
+
|
|
277
|
+
getInheritanceTree(klass, allClasses).reverse().forEach((k) => {
|
|
278
|
+
properties = properties.concat(k.properties);
|
|
279
|
+
});
|
|
280
|
+
|
|
281
|
+
return properties;
|
|
282
|
+
}
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { Class, Property, File, getCommentHeader, getInheritanceTree, Context } from "../types.js";
|
|
2
2
|
import { GenerateOptions } from "../api.js";
|
|
3
3
|
|
|
4
|
+
export const name = "C++";
|
|
5
|
+
|
|
4
6
|
const typeMaps: { [key: string]: string } = {
|
|
5
7
|
"string": "string",
|
|
6
8
|
"number": "varint_t",
|
|
@@ -33,6 +35,12 @@ const typeInitializer: { [key: string]: string } = {
|
|
|
33
35
|
"float64": "0",
|
|
34
36
|
}
|
|
35
37
|
|
|
38
|
+
const COMMON_INCLUDES = `#include "schema.h"
|
|
39
|
+
#include <typeinfo>
|
|
40
|
+
#include <typeindex>
|
|
41
|
+
|
|
42
|
+
using namespace colyseus::schema;`;
|
|
43
|
+
|
|
36
44
|
/**
|
|
37
45
|
* C++ Code Generator
|
|
38
46
|
*/
|
|
@@ -44,6 +52,9 @@ const capitalize = (s: string) => {
|
|
|
44
52
|
const distinct = (value: string, index: number, self: string[]) =>
|
|
45
53
|
self.indexOf(value) === index;
|
|
46
54
|
|
|
55
|
+
/**
|
|
56
|
+
* Generate individual files for each class
|
|
57
|
+
*/
|
|
47
58
|
export function generate (context: Context, options: GenerateOptions): File[] {
|
|
48
59
|
return context.classes.map(klass => ({
|
|
49
60
|
name: klass.name + ".hpp",
|
|
@@ -51,7 +62,35 @@ export function generate (context: Context, options: GenerateOptions): File[] {
|
|
|
51
62
|
}));
|
|
52
63
|
}
|
|
53
64
|
|
|
54
|
-
|
|
65
|
+
/**
|
|
66
|
+
* Generate a single bundled header file containing all classes
|
|
67
|
+
*/
|
|
68
|
+
export function renderBundle(context: Context, options: GenerateOptions): File {
|
|
69
|
+
const fileName = options.namespace ? `${options.namespace}.hpp` : "schema.hpp";
|
|
70
|
+
const guardName = `__SCHEMA_CODEGEN_${(options.namespace || "SCHEMA").toUpperCase()}_H__`;
|
|
71
|
+
|
|
72
|
+
const classBodies = context.classes.map(klass => generateClassBody(klass, context.classes, options.namespace));
|
|
73
|
+
|
|
74
|
+
const content = `${getCommentHeader()}
|
|
75
|
+
#ifndef ${guardName}
|
|
76
|
+
#define ${guardName} 1
|
|
77
|
+
|
|
78
|
+
${COMMON_INCLUDES}
|
|
79
|
+
|
|
80
|
+
${options.namespace ? `namespace ${options.namespace} {\n` : ""}
|
|
81
|
+
${classBodies.join("\n\n")}
|
|
82
|
+
${options.namespace ? "}" : ""}
|
|
83
|
+
|
|
84
|
+
#endif
|
|
85
|
+
`;
|
|
86
|
+
|
|
87
|
+
return { name: fileName, content };
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Generate just the class body (without includes/guards) for bundling
|
|
92
|
+
*/
|
|
93
|
+
function generateClassBody(klass: Class, allClasses: Class[], namespace: string): string {
|
|
55
94
|
const propertiesPerType: {[type: string]: Property[]} = {};
|
|
56
95
|
const allRefs: Property[] = [];
|
|
57
96
|
klass.properties.forEach(property => {
|
|
@@ -79,26 +118,7 @@ function generateClass(klass: Class, namespace: string, allClasses: Class[]) {
|
|
|
79
118
|
\t\treturn ${klass.extends}::createInstance(type);
|
|
80
119
|
\t}`;
|
|
81
120
|
|
|
82
|
-
return
|
|
83
|
-
#ifndef __SCHEMA_CODEGEN_${klass.name.toUpperCase()}_H__
|
|
84
|
-
#define __SCHEMA_CODEGEN_${klass.name.toUpperCase()}_H__ 1
|
|
85
|
-
|
|
86
|
-
#include "schema.h"
|
|
87
|
-
#include <typeinfo>
|
|
88
|
-
#include <typeindex>
|
|
89
|
-
|
|
90
|
-
${allRefs.
|
|
91
|
-
filter(ref => ref.childType && typeMaps[ref.childType] === undefined).
|
|
92
|
-
map(ref => ref.childType).
|
|
93
|
-
concat(getInheritanceTree(klass, allClasses, false).map(klass => klass.name)).
|
|
94
|
-
filter(distinct).
|
|
95
|
-
map(childType => `#include "${childType}.hpp"`).
|
|
96
|
-
join("\n")}
|
|
97
|
-
|
|
98
|
-
using namespace colyseus::schema;
|
|
99
|
-
|
|
100
|
-
${namespace ? `namespace ${namespace} {` : ""}
|
|
101
|
-
class ${klass.name} : public ${klass.extends} {
|
|
121
|
+
return `class ${klass.name} : public ${klass.extends} {
|
|
102
122
|
public:
|
|
103
123
|
${klass.properties.map(prop => generateProperty(prop)).join("\n")}
|
|
104
124
|
|
|
@@ -119,7 +139,39 @@ ${Object.keys(propertiesPerType).map(type =>
|
|
|
119
139
|
join("\n")}
|
|
120
140
|
|
|
121
141
|
${createInstanceMethod}
|
|
122
|
-
}
|
|
142
|
+
};`;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* Generate a complete class file with includes/guards (for individual file mode)
|
|
147
|
+
*/
|
|
148
|
+
function generateClass(klass: Class, namespace: string, allClasses: Class[]) {
|
|
149
|
+
const allRefs: Property[] = [];
|
|
150
|
+
klass.properties.forEach(property => {
|
|
151
|
+
let type = property.type;
|
|
152
|
+
// keep all refs list
|
|
153
|
+
if ((type === "ref" || type === "array" || type === "map")) {
|
|
154
|
+
allRefs.push(property);
|
|
155
|
+
}
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
const localIncludes = allRefs.
|
|
159
|
+
filter(ref => ref.childType && typeMaps[ref.childType] === undefined).
|
|
160
|
+
map(ref => ref.childType).
|
|
161
|
+
concat(getInheritanceTree(klass, allClasses, false).map(klass => klass.name)).
|
|
162
|
+
filter(distinct).
|
|
163
|
+
map(childType => `#include "${childType}.hpp"`).
|
|
164
|
+
join("\n");
|
|
165
|
+
|
|
166
|
+
return `${getCommentHeader()}
|
|
167
|
+
#ifndef __SCHEMA_CODEGEN_${klass.name.toUpperCase()}_H__
|
|
168
|
+
#define __SCHEMA_CODEGEN_${klass.name.toUpperCase()}_H__ 1
|
|
169
|
+
|
|
170
|
+
${COMMON_INCLUDES}
|
|
171
|
+
${localIncludes}
|
|
172
|
+
|
|
173
|
+
${namespace ? `namespace ${namespace} {` : ""}
|
|
174
|
+
${generateClassBody(klass, allClasses, namespace)}
|
|
123
175
|
${namespace ? "}" : ""}
|
|
124
176
|
|
|
125
177
|
#endif
|