@colyseus/schema 5.0.9 → 5.0.11
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 +87 -51
- package/build/codegen/cli.cjs +13 -5
- package/build/codegen/cli.cjs.map +1 -1
- package/build/encoder/ChangeTree.d.ts +10 -3
- package/build/encoder/StateView.d.ts +0 -6
- package/build/index.cjs +182 -102
- package/build/index.cjs.map +1 -1
- package/build/index.js +182 -102
- package/build/index.mjs +182 -102
- package/build/index.mjs.map +1 -1
- package/build/types/custom/ArraySchema.d.ts +9 -1
- package/build/types/custom/MapSchema.d.ts +18 -1
- package/package.json +9 -3
- package/src/codegen/cli.ts +4 -4
- package/src/codegen/parser.ts +10 -1
- package/src/decoder/DecodeOperation.ts +23 -7
- package/src/encoder/ChangeTree.ts +18 -12
- package/src/encoder/EncodeOperation.ts +14 -1
- package/src/encoder/Encoder.ts +3 -16
- package/src/encoder/StateView.ts +16 -35
- package/src/types/custom/ArraySchema.ts +77 -30
- package/src/types/custom/MapSchema.ts +34 -1
package/README.md
CHANGED
|
@@ -20,7 +20,33 @@
|
|
|
20
20
|
|
|
21
21
|
## Schema definition
|
|
22
22
|
|
|
23
|
-
|
|
23
|
+
Define synchronizable structures with `schema()` and `t.*` field builders:
|
|
24
|
+
|
|
25
|
+
```typescript
|
|
26
|
+
import { schema, t, type SchemaType } from '@colyseus/schema';
|
|
27
|
+
|
|
28
|
+
export const Player = schema({
|
|
29
|
+
name: t.string(),
|
|
30
|
+
x: t.number(),
|
|
31
|
+
y: t.number(),
|
|
32
|
+
}, "Player");
|
|
33
|
+
export type Player = SchemaType<typeof Player>;
|
|
34
|
+
|
|
35
|
+
export const MyState = schema({
|
|
36
|
+
fieldString: t.string(),
|
|
37
|
+
fieldNumber: t.number(),
|
|
38
|
+
player: Player,
|
|
39
|
+
arrayOfPlayers: t.array(Player),
|
|
40
|
+
mapOfPlayers: t.map(Player),
|
|
41
|
+
}, "MyState");
|
|
42
|
+
export type MyState = SchemaType<typeof MyState>;
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
`schema()` returns a real class (`instanceof` works) and runs in plain JavaScript and TypeScript alike, with no compiler configuration. The `type` aliases are TypeScript-only sugar — omit them in plain JS.
|
|
46
|
+
|
|
47
|
+
### Decorators (still supported)
|
|
48
|
+
|
|
49
|
+
The classic `@type()` decorator style remains fully supported, and both styles produce the identical wire format:
|
|
24
50
|
|
|
25
51
|
```typescript
|
|
26
52
|
import { Schema, type, ArraySchema, MapSchema } from '@colyseus/schema';
|
|
@@ -30,16 +56,30 @@ export class Player extends Schema {
|
|
|
30
56
|
@type("number") x: number;
|
|
31
57
|
@type("number") y: number;
|
|
32
58
|
}
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
We are moving away from decorators due to ecosystem compatibility issues: they depend on the legacy `experimentalDecorators` implementation and `useDefineForClassFields: false`, which conflict with modern toolchain defaults (esbuild, SWC, Vite), diverge from the TC39 decorators specification, and aren't available in plain JavaScript. See the [Decorators reference](https://docs.colyseus.io/state/schema/decorators) for setup and the full decorator documentation.
|
|
62
|
+
|
|
63
|
+
## TypeScript support
|
|
64
|
+
|
|
65
|
+
Compatible with TypeScript **5.x**, **6.x** and **7.x**.
|
|
33
66
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
67
|
+
The `@type()` decorator uses legacy decorators — enable them in your `tsconfig.json`:
|
|
68
|
+
|
|
69
|
+
```json
|
|
70
|
+
{
|
|
71
|
+
"compilerOptions": {
|
|
72
|
+
"experimentalDecorators": true,
|
|
73
|
+
"useDefineForClassFields": false
|
|
74
|
+
}
|
|
40
75
|
}
|
|
41
76
|
```
|
|
42
77
|
|
|
78
|
+
> **Note:** `schema-codegen` requires TypeScript 5.x or 6.x installed in your
|
|
79
|
+
> project — TypeScript 7's native compiler no longer ships the JS compiler API
|
|
80
|
+
> that codegen uses to parse your schema files. The runtime and your build are
|
|
81
|
+
> not affected by this.
|
|
82
|
+
|
|
43
83
|
## Supported types
|
|
44
84
|
|
|
45
85
|
### Primitive Types
|
|
@@ -62,28 +102,25 @@ export class MyState extends Schema {
|
|
|
62
102
|
|
|
63
103
|
### Declaration:
|
|
64
104
|
|
|
105
|
+
Each primitive type is declared through its `t.*` factory (`t.string()`, `t.uint8()`, …). The string names above remain valid as collection child types (`t.array("string")`).
|
|
106
|
+
|
|
65
107
|
#### Primitive types (`string`, `number`, `boolean`, etc)
|
|
66
108
|
|
|
67
109
|
```typescript
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
@type("int32")
|
|
72
|
-
name: number;
|
|
110
|
+
name: t.string(),
|
|
111
|
+
health: t.int32(),
|
|
73
112
|
```
|
|
74
113
|
|
|
75
114
|
#### Child `Schema` structures
|
|
76
115
|
|
|
77
116
|
```typescript
|
|
78
|
-
|
|
79
|
-
player: Player;
|
|
117
|
+
player: Player, // shorthand for t.ref(Player)
|
|
80
118
|
```
|
|
81
119
|
|
|
82
120
|
#### Array of `Schema` structure
|
|
83
121
|
|
|
84
122
|
```typescript
|
|
85
|
-
|
|
86
|
-
arrayOfPlayers: ArraySchema<Player>;
|
|
123
|
+
arrayOfPlayers: t.array(Player),
|
|
87
124
|
```
|
|
88
125
|
|
|
89
126
|
#### Array of a primitive type
|
|
@@ -91,18 +128,14 @@ arrayOfPlayers: ArraySchema<Player>;
|
|
|
91
128
|
You can't mix types inside arrays.
|
|
92
129
|
|
|
93
130
|
```typescript
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
@type([ "string" ])
|
|
98
|
-
arrayOfStrings: ArraySchema<string>;
|
|
131
|
+
arrayOfNumbers: t.array("number"),
|
|
132
|
+
arrayOfStrings: t.array("string"),
|
|
99
133
|
```
|
|
100
134
|
|
|
101
135
|
#### Map of `Schema` structure
|
|
102
136
|
|
|
103
137
|
```typescript
|
|
104
|
-
|
|
105
|
-
mapOfPlayers: MapSchema<Player>;
|
|
138
|
+
mapOfPlayers: t.map(Player),
|
|
106
139
|
```
|
|
107
140
|
|
|
108
141
|
#### Map of a primitive type
|
|
@@ -110,11 +143,8 @@ mapOfPlayers: MapSchema<Player>;
|
|
|
110
143
|
You can't mix primitive types inside maps.
|
|
111
144
|
|
|
112
145
|
```typescript
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
@type({ map: "string" })
|
|
117
|
-
mapOfStrings: MapSchema<string>;
|
|
146
|
+
mapOfNumbers: t.map("number"),
|
|
147
|
+
mapOfStrings: t.map("string"),
|
|
118
148
|
```
|
|
119
149
|
|
|
120
150
|
### Reflection
|
|
@@ -124,35 +154,38 @@ definition implementation in the server-side, and just send the encoded
|
|
|
124
154
|
reflection to the client-side, for example:
|
|
125
155
|
|
|
126
156
|
```typescript
|
|
127
|
-
import {
|
|
157
|
+
import { schema, t, Encoder, Reflection } from "@colyseus/schema";
|
|
128
158
|
|
|
129
|
-
|
|
130
|
-
|
|
159
|
+
const MyState = schema({
|
|
160
|
+
currentTurn: t.string(),
|
|
131
161
|
// ... more definitions
|
|
132
|
-
}
|
|
162
|
+
}, "MyState");
|
|
133
163
|
|
|
134
|
-
//
|
|
135
|
-
const
|
|
164
|
+
// server-side: encode the schema definition itself
|
|
165
|
+
const encoder = new Encoder(new MyState());
|
|
166
|
+
const encodedStateSchema = Reflection.encode(encoder);
|
|
167
|
+
// ... send `encodedStateSchema` across the network
|
|
136
168
|
|
|
137
|
-
//
|
|
138
|
-
const
|
|
169
|
+
// client-side: rebuild the state without having its definition
|
|
170
|
+
const decoder = Reflection.decode(encodedStateSchema);
|
|
171
|
+
const myState = decoder.state;
|
|
139
172
|
```
|
|
140
173
|
|
|
141
|
-
### `StateView` /
|
|
174
|
+
### `StateView` / `.view()`
|
|
142
175
|
|
|
143
|
-
You can use
|
|
176
|
+
You can use the `.view()` field modifier to filter properties that should be sent only to `StateView`'s that have access to it.
|
|
144
177
|
|
|
145
178
|
```typescript
|
|
146
|
-
import {
|
|
179
|
+
import { schema, t } from "@colyseus/schema";
|
|
147
180
|
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
}
|
|
181
|
+
const Player = schema({
|
|
182
|
+
secret: t.string().view(),
|
|
183
|
+
notSecret: t.string(),
|
|
184
|
+
}, "Player");
|
|
152
185
|
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
}
|
|
186
|
+
const MyState = schema({
|
|
187
|
+
players: t.map(Player),
|
|
188
|
+
}, "MyState");
|
|
156
189
|
```
|
|
157
190
|
|
|
158
191
|
Using the `StateView`
|
|
@@ -168,7 +201,7 @@ There are 3 major features of the `Encoder` class:
|
|
|
168
201
|
|
|
169
202
|
- Encoding the full state
|
|
170
203
|
- Encoding the state changes
|
|
171
|
-
- Encoding state with filters (properties
|
|
204
|
+
- Encoding state with filters (properties tagged with `.view()`)
|
|
172
205
|
|
|
173
206
|
```typescript
|
|
174
207
|
import { Encoder } from "@colyseus/schema";
|
|
@@ -193,7 +226,7 @@ const changesBuffer = encoder.encode();
|
|
|
193
226
|
|
|
194
227
|
### Encoding with views
|
|
195
228
|
|
|
196
|
-
When using
|
|
229
|
+
When using `.view()` and `StateView`'s, a single "full encode" must be used for multiple views. Each view also must add its own changes.
|
|
197
230
|
|
|
198
231
|
```typescript
|
|
199
232
|
// shared buffer iterator
|
|
@@ -250,7 +283,7 @@ decoder.decode(encodedBytes);
|
|
|
250
283
|
|
|
251
284
|
Backwards/forwards compatibility is possible by declaring new fields at the
|
|
252
285
|
end of existing structures, and earlier declarations to not be removed, but
|
|
253
|
-
be marked
|
|
286
|
+
be marked `.deprecated()` when needed.
|
|
254
287
|
|
|
255
288
|
This is particularly useful for native-compiled targets, such as C#, C++,
|
|
256
289
|
Haxe, etc - where the client-side can potentially not have the most
|
|
@@ -260,7 +293,7 @@ up-to-date version of the schema definitions.
|
|
|
260
293
|
## Limitations and best practices
|
|
261
294
|
|
|
262
295
|
- Each `Schema` structure can hold up to `64` fields. If you need more fields, use nested structures.
|
|
263
|
-
- Fields tagged with
|
|
296
|
+
- Fields tagged with `.view()`, `.unreliable()`, or `.static()` at field indexes `≥ 32` use a slower per-mutation classification path (linear scan over the tagged-field list instead of a single bitwise op). For schemas with more than 32 fields, declare frequently-mutated tagged fields earlier so they fall in the bitmask fast path.
|
|
264
297
|
- Schemas with `≤ 8` fields store per-field operation bytes inline in two numbers (no allocation per instance). Schemas with `> 8` fields allocate a small `Uint8Array` per instance for op storage. The difference is only material when allocating thousands of instances per tick — prefer narrower nested structures in that regime.
|
|
265
298
|
- `NaN` or `null` numbers are encoded as `0`
|
|
266
299
|
- `null` strings are encoded as `""`
|
|
@@ -276,7 +309,10 @@ up-to-date version of the schema definitions.
|
|
|
276
309
|
> If you're using JavaScript or LUA, there's no need to bother about this.
|
|
277
310
|
> Interpreted programming languages are able to re-build the Schema locally through the use of `Reflection`.
|
|
278
311
|
|
|
279
|
-
You can generate the client-side schema files based on
|
|
312
|
+
You can generate the client-side schema files based on your server-side schema definitions automatically — both `schema()` and decorator styles are supported.
|
|
313
|
+
|
|
314
|
+
> `schema-codegen` requires TypeScript 5.x or 6.x installed in your project
|
|
315
|
+
> (TypeScript 7+ no longer ships the JS compiler API it uses for parsing).
|
|
280
316
|
|
|
281
317
|
```
|
|
282
318
|
# C#/Unity
|
package/build/codegen/cli.cjs
CHANGED
|
@@ -551,6 +551,11 @@ function inspectNode(node, context, decoratorName) {
|
|
|
551
551
|
}
|
|
552
552
|
let parsedFiles;
|
|
553
553
|
function parseFiles(fileNames, decoratorName = "type", context = new Context()) {
|
|
554
|
+
if (typeof ts__namespace.createSourceFile !== "function") {
|
|
555
|
+
// typescript@7+ (native) no longer ships the JS compiler API
|
|
556
|
+
throw new Error(`schema-codegen requires the TypeScript compiler API, which the installed "typescript@${ts__namespace.version}" package does not provide.\n` +
|
|
557
|
+
`TypeScript 7+ no longer ships the JS compiler API — install typescript 5.x or 6.x (e.g. \`npm install --save-dev typescript@6\`) to use schema-codegen.`);
|
|
558
|
+
}
|
|
554
559
|
/**
|
|
555
560
|
* Re-set globalContext for each test case
|
|
556
561
|
*/
|
|
@@ -587,7 +592,10 @@ function parseFiles(fileNames, decoratorName = "type", context = new Context())
|
|
|
587
592
|
break;
|
|
588
593
|
}
|
|
589
594
|
catch (e) {
|
|
590
|
-
//
|
|
595
|
+
// only swallow fs errors (ENOENT/EISDIR) while probing alternatives
|
|
596
|
+
if (!e?.code) {
|
|
597
|
+
throw e;
|
|
598
|
+
}
|
|
591
599
|
}
|
|
592
600
|
}
|
|
593
601
|
if (sourceFile) {
|
|
@@ -2365,7 +2373,7 @@ function recursiveFiles(dir) {
|
|
|
2365
2373
|
return collect;
|
|
2366
2374
|
}
|
|
2367
2375
|
|
|
2368
|
-
function displayHelp() {
|
|
2376
|
+
function displayHelp(exitCode = 0) {
|
|
2369
2377
|
console.log(`\nschema-codegen [path/to/Schema.ts]
|
|
2370
2378
|
|
|
2371
2379
|
Usage (C#/Unity)
|
|
@@ -2384,7 +2392,7 @@ ${Object.
|
|
|
2384
2392
|
Optional:
|
|
2385
2393
|
--namespace: generate namespace on output code
|
|
2386
2394
|
--decorator: custom name for @type decorator to scan for`);
|
|
2387
|
-
process.exit();
|
|
2395
|
+
process.exit(exitCode);
|
|
2388
2396
|
}
|
|
2389
2397
|
const args = argv(process.argv.slice(2));
|
|
2390
2398
|
if (args.help) {
|
|
@@ -2398,7 +2406,7 @@ for (let target in generators) {
|
|
|
2398
2406
|
}
|
|
2399
2407
|
if (!args.output) {
|
|
2400
2408
|
console.error("You must provide a valid --output directory.");
|
|
2401
|
-
displayHelp();
|
|
2409
|
+
displayHelp(1);
|
|
2402
2410
|
}
|
|
2403
2411
|
try {
|
|
2404
2412
|
args.files = args._;
|
|
@@ -2413,6 +2421,6 @@ try {
|
|
|
2413
2421
|
catch (e) {
|
|
2414
2422
|
console.error(e.message);
|
|
2415
2423
|
console.error(e.stack);
|
|
2416
|
-
displayHelp();
|
|
2424
|
+
displayHelp(1);
|
|
2417
2425
|
}
|
|
2418
2426
|
//# sourceMappingURL=cli.cjs.map
|