@colyseus/schema 3.0.0-alpha.8 → 3.0.0

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.
Files changed (150) hide show
  1. package/README.md +148 -62
  2. package/bin/schema-debug +94 -0
  3. package/build/cjs/index.js +2227 -1519
  4. package/build/cjs/index.js.map +1 -1
  5. package/build/esm/index.mjs +2228 -1522
  6. package/build/esm/index.mjs.map +1 -1
  7. package/build/umd/index.js +2230 -1522
  8. package/lib/Metadata.d.ts +21 -9
  9. package/lib/Metadata.js +169 -32
  10. package/lib/Metadata.js.map +1 -1
  11. package/lib/Reflection.d.ts +19 -4
  12. package/lib/Reflection.js +66 -31
  13. package/lib/Reflection.js.map +1 -1
  14. package/lib/Schema.d.ts +12 -5
  15. package/lib/Schema.js +57 -56
  16. package/lib/Schema.js.map +1 -1
  17. package/lib/annotations.d.ts +31 -34
  18. package/lib/annotations.js +110 -160
  19. package/lib/annotations.js.map +1 -1
  20. package/lib/bench_encode.d.ts +1 -0
  21. package/lib/bench_encode.js +130 -0
  22. package/lib/bench_encode.js.map +1 -0
  23. package/lib/codegen/api.js +1 -2
  24. package/lib/codegen/api.js.map +1 -1
  25. package/lib/codegen/languages/cpp.js +1 -2
  26. package/lib/codegen/languages/cpp.js.map +1 -1
  27. package/lib/codegen/languages/csharp.js +9 -46
  28. package/lib/codegen/languages/csharp.js.map +1 -1
  29. package/lib/codegen/languages/haxe.js +4 -2
  30. package/lib/codegen/languages/haxe.js.map +1 -1
  31. package/lib/codegen/languages/java.js +1 -2
  32. package/lib/codegen/languages/java.js.map +1 -1
  33. package/lib/codegen/languages/js.js +1 -2
  34. package/lib/codegen/languages/js.js.map +1 -1
  35. package/lib/codegen/languages/lua.js +23 -25
  36. package/lib/codegen/languages/lua.js.map +1 -1
  37. package/lib/codegen/languages/ts.js +1 -2
  38. package/lib/codegen/languages/ts.js.map +1 -1
  39. package/lib/codegen/parser.js +85 -3
  40. package/lib/codegen/parser.js.map +1 -1
  41. package/lib/codegen/types.js +6 -3
  42. package/lib/codegen/types.js.map +1 -1
  43. package/lib/debug.d.ts +1 -0
  44. package/lib/debug.js +51 -0
  45. package/lib/debug.js.map +1 -0
  46. package/lib/decoder/DecodeOperation.d.ts +3 -4
  47. package/lib/decoder/DecodeOperation.js +35 -17
  48. package/lib/decoder/DecodeOperation.js.map +1 -1
  49. package/lib/decoder/Decoder.d.ts +5 -6
  50. package/lib/decoder/Decoder.js +10 -10
  51. package/lib/decoder/Decoder.js.map +1 -1
  52. package/lib/decoder/ReferenceTracker.js +4 -2
  53. package/lib/decoder/ReferenceTracker.js.map +1 -1
  54. package/lib/decoder/strategy/RawChanges.js +1 -2
  55. package/lib/decoder/strategy/RawChanges.js.map +1 -1
  56. package/lib/decoder/strategy/StateCallbacks.d.ts +44 -11
  57. package/lib/decoder/strategy/StateCallbacks.js +74 -64
  58. package/lib/decoder/strategy/StateCallbacks.js.map +1 -1
  59. package/lib/encoder/ChangeTree.d.ts +28 -20
  60. package/lib/encoder/ChangeTree.js +242 -188
  61. package/lib/encoder/ChangeTree.js.map +1 -1
  62. package/lib/encoder/EncodeOperation.d.ts +3 -6
  63. package/lib/encoder/EncodeOperation.js +51 -65
  64. package/lib/encoder/EncodeOperation.js.map +1 -1
  65. package/lib/encoder/Encoder.d.ts +8 -7
  66. package/lib/encoder/Encoder.js +133 -85
  67. package/lib/encoder/Encoder.js.map +1 -1
  68. package/lib/encoder/Root.d.ts +22 -0
  69. package/lib/encoder/Root.js +81 -0
  70. package/lib/encoder/Root.js.map +1 -0
  71. package/lib/encoder/StateView.d.ts +7 -7
  72. package/lib/encoder/StateView.js +72 -74
  73. package/lib/encoder/StateView.js.map +1 -1
  74. package/lib/encoding/assert.d.ts +7 -6
  75. package/lib/encoding/assert.js +13 -5
  76. package/lib/encoding/assert.js.map +1 -1
  77. package/lib/encoding/decode.d.ts +36 -19
  78. package/lib/encoding/decode.js +54 -84
  79. package/lib/encoding/decode.js.map +1 -1
  80. package/lib/encoding/encode.d.ts +36 -18
  81. package/lib/encoding/encode.js +61 -48
  82. package/lib/encoding/encode.js.map +1 -1
  83. package/lib/encoding/spec.d.ts +4 -5
  84. package/lib/encoding/spec.js +1 -2
  85. package/lib/encoding/spec.js.map +1 -1
  86. package/lib/index.d.ts +10 -9
  87. package/lib/index.js +24 -17
  88. package/lib/index.js.map +1 -1
  89. package/lib/types/HelperTypes.d.ts +34 -2
  90. package/lib/types/HelperTypes.js.map +1 -1
  91. package/lib/types/TypeContext.d.ts +29 -0
  92. package/lib/types/TypeContext.js +151 -0
  93. package/lib/types/TypeContext.js.map +1 -0
  94. package/lib/types/custom/ArraySchema.d.ts +2 -2
  95. package/lib/types/custom/ArraySchema.js +33 -22
  96. package/lib/types/custom/ArraySchema.js.map +1 -1
  97. package/lib/types/custom/CollectionSchema.d.ts +2 -2
  98. package/lib/types/custom/CollectionSchema.js +1 -0
  99. package/lib/types/custom/CollectionSchema.js.map +1 -1
  100. package/lib/types/custom/MapSchema.d.ts +18 -16
  101. package/lib/types/custom/MapSchema.js +12 -4
  102. package/lib/types/custom/MapSchema.js.map +1 -1
  103. package/lib/types/custom/SetSchema.d.ts +2 -2
  104. package/lib/types/custom/SetSchema.js +1 -0
  105. package/lib/types/custom/SetSchema.js.map +1 -1
  106. package/lib/types/registry.d.ts +8 -1
  107. package/lib/types/registry.js +23 -6
  108. package/lib/types/registry.js.map +1 -1
  109. package/lib/types/symbols.d.ts +8 -5
  110. package/lib/types/symbols.js +9 -6
  111. package/lib/types/symbols.js.map +1 -1
  112. package/lib/types/utils.js +1 -2
  113. package/lib/types/utils.js.map +1 -1
  114. package/lib/utils.js +9 -7
  115. package/lib/utils.js.map +1 -1
  116. package/package.json +19 -18
  117. package/src/Metadata.ts +190 -42
  118. package/src/Reflection.ts +76 -38
  119. package/src/Schema.ts +72 -70
  120. package/src/annotations.ts +156 -202
  121. package/src/bench_encode.ts +108 -0
  122. package/src/codegen/languages/csharp.ts +8 -47
  123. package/src/codegen/languages/haxe.ts +4 -0
  124. package/src/codegen/languages/lua.ts +19 -27
  125. package/src/codegen/parser.ts +107 -0
  126. package/src/codegen/types.ts +1 -0
  127. package/src/debug.ts +55 -0
  128. package/src/decoder/DecodeOperation.ts +43 -15
  129. package/src/decoder/Decoder.ts +12 -10
  130. package/src/decoder/ReferenceTracker.ts +5 -3
  131. package/src/decoder/strategy/StateCallbacks.ts +152 -81
  132. package/src/encoder/ChangeTree.ts +282 -209
  133. package/src/encoder/EncodeOperation.ts +78 -78
  134. package/src/encoder/Encoder.ts +157 -93
  135. package/src/encoder/Root.ts +93 -0
  136. package/src/encoder/StateView.ts +80 -88
  137. package/src/encoding/assert.ts +17 -8
  138. package/src/encoding/decode.ts +73 -93
  139. package/src/encoding/encode.ts +76 -45
  140. package/src/encoding/spec.ts +3 -5
  141. package/src/index.ts +12 -20
  142. package/src/types/HelperTypes.ts +54 -2
  143. package/src/types/TypeContext.ts +175 -0
  144. package/src/types/custom/ArraySchema.ts +49 -19
  145. package/src/types/custom/CollectionSchema.ts +1 -0
  146. package/src/types/custom/MapSchema.ts +30 -17
  147. package/src/types/custom/SetSchema.ts +1 -0
  148. package/src/types/registry.ts +22 -3
  149. package/src/types/symbols.ts +10 -7
  150. package/src/utils.ts +7 -3
package/README.md CHANGED
@@ -1,55 +1,47 @@
1
1
  <div align="center">
2
- <img src="logo.png?raw=true" />
2
+ <img src="logo.png?raw=true" width="50%" />
3
3
  <br>
4
- <br>
5
-
6
4
  <p>
7
5
  An incremental binary state serializer with delta encoding for games.<br>
8
- Although it was born to be used on <a href="https://github.com/colyseus/colyseus">Colyseus</a>, this library can be used as standalone.
6
+ Made for <a href="https://github.com/colyseus/colyseus">Colyseus</a>, yet can be used standalone.
9
7
  </p>
10
8
  </div>
11
9
 
12
- ## Defining Schema
10
+ # Features
11
+
12
+ - Flexible Schema Definition
13
+ - Optimized Data Encoding
14
+ - Automatic State Synchronization
15
+ - Client-side Change Detection
16
+ - Per-client portions of the state
17
+ - Type Safety
18
+ - *...decoders available for multiple languages (C#, Lua, Haxe)*
13
19
 
14
- As Colyseus is written in TypeScript, the schema is defined as type annotations inside the state class. Additional server logic may be added to that class, but client-side generated (not implemented) files will consider only the schema itself.
20
+ ## Schema definition
21
+
22
+ `@colyseus/schema` uses type annotations to define types of synchronized properties.
15
23
 
16
24
  ```typescript
17
25
  import { Schema, type, ArraySchema, MapSchema } from '@colyseus/schema';
18
26
 
19
27
  export class Player extends Schema {
20
- @type("string")
21
- name: string;
22
-
23
- @type("number")
24
- x: number;
25
-
26
- @type("number")
27
- y: number;
28
+ @type("string") name: string;
29
+ @type("number") x: number;
30
+ @type("number") y: number;
28
31
  }
29
32
 
30
- export class State extends Schema {
31
- @type('string')
32
- fieldString: string;
33
-
34
- @type('number') // varint
35
- fieldNumber: number;
36
-
37
- @type(Player)
38
- player: Player;
39
-
40
- @type([ Player ])
41
- arrayOfPlayers: ArraySchema<Player>;
42
-
43
- @type({ map: Player })
44
- mapOfPlayers: MapSchema<Player>;
33
+ export class MyState extends Schema {
34
+ @type('string') fieldString: string;
35
+ @type('number') fieldNumber: number;
36
+ @type(Player) player: Player;
37
+ @type([ Player ]) arrayOfPlayers: ArraySchema<Player>;
38
+ @type({ map: Player }) mapOfPlayers: MapSchema<Player>;
45
39
  }
46
40
  ```
47
41
 
48
- See [example](test/Schema.ts).
49
-
50
42
  ## Supported types
51
43
 
52
- ## Primitive Types
44
+ ### Primitive Types
53
45
 
54
46
  | Type | Description | Limitation |
55
47
  |------|-------------|------------|
@@ -79,14 +71,14 @@ name: string;
79
71
  name: number;
80
72
  ```
81
73
 
82
- #### Custom `Schema` type
74
+ #### Child `Schema` structures
83
75
 
84
76
  ```typescript
85
77
  @type(Player)
86
78
  player: Player;
87
79
  ```
88
80
 
89
- #### Array of custom `Schema` type
81
+ #### Array of `Schema` structure
90
82
 
91
83
  ```typescript
92
84
  @type([ Player ])
@@ -105,7 +97,7 @@ arrayOfNumbers: ArraySchema<number>;
105
97
  arrayOfStrings: ArraySchema<string>;
106
98
  ```
107
99
 
108
- #### Map of custom `Schema` type
100
+ #### Map of `Schema` structure
109
101
 
110
102
  ```typescript
111
103
  @type({ map: Player })
@@ -114,7 +106,7 @@ mapOfPlayers: MapSchema<Player>;
114
106
 
115
107
  #### Map of a primitive type
116
108
 
117
- You can't mix types inside maps.
109
+ You can't mix primitive types inside maps.
118
110
 
119
111
  ```typescript
120
112
  @type({ map: "number" })
@@ -124,16 +116,6 @@ mapOfNumbers: MapSchema<number>;
124
116
  mapOfStrings: MapSchema<string>;
125
117
  ```
126
118
 
127
- ### Backwards/forwards compability
128
-
129
- Backwards/fowards compatibility is possible by declaring new fields at the
130
- end of existing structures, and earlier declarations to not be removed, but
131
- be marked `@deprecated()` when needed.
132
-
133
- This is particularly useful for native-compiled targets, such as C#, C++,
134
- Haxe, etc - where the client-side can potentially not have the most
135
- up-to-date version of the schema definitions.
136
-
137
119
  ### Reflection
138
120
 
139
121
  The Schema definitions can encode itself through `Reflection`. You can have the
@@ -144,10 +126,8 @@ reflection to the client-side, for example:
144
126
  import { Schema, type, Reflection } from "@colyseus/schema";
145
127
 
146
128
  class MyState extends Schema {
147
- @type("string")
148
- currentTurn: string;
149
-
150
- // more definitions relating to more Schema types.
129
+ @type("string") currentTurn: string;
130
+ // ... more definitions
151
131
  }
152
132
 
153
133
  // send `encodedStateSchema` across the network
@@ -157,22 +137,125 @@ const encodedStateSchema = Reflection.encode(new MyState());
157
137
  const myState = Reflection.decode(encodedStateSchema);
158
138
  ```
159
139
 
160
- ### Data filters
140
+ ### `StateView` / `@view()`
161
141
 
162
- On the example below, considering we're making a card game, we are filtering the cards to be available only for the owner of the cards, or if the card has been flagged as `"revealed"`.
142
+ You can use `@view()` to filter properties that should be sent only to `StateView`'s that have access to it.
163
143
 
164
144
  ```typescript
165
- import { Schema, type, filter } from "@colyseus/schema";
145
+ import { Schema, type, view } from "@colyseus/schema";
166
146
 
167
- export class State extends Schema {
168
- @filterChildren(function(client: any, key: string, value: Card, root: State) {
169
- return (value.ownerId === client.sessionId) || value.revealed;
170
- })
171
- @type({ map: Card })
172
- cards = new MapSchema<Card>();
147
+ class Player extends Schema {
148
+ @view() @type("string") secret: string;
149
+ @type("string") notSecret: string;
150
+ }
151
+
152
+ class MyState extends Schema {
153
+ @type({ map: Player }) players = new MapSchema<Player>();
173
154
  }
174
155
  ```
175
156
 
157
+ Using the `StateView`
158
+
159
+ ```typescript
160
+ const view = new StateView();
161
+ view.add(player);
162
+ ```
163
+
164
+ ## Encoder
165
+
166
+ There are 3 major features of the `Encoder` class:
167
+
168
+ - Encoding the full state
169
+ - Encoding the state changes
170
+ - Encoding state with filters (properties using `@view()` tag)
171
+
172
+ ```typescript
173
+ import { Encoder } from "@colyseus/schema";
174
+
175
+ const state = new MyState();
176
+ const encoder = new Encoder(state);
177
+ ```
178
+
179
+ New clients must receive the full state on their first connection:
180
+
181
+ ```typescript
182
+ const fullEncode = encoder.encodeAll();
183
+ // ... send "fullEncode" to client and decode it
184
+ ```
185
+
186
+ Further state changes must be sent in order:
187
+
188
+ ```typescript
189
+ const changesBuffer = encoder.encode();
190
+ // ... send "changesBuffer" to client and decode it
191
+ ```
192
+
193
+ ### Encoding with views
194
+
195
+ When using `@view()` and `StateView`'s, a single "full encode" must be used for multiple views. Each view also must add its own changes.
196
+
197
+ ```typescript
198
+ // shared buffer iterator
199
+ const it = { offset: 0 };
200
+
201
+ // shared full encode
202
+ encoder.encodeAll(it);
203
+ const sharedOffset = it.offset;
204
+
205
+ // view 1
206
+ const fullEncode1 = encoder.encodeAllView(view1, sharedOffset, it);
207
+ // ... send "fullEncode1" to client1 and decode it
208
+
209
+ // view 2
210
+ const fullEncode2 = encoder.encodeAllView(view2, sharedOffset, it);
211
+ // ... send "fullEncode" to client2 and decode it
212
+ ```
213
+
214
+ Encoding changes per views:
215
+
216
+ ```typescript
217
+ // shared buffer iterator
218
+ const it = { offset: 0 };
219
+
220
+ // shared changes encode
221
+ encoder.encode(it);
222
+ const sharedOffset = it.offset;
223
+
224
+ // view 1
225
+ const view1Encoded = this.encoder.encodeView(view1, sharedOffset, it);
226
+ // ... send "view1Encoded" to client1 and decode it
227
+
228
+ // view 2
229
+ const view2Encoded = this.encoder.encodeView(view2, sharedOffset, it);
230
+ // ... send "view2Encoded" to client2 and decode it
231
+
232
+ // discard all changes after encoding is done.
233
+ encoder.discardChanges();
234
+ ```
235
+
236
+ ## Decoder
237
+
238
+ The `Decoder` class is used to decode the binary data received from the server.
239
+
240
+ ```typescript
241
+ import { Decoder } from "@colyseus/schema";
242
+
243
+ const state = new MyState();
244
+ const decoder = new Decoder(state);
245
+ decoder.decode(encodedBytes);
246
+ ```
247
+
248
+ ### Backwards/forwards compability
249
+
250
+ Backwards/fowards compatibility is possible by declaring new fields at the
251
+ end of existing structures, and earlier declarations to not be removed, but
252
+ be marked `@deprecated()` when needed.
253
+
254
+ This is particularly useful for native-compiled targets, such as C#, C++,
255
+ Haxe, etc - where the client-side can potentially not have the most
256
+ up-to-date version of the schema definitions.
257
+
258
+
176
259
  ## Limitations and best practices
177
260
 
178
261
  - Each `Schema` structure can hold up to `64` fields. If you need more fields, use nested structures.
@@ -184,7 +267,6 @@ export class State extends Schema {
184
267
  - `@colyseus/schema` encodes only field values in the specified order.
185
268
  - Both encoder (server) and decoder (client) must have same schema definition.
186
269
  - The order of the fields must be the same.
187
- - Avoid manipulating indexes of an array. This result in at least `2` extra bytes for each index change. **Example:** If you have an array of 20 items, and remove the first item (through `shift()`) this means `38` extra bytes to be serialized.
188
270
 
189
271
  ## Generating client-side schema files (for strictly typed languages)
190
272
 
@@ -213,10 +295,14 @@ schema-codegen ./schemas/State.ts --output ./haxe-project/ --haxe
213
295
  | Updating x/y of 50 entities after initial state | 342 | 684 |
214
296
  | Updating x/y of 100 entities after initial state | 668 | 1529 |
215
297
 
298
+ ## Decoder implementation in other languages
216
299
 
217
- ## Decoder implementations
300
+ Each Colyseus SDK has its own decoder implementation of the `@colyseus/schema` protocol:
218
301
 
219
- Decoders for each target language are located at [`/decoders/`](decoders). They have no third party dependencies.
302
+ - [C#](https://github.com/colyseus/colyseus-unity-sdk)
303
+ - [Haxe](https://github.com/colyseus/colyseus-haxe)
304
+ - [Lua](https://github.com/colyseus/colyseus-defold)
305
+ - [C++](https://github.com/colyseus/colyseus-cocos2d-x) _(Not up-to-date)_
220
306
 
221
307
  ## Why
222
308
 
@@ -0,0 +1,94 @@
1
+ #!/usr/bin/env node
2
+
3
+ const fs = require('fs');
4
+ const schema = require('@colyseus/schema');
5
+
6
+ const Reflection = schema.Reflection;
7
+ const Decoder = schema.Decoder;
8
+
9
+ if (!fs.existsSync(process.argv[2])) {
10
+ console.error("Error: File not found:", process.argv[2]);
11
+ console.log("");
12
+ console.log("Usage: schema-debug <schema-debug.txt>");
13
+ console.log("");
14
+ console.log(" From your server-side, you must use the SchemaSerializerDebug as serializer, in order to generate the schema-debug.txt file.");
15
+ console.log("");
16
+ console.log(" Example:");
17
+ console.log("");
18
+ console.log(" import { SchemaSerializerDebug } from '@colyseus/core';");
19
+ console.log(" // ...");
20
+ console.log(" onCreate() {");
21
+ console.log(" const state = new MyRoomState();");
22
+ console.log(" this.setState(state);");
23
+ console.log(" // Override serializer");
24
+ console.log(" this.setSerializer(new SchemaSerializerDebug());");
25
+ console.log(" this['_serializer'].reset(state)");
26
+ console.log(" // ...");
27
+ console.log("");
28
+ process.exit(1);
29
+ }
30
+
31
+ const contents = fs.readFileSync(process.argv[2], { encoding: "utf8" }).toString();
32
+
33
+ let isCommentBlock = false;
34
+ let lastComment = "";
35
+
36
+ /**
37
+ * @type {Decoder}
38
+ */
39
+ let decoder;
40
+
41
+ /**
42
+ *
43
+ * @param line string
44
+ * @returns {Buffer}
45
+ */
46
+ function getBuffer(line) {
47
+ const start = line.lastIndexOf(":");
48
+ const buffer = Buffer.from(new Uint8Array(line.substring(start + 1).split(",").map(n => Number(n))));
49
+ console.log(`(${buffer.byteLength}) ${Array.from(buffer).join(",")}`)
50
+ // console.log("");
51
+ // console.log("");
52
+ // console.log("> ", line);
53
+ // console.log("> substring:", line.substring(start + 1))
54
+ return buffer;
55
+ }
56
+
57
+ /**
58
+ * @param buffer {Buffer}
59
+ */
60
+ function decode(buffer) {
61
+ try {
62
+ decoder.decode(buffer);
63
+ } catch (e) {
64
+ console.error(e);
65
+ console.log("NOT OK. Last log:\n\n")
66
+ console.log(lastComment);
67
+ }
68
+ }
69
+
70
+ contents.split("\n").forEach((line) => {
71
+ if (line.startsWith("#")) {
72
+ // reset last comment.
73
+ if (isCommentBlock === false) { lastComment = ""; }
74
+
75
+ isCommentBlock = true;
76
+ lastComment += line.substring(line.indexOf(":") + 1) + "\n";
77
+ return;
78
+ }
79
+
80
+ isCommentBlock = false;
81
+
82
+ if (line.startsWith("handshake:") && !decoder) {
83
+ const state = Reflection.decode(getBuffer(line));
84
+ decoder = new Decoder(state);
85
+
86
+ } else if (line.startsWith("state:")) {
87
+ decode(getBuffer(line));
88
+
89
+ } else if (line.startsWith("patch:")) {
90
+ decode(getBuffer(line));
91
+ }
92
+ });
93
+
94
+ console.log("OK:", decoder.state.toJSON());