@colyseus/schema 3.0.0-alpha.4 → 3.0.0-alpha.40

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 (141) hide show
  1. package/README.md +131 -61
  2. package/bin/schema-debug +94 -0
  3. package/build/cjs/index.js +1521 -809
  4. package/build/cjs/index.js.map +1 -1
  5. package/build/esm/index.mjs +1519 -808
  6. package/build/esm/index.mjs.map +1 -1
  7. package/build/umd/index.js +1528 -816
  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 -32
  13. package/lib/Reflection.js.map +1 -1
  14. package/lib/Schema.d.ts +4 -4
  15. package/lib/Schema.js +44 -50
  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 +1 -2
  28. package/lib/codegen/languages/csharp.js.map +1 -1
  29. package/lib/codegen/languages/haxe.js +1 -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 +1 -2
  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 +0 -1
  47. package/lib/decoder/DecodeOperation.js +30 -12
  48. package/lib/decoder/DecodeOperation.js.map +1 -1
  49. package/lib/decoder/Decoder.d.ts +6 -7
  50. package/lib/decoder/Decoder.js +9 -9
  51. package/lib/decoder/Decoder.js.map +1 -1
  52. package/lib/decoder/ReferenceTracker.js +3 -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 +75 -65
  58. package/lib/decoder/strategy/StateCallbacks.js.map +1 -1
  59. package/lib/encoder/ChangeTree.d.ts +27 -21
  60. package/lib/encoder/ChangeTree.js +246 -186
  61. package/lib/encoder/ChangeTree.js.map +1 -1
  62. package/lib/encoder/EncodeOperation.d.ts +3 -6
  63. package/lib/encoder/EncodeOperation.js +47 -61
  64. package/lib/encoder/EncodeOperation.js.map +1 -1
  65. package/lib/encoder/Encoder.d.ts +9 -8
  66. package/lib/encoder/Encoder.js +165 -88
  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 +70 -74
  73. package/lib/encoder/StateView.js.map +1 -1
  74. package/lib/encoding/assert.d.ts +2 -1
  75. package/lib/encoding/assert.js +5 -5
  76. package/lib/encoding/assert.js.map +1 -1
  77. package/lib/encoding/decode.js +20 -21
  78. package/lib/encoding/decode.js.map +1 -1
  79. package/lib/encoding/encode.d.ts +2 -2
  80. package/lib/encoding/encode.js +52 -48
  81. package/lib/encoding/encode.js.map +1 -1
  82. package/lib/encoding/spec.d.ts +2 -1
  83. package/lib/encoding/spec.js +1 -0
  84. package/lib/encoding/spec.js.map +1 -1
  85. package/lib/index.d.ts +6 -3
  86. package/lib/index.js +19 -13
  87. package/lib/index.js.map +1 -1
  88. package/lib/types/HelperTypes.d.ts +34 -2
  89. package/lib/types/HelperTypes.js.map +1 -1
  90. package/lib/types/TypeContext.d.ts +23 -0
  91. package/lib/types/TypeContext.js +111 -0
  92. package/lib/types/TypeContext.js.map +1 -0
  93. package/lib/types/custom/ArraySchema.d.ts +2 -2
  94. package/lib/types/custom/ArraySchema.js +33 -22
  95. package/lib/types/custom/ArraySchema.js.map +1 -1
  96. package/lib/types/custom/CollectionSchema.js +1 -0
  97. package/lib/types/custom/CollectionSchema.js.map +1 -1
  98. package/lib/types/custom/MapSchema.d.ts +3 -1
  99. package/lib/types/custom/MapSchema.js +12 -4
  100. package/lib/types/custom/MapSchema.js.map +1 -1
  101. package/lib/types/custom/SetSchema.js +1 -0
  102. package/lib/types/custom/SetSchema.js.map +1 -1
  103. package/lib/types/registry.js +3 -4
  104. package/lib/types/registry.js.map +1 -1
  105. package/lib/types/symbols.d.ts +8 -5
  106. package/lib/types/symbols.js +9 -6
  107. package/lib/types/symbols.js.map +1 -1
  108. package/lib/types/utils.js +1 -2
  109. package/lib/types/utils.js.map +1 -1
  110. package/lib/utils.js +9 -7
  111. package/lib/utils.js.map +1 -1
  112. package/package.json +7 -6
  113. package/src/Metadata.ts +190 -42
  114. package/src/Reflection.ts +77 -39
  115. package/src/Schema.ts +59 -64
  116. package/src/annotations.ts +155 -203
  117. package/src/bench_encode.ts +108 -0
  118. package/src/codegen/parser.ts +107 -0
  119. package/src/codegen/types.ts +1 -0
  120. package/src/debug.ts +55 -0
  121. package/src/decoder/DecodeOperation.ts +40 -12
  122. package/src/decoder/Decoder.ts +14 -12
  123. package/src/decoder/ReferenceTracker.ts +3 -2
  124. package/src/decoder/strategy/StateCallbacks.ts +153 -82
  125. package/src/encoder/ChangeTree.ts +286 -202
  126. package/src/encoder/EncodeOperation.ts +77 -77
  127. package/src/encoder/Encoder.ts +201 -96
  128. package/src/encoder/Root.ts +93 -0
  129. package/src/encoder/StateView.ts +76 -88
  130. package/src/encoding/assert.ts +4 -3
  131. package/src/encoding/encode.ts +37 -31
  132. package/src/encoding/spec.ts +1 -0
  133. package/src/index.ts +8 -14
  134. package/src/types/HelperTypes.ts +54 -2
  135. package/src/types/TypeContext.ts +133 -0
  136. package/src/types/custom/ArraySchema.ts +49 -19
  137. package/src/types/custom/CollectionSchema.ts +1 -0
  138. package/src/types/custom/MapSchema.ts +18 -5
  139. package/src/types/custom/SetSchema.ts +1 -0
  140. package/src/types/symbols.ts +10 -7
  141. 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,113 @@ 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";
166
-
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>();
145
+ import { Schema, type, view } from "@colyseus/schema";
146
+
147
+ class Player extends Schema {
148
+ @view() @type("string") secret: string;
149
+ @type("string") notSecret: string;
173
150
  }
151
+
152
+ class MyState extends Schema {
153
+ @type({ map: Player }) players = new MapSchema<Player>();
154
+ }
155
+ ```
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 majour 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();
174
234
  ```
175
235
 
236
+ ### Backwards/forwards compability
237
+
238
+ Backwards/fowards compatibility is possible by declaring new fields at the
239
+ end of existing structures, and earlier declarations to not be removed, but
240
+ be marked `@deprecated()` when needed.
241
+
242
+ This is particularly useful for native-compiled targets, such as C#, C++,
243
+ Haxe, etc - where the client-side can potentially not have the most
244
+ up-to-date version of the schema definitions.
245
+
246
+
176
247
  ## Limitations and best practices
177
248
 
178
249
  - Each `Schema` structure can hold up to `64` fields. If you need more fields, use nested structures.
@@ -184,7 +255,6 @@ export class State extends Schema {
184
255
  - `@colyseus/schema` encodes only field values in the specified order.
185
256
  - Both encoder (server) and decoder (client) must have same schema definition.
186
257
  - 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
258
 
189
259
  ## Generating client-side schema files (for strictly typed languages)
190
260
 
@@ -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());