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

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 (146) hide show
  1. package/README.md +148 -62
  2. package/bin/schema-debug +94 -0
  3. package/build/cjs/index.js +2201 -1507
  4. package/build/cjs/index.js.map +1 -1
  5. package/build/esm/index.mjs +2198 -1506
  6. package/build/esm/index.mjs.map +1 -1
  7. package/build/umd/index.js +2208 -1514
  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 +2 -46
  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 +3 -4
  47. package/lib/decoder/DecodeOperation.js +37 -19
  48. package/lib/decoder/DecodeOperation.js.map +1 -1
  49. package/lib/decoder/Decoder.d.ts +6 -7
  50. package/lib/decoder/Decoder.js +14 -14
  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 +51 -65
  64. package/lib/encoder/EncodeOperation.js.map +1 -1
  65. package/lib/encoder/Encoder.d.ts +9 -8
  66. package/lib/encoder/Encoder.js +168 -91
  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 +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 +35 -20
  78. package/lib/encoding/decode.js +43 -87
  79. package/lib/encoding/decode.js.map +1 -1
  80. package/lib/encoding/encode.d.ts +36 -17
  81. package/lib/encoding/encode.js +82 -68
  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 +23 -0
  92. package/lib/types/TypeContext.js +111 -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.js +1 -0
  98. package/lib/types/custom/CollectionSchema.js.map +1 -1
  99. package/lib/types/custom/MapSchema.d.ts +3 -1
  100. package/lib/types/custom/MapSchema.js +12 -4
  101. package/lib/types/custom/MapSchema.js.map +1 -1
  102. package/lib/types/custom/SetSchema.js +1 -0
  103. package/lib/types/custom/SetSchema.js.map +1 -1
  104. package/lib/types/registry.d.ts +8 -1
  105. package/lib/types/registry.js +23 -6
  106. package/lib/types/registry.js.map +1 -1
  107. package/lib/types/symbols.d.ts +8 -5
  108. package/lib/types/symbols.js +9 -6
  109. package/lib/types/symbols.js.map +1 -1
  110. package/lib/types/utils.js +1 -2
  111. package/lib/types/utils.js.map +1 -1
  112. package/lib/utils.js +9 -7
  113. package/lib/utils.js.map +1 -1
  114. package/package.json +7 -6
  115. package/src/Metadata.ts +190 -42
  116. package/src/Reflection.ts +77 -39
  117. package/src/Schema.ts +59 -64
  118. package/src/annotations.ts +156 -202
  119. package/src/bench_encode.ts +108 -0
  120. package/src/codegen/languages/csharp.ts +1 -47
  121. package/src/codegen/parser.ts +107 -0
  122. package/src/codegen/types.ts +1 -0
  123. package/src/debug.ts +55 -0
  124. package/src/decoder/DecodeOperation.ts +46 -18
  125. package/src/decoder/Decoder.ts +17 -15
  126. package/src/decoder/ReferenceTracker.ts +3 -2
  127. package/src/decoder/strategy/StateCallbacks.ts +153 -82
  128. package/src/encoder/ChangeTree.ts +286 -202
  129. package/src/encoder/EncodeOperation.ts +78 -78
  130. package/src/encoder/Encoder.ts +202 -97
  131. package/src/encoder/Root.ts +93 -0
  132. package/src/encoder/StateView.ts +76 -88
  133. package/src/encoding/assert.ts +17 -8
  134. package/src/encoding/decode.ts +62 -97
  135. package/src/encoding/encode.ts +99 -65
  136. package/src/encoding/spec.ts +3 -5
  137. package/src/index.ts +12 -20
  138. package/src/types/HelperTypes.ts +54 -2
  139. package/src/types/TypeContext.ts +133 -0
  140. package/src/types/custom/ArraySchema.ts +49 -19
  141. package/src/types/custom/CollectionSchema.ts +1 -0
  142. package/src/types/custom/MapSchema.ts +18 -5
  143. package/src/types/custom/SetSchema.ts +1 -0
  144. package/src/types/registry.ts +22 -3
  145. package/src/types/symbols.ts +10 -7
  146. package/src/utils.ts +7 -3
@@ -1,14 +1,16 @@
1
1
  import type { Schema } from "../Schema";
2
- import { TypeContext } from "../annotations";
3
- import { $changes, $encoder, $filter } from "../types/symbols";
2
+ import { TypeContext } from "../types/TypeContext";
3
+ import { $changes, $encoder, $filter, $onEncodeEnd } from "../types/symbols";
4
4
 
5
- import * as encode from "../encoding/encode";
5
+ import { encode } from "../encoding/encode";
6
6
  import type { Iterator } from "../encoding/decode";
7
7
 
8
- import { SWITCH_TO_STRUCTURE, TYPE_ID } from '../encoding/spec';
9
- import { Root } from "./ChangeTree";
8
+ import { OPERATION, SWITCH_TO_STRUCTURE, TYPE_ID } from '../encoding/spec';
9
+ import { Root } from "./Root";
10
10
  import { getNextPowerOf2 } from "../utils";
11
+
11
12
  import type { StateView } from "./StateView";
13
+ import type { Metadata } from "../Metadata";
12
14
 
13
15
  export class Encoder<T extends Schema = any> {
14
16
  static BUFFER_SIZE = 8 * 1024;// 8KB
@@ -19,14 +21,16 @@ export class Encoder<T extends Schema = any> {
19
21
 
20
22
  root: Root;
21
23
 
22
- constructor(root: T) {
23
- this.setRoot(root);
24
+ constructor(state: T) {
24
25
 
25
26
  //
26
27
  // TODO: cache and restore "Context" based on root schema
27
28
  // (to avoid creating a new context for every new room)
28
29
  //
29
- this.context = new TypeContext(root.constructor as typeof Schema);
30
+ this.context = new TypeContext(state.constructor as typeof Schema);
31
+ this.root = new Root(this.context);
32
+
33
+ this.setState(state);
30
34
 
31
35
  // console.log(">>>>>>>>>>>>>>>> Encoder types");
32
36
  // this.context.schemas.forEach((id, schema) => {
@@ -34,32 +38,45 @@ export class Encoder<T extends Schema = any> {
34
38
  // });
35
39
  }
36
40
 
37
- protected setRoot(state: T) {
38
- this.root = new Root();
41
+ protected setState(state: T) {
39
42
  this.state = state;
40
- state[$changes].setRoot(this.root);
43
+ this.state[$changes].setRoot(this.root);
41
44
  }
42
45
 
43
46
  encode(
44
47
  it: Iterator = { offset: 0 },
45
48
  view?: StateView,
46
- bytes = this.sharedBuffer,
47
- changeTrees = this.root.changes
49
+ buffer = this.sharedBuffer,
50
+ changeSetName: "changes" | "allChanges" | "filteredChanges" | "allFilteredChanges" = "changes",
51
+ isEncodeAll = changeSetName === "allChanges",
52
+ initialOffset = it.offset // cache current offset in case we need to resize the buffer
48
53
  ): Buffer {
49
- const initialOffset = it.offset; // cache current offset in case we need to resize the buffer
50
-
51
- const isEncodeAll = this.root.allChanges === changeTrees;
52
54
  const hasView = (view !== undefined);
53
55
  const rootChangeTree = this.state[$changes];
54
56
 
55
- const changeTreesIterator = changeTrees.entries();
57
+ const shouldDiscardChanges = !isEncodeAll && !hasView;
58
+ const changeTrees = this.root[changeSetName];
56
59
 
57
- for (const [changeTree, changes] of changeTreesIterator) {
60
+ for (let i = 0, numChangeTrees = changeTrees.length; i < numChangeTrees; i++) {
61
+ const changeTree = changeTrees[i];
62
+
63
+ // // Root#removeChangeFromChangeSet() is now removing instead of setting to "undefined"
64
+ // if (changeTree === undefined) { continue; }
65
+
66
+ const operations = changeTree[changeSetName];
58
67
  const ref = changeTree.ref;
59
68
 
60
- const ctor = ref['constructor'];
69
+ const ctor = ref.constructor;
61
70
  const encoder = ctor[$encoder];
62
71
  const filter = ctor[$filter];
72
+ const metadata = ctor[Symbol.metadata];
73
+
74
+ // try { throw new Error(); } catch (e) {
75
+ // // only print if not coming from Reflection.ts
76
+ // if (!e.stack.includes("src/Reflection.ts")) {
77
+ // console.log("ChangeTree:", { refId: changeTree.refId, ref: ref.constructor.name });
78
+ // }
79
+ // }
63
80
 
64
81
  if (hasView) {
65
82
  if (!view.items.has(changeTree)) {
@@ -72,14 +89,21 @@ export class Encoder<T extends Schema = any> {
72
89
  }
73
90
 
74
91
  // skip root `refId` if it's the first change tree
75
- if (it.offset !== initialOffset || changeTree !== rootChangeTree) {
76
- bytes[it.offset++] = SWITCH_TO_STRUCTURE & 255;
77
- encode.number(bytes, changeTree.refId, it);
92
+ // (unless it "hasView", which will need to revisit the root)
93
+ if (hasView || it.offset > initialOffset || changeTree !== rootChangeTree) {
94
+ buffer[it.offset++] = SWITCH_TO_STRUCTURE & 255;
95
+ encode.number(buffer, changeTree.refId, it);
78
96
  }
79
97
 
80
- const changesIterator = changes.entries();
98
+ for (let j = 0, numChanges = operations.operations.length; j < numChanges; j++) {
99
+ const fieldIndex = operations.operations[j];
100
+
101
+ const operation = (fieldIndex < 0)
102
+ ? Math.abs(fieldIndex) // "pure" operation without fieldIndex (e.g. CLEAR, REVERSE, etc.)
103
+ : (isEncodeAll)
104
+ ? OPERATION.ADD
105
+ : changeTree.indexedOperations[fieldIndex];
81
106
 
82
- for (const [fieldIndex, operation] of changesIterator) {
83
107
  //
84
108
  // first pass (encodeAll), identify "filtered" operations without encoding them
85
109
  // they will be encoded per client, based on their view.
@@ -87,116 +111,158 @@ export class Encoder<T extends Schema = any> {
87
111
  // TODO: how can we optimize filtering out "encode all" operations?
88
112
  // TODO: avoid checking if no view tags were defined
89
113
  //
90
- if (filter && !filter(ref, fieldIndex, view)) {
91
- // console.log("SKIP FIELD:", { ref: changeTree.ref.constructor.name, fieldIndex, })
92
-
114
+ if (fieldIndex === undefined || operation === undefined || (filter && !filter(ref, fieldIndex, view))) {
93
115
  // console.log("ADD AS INVISIBLE:", fieldIndex, changeTree.ref.constructor.name)
94
116
  // view?.invisible.add(changeTree);
95
117
  continue;
96
118
  }
97
119
 
98
- // console.log("WILL ENCODE", {
99
- // ref: changeTree.ref.constructor.name,
100
- // fieldIndex,
101
- // operation: OPERATION[operation],
102
- // });
120
+ // try { throw new Error(); } catch (e) {
121
+ // // only print if not coming from Reflection.ts
122
+ // if (!e.stack.includes("src/Reflection.ts")) {
123
+ // console.log("WILL ENCODE", {
124
+ // ref: changeTree.ref.constructor.name,
125
+ // fieldIndex,
126
+ // operation: OPERATION[operation],
127
+ // });
128
+ // }
129
+ // }
130
+
131
+ // console.log("encode...", { ref: changeTree.ref.constructor.name, refId: changeTree.refId, fieldIndex, operation });
132
+
133
+ encoder(this, buffer, changeTree, fieldIndex, operation, it, isEncodeAll, hasView, metadata);
134
+ }
135
+
136
+ if (shouldDiscardChanges) {
137
+ changeTree.discard();
103
138
 
104
- encoder(this, bytes, changeTree, fieldIndex, operation, it, isEncodeAll, hasView);
139
+ // Not a new instance anymore
140
+ changeTree.isNew = false;
105
141
  }
106
142
  }
107
143
 
108
- if (it.offset > bytes.byteLength) {
109
- const newSize = getNextPowerOf2(this.sharedBuffer.byteLength * 2);
110
- console.warn("@colyseus/schema encode buffer overflow. Current buffer size: " + bytes.byteLength + ", encoding offset: " + it.offset + ", new size: " + newSize);
144
+ if (it.offset > buffer.byteLength) {
145
+ const newSize = getNextPowerOf2(buffer.byteLength * 2);
146
+ console.warn(`@colyseus/schema buffer overflow. Encoded state is higher than default BUFFER_SIZE. Use the following to increase default BUFFER_SIZE:
147
+
148
+ import { Encoder } from "@colyseus/schema";
149
+ Encoder.BUFFER_SIZE = ${Math.round(newSize / 1024)} * 1024; // ${Math.round(newSize / 1024)} KB
150
+ `);
111
151
 
112
152
  //
113
153
  // resize buffer and re-encode (TODO: can we avoid re-encoding here?)
114
154
  //
115
- this.sharedBuffer = Buffer.allocUnsafeSlow(newSize);
116
- return this.encode({ offset: initialOffset }, view);
155
+ buffer = Buffer.allocUnsafeSlow(newSize);
117
156
 
118
- } else {
119
- //
120
- // only clear changes after making sure buffer resize is not required.
121
- //
122
- if (!isEncodeAll && !hasView) {
123
- //
124
- // FIXME: avoid iterating over change trees twice.
125
- //
126
- this.onEndEncode(changeTrees);
157
+ // assign resized buffer to local sharedBuffer
158
+ if (buffer === this.sharedBuffer) {
159
+ this.sharedBuffer = buffer;
127
160
  }
128
161
 
129
- // return bytes;
130
- return bytes.slice(0, it.offset);
162
+ return this.encode({ offset: initialOffset }, view, buffer, changeSetName, isEncodeAll);
163
+
164
+ } else {
165
+ // //
166
+ // // only clear changes after making sure buffer resize is not required.
167
+ // //
168
+ // if (shouldClearChanges) {
169
+ // //
170
+ // // FIXME: avoid iterating over change trees twice.
171
+ // //
172
+ // this.onEndEncode(changeTrees);
173
+ // }
174
+
175
+ return buffer.subarray(0, it.offset);
131
176
  }
132
177
  }
133
178
 
134
- encodeAll(it: Iterator = { offset: 0 }) {
135
- // console.log(`encodeAll(), this.$root.allChanges (${this.$root.allChanges.size})`);
179
+ encodeAll(it: Iterator = { offset: 0 }, buffer: Buffer = this.sharedBuffer) {
180
+ // console.log(`\nencodeAll(), this.root.allChanges (${(Object.keys(this.root.allChanges).length)})`);
181
+ // this.debugChanges("allChanges");
136
182
 
137
- // Array.from(this.$root.allChanges.entries()).map((item) => {
138
- // console.log("->", item[0].refId, item[0].ref.toJSON());
139
- // });
140
-
141
- return this.encode(it, undefined, this.sharedBuffer, this.root.allChanges);
183
+ return this.encode(it, undefined, buffer, "allChanges", true);
142
184
  }
143
185
 
144
186
  encodeAllView(view: StateView, sharedOffset: number, it: Iterator, bytes = this.sharedBuffer) {
145
187
  const viewOffset = it.offset;
146
188
 
147
- // console.log(`encodeAllView(), this.$root.allFilteredChanges (${this.$root.allFilteredChanges.size})`);
148
- // this.debugAllFilteredChanges();
189
+ // console.log(`\nencodeAllView(), this.root.allFilteredChanges (${(Object.keys(this.root.allFilteredChanges).length)})`);
190
+ // this.debugChanges("allFilteredChanges");
191
+
192
+ // console.log("\n\nENCODE ALL FOR VIEW...\n\n")
149
193
 
150
194
  // try to encode "filtered" changes
151
- this.encode(it, view, bytes, this.root.allFilteredChanges);
195
+ this.encode(it, view, bytes, "allFilteredChanges", true, viewOffset);
152
196
 
153
197
  return Buffer.concat([
154
- bytes.slice(0, sharedOffset),
155
- bytes.slice(viewOffset, it.offset)
198
+ bytes.subarray(0, sharedOffset),
199
+ bytes.subarray(viewOffset, it.offset)
156
200
  ]);
157
201
  }
158
202
 
159
-
160
- // debugAllFilteredChanges() {
161
- // Array.from(this.$root.allFilteredChanges.entries()).map((item) => {
162
- // console.log("->", { refId: item[0].refId }, item[0].ref.toJSON());
163
- // if (Array.isArray(item[0].ref.toJSON())) {
164
- // item[1].forEach((op, key) => {
165
- // console.log(" ->", { key, op: OPERATION[op] });
166
- // })
167
- // }
168
- // });
169
- // }
203
+ debugChanges(field: "changes" | "allFilteredChanges" | "allChanges" | "filteredChanges") {
204
+ const rootChangeSet = (typeof (field) === "string")
205
+ ? this.root[field]
206
+ : field;
207
+
208
+ rootChangeSet.forEach((changeTree) => {
209
+ const changeSet = changeTree[field];
210
+
211
+ const metadata: Metadata = changeTree.ref.constructor[Symbol.metadata];
212
+ console.log("->", { ref: changeTree.ref.constructor.name, refId: changeTree.refId, changes: Object.keys(changeSet).length });
213
+ for (const index in changeSet) {
214
+ const op = changeSet[index];
215
+ console.log(" ->", {
216
+ index,
217
+ field: metadata?.[index],
218
+ op: OPERATION[op],
219
+ });
220
+ }
221
+ });
222
+ }
170
223
 
171
224
  encodeView(view: StateView, sharedOffset: number, it: Iterator, bytes = this.sharedBuffer) {
172
225
  const viewOffset = it.offset;
173
226
 
174
- // try to encode "filtered" changes
175
- this.encode(it, view, bytes, this.root.filteredChanges);
227
+ // console.log(`\nencodeView(), view.changes (${view.changes.size})`);
228
+ // this.debugChanges(view.changes);
229
+
230
+ // console.log(`\nencodeView(), this.root.filteredChanges (${this.root.filteredChanges.size})`);
231
+ // this.debugChanges("filteredChanges");
176
232
 
177
233
  // encode visibility changes (add/remove for this view)
178
- const viewChangesIterator = view.changes.entries();
179
- for (const [changeTree, changes] of viewChangesIterator) {
180
- if (changes.size === 0) {
181
- // FIXME: avoid having empty changes if no changes were made
182
- // console.log("changes.size === 0", changeTree.ref.constructor.name);
234
+ const refIds = Object.keys(view.changes);
235
+ // console.log("ENCODE VIEW:", refIds);
236
+ for (let i = 0, numRefIds = refIds.length; i < numRefIds; i++) {
237
+ const refId = refIds[i];
238
+ const changes = view.changes[refId];
239
+ const changeTree = this.root.changeTrees[refId];
240
+
241
+ if (
242
+ changeTree === undefined ||
243
+ Object.keys(changes).length === 0 // FIXME: avoid having empty changes if no changes were made
244
+ ) {
245
+ // console.log("changes.size === 0, skip", changeTree.ref.constructor.name);
183
246
  continue;
184
247
  }
185
248
 
186
249
  const ref = changeTree.ref;
187
250
 
188
- const ctor = ref['constructor'];
251
+ const ctor = ref.constructor;
189
252
  const encoder = ctor[$encoder];
253
+ const metadata = ctor[Symbol.metadata];
190
254
 
191
255
  bytes[it.offset++] = SWITCH_TO_STRUCTURE & 255;
192
256
  encode.number(bytes, changeTree.refId, it);
193
257
 
194
- const changesIterator = changes.entries();
258
+ const keys = Object.keys(changes);
259
+ for (let i = 0, numChanges = keys.length; i < numChanges; i++) {
260
+ const key = keys[i];
261
+ const operation = changes[key];
195
262
 
196
- for (const [fieldIndex, operation] of changesIterator) {
197
263
  // isEncodeAll = false
198
264
  // hasView = true
199
- encoder(this, bytes, changeTree, fieldIndex, operation, it, false, true);
265
+ encoder(this, bytes, changeTree, Number(key), operation, it, false, true, metadata);
200
266
  }
201
267
  }
202
268
 
@@ -205,31 +271,58 @@ export class Encoder<T extends Schema = any> {
205
271
  // (to allow re-using StateView's for multiple clients)
206
272
  //
207
273
  // clear "view" changes after encoding
208
- view.changes.clear();
274
+ view.changes = {};
275
+
276
+ // console.log("FILTERED CHANGES:", this.root.filteredChanges);
277
+
278
+ // try to encode "filtered" changes
279
+ this.encode(it, view, bytes, "filteredChanges", false, viewOffset);
209
280
 
210
281
  return Buffer.concat([
211
- bytes.slice(0, sharedOffset),
212
- bytes.slice(viewOffset, it.offset)
282
+ bytes.subarray(0, sharedOffset),
283
+ bytes.subarray(viewOffset, it.offset)
213
284
  ]);
214
285
  }
215
286
 
216
287
  onEndEncode(changeTrees = this.root.changes) {
217
- const changeTreesIterator = changeTrees.entries();
218
- for (const [changeTree, _] of changeTreesIterator) {
219
- changeTree.endEncode();
220
- }
288
+ // changeTrees.forEach(function(changeTree) {
289
+ // changeTree.endEncode();
290
+ // });
291
+
292
+
293
+ // for (const refId in changeTrees) {
294
+ // const changeTree = this.root.changeTrees[refId];
295
+ // changeTree.endEncode();
296
+
297
+ // // changeTree.changes.clear();
298
+
299
+ // // // ArraySchema and MapSchema have a custom "encode end" method
300
+ // // changeTree.ref[$onEncodeEnd]?.();
301
+
302
+ // // // Not a new instance anymore
303
+ // // delete changeTree[$isNew];
304
+ // }
221
305
  }
222
306
 
223
307
  discardChanges() {
308
+ // console.log("DISCARD CHANGES!");
309
+
224
310
  // discard shared changes
225
- if (this.root.changes.size > 0) {
226
- this.onEndEncode(this.root.changes);
227
- this.root.changes.clear();
311
+ let length = this.root.changes.length;
312
+ if (length > 0) {
313
+ while (length--) {
314
+ this.root.changes[length]?.endEncode();
315
+ }
316
+ this.root.changes.length = 0;
228
317
  }
318
+
229
319
  // discard filtered changes
230
- if (this.root.filteredChanges.size > 0) {
231
- this.onEndEncode(this.root.filteredChanges);
232
- this.root.filteredChanges.clear();
320
+ length = this.root.filteredChanges.length;
321
+ if (length > 0) {
322
+ while (length--) {
323
+ this.root.filteredChanges[length]?.endEncode();
324
+ }
325
+ this.root.filteredChanges.length = 0;
233
326
  }
234
327
  }
235
328
 
@@ -237,9 +330,21 @@ export class Encoder<T extends Schema = any> {
237
330
  const baseTypeId = this.context.getTypeId(baseType);
238
331
  const targetTypeId = this.context.getTypeId(targetType);
239
332
 
333
+ if (targetTypeId === undefined) {
334
+ console.warn(`@colyseus/schema WARNING: Class "${targetType.name}" is not registered on TypeRegistry - Please either tag the class with @entity or define a @type() field.`);
335
+ return;
336
+ }
337
+
240
338
  if (baseTypeId !== targetTypeId) {
241
339
  bytes[it.offset++] = TYPE_ID & 255;
242
340
  encode.number(bytes, targetTypeId, it);
243
341
  }
244
342
  }
245
- }
343
+
344
+ get hasChanges() {
345
+ return (
346
+ this.root.changes.length > 0 ||
347
+ this.root.filteredChanges.length > 0
348
+ );
349
+ }
350
+ }
@@ -0,0 +1,93 @@
1
+ import { OPERATION } from "../encoding/spec";
2
+ import { TypeContext } from "../types/TypeContext";
3
+ import { spliceOne } from "../types/utils";
4
+ import { ChangeTree, setOperationAtIndex } from "./ChangeTree";
5
+
6
+ export class Root {
7
+ protected nextUniqueId: number = 0;
8
+
9
+ refCount: {[id: number]: number} = {};
10
+ changeTrees: {[refId: number]: ChangeTree} = {};
11
+
12
+ // all changes
13
+ allChanges: ChangeTree[] = [];
14
+ allFilteredChanges: ChangeTree[] = [];// TODO: do not initialize it if filters are not used
15
+
16
+ // pending changes to be encoded
17
+ changes: ChangeTree[] = [];
18
+ filteredChanges: ChangeTree[] = [];// TODO: do not initialize it if filters are not used
19
+
20
+ constructor(public types: TypeContext) { }
21
+
22
+ getNextUniqueId() {
23
+ return this.nextUniqueId++;
24
+ }
25
+
26
+ add(changeTree: ChangeTree) {
27
+ // FIXME: move implementation of `ensureRefId` to `Root` class
28
+ changeTree.ensureRefId();
29
+
30
+ const isNewChangeTree = (this.changeTrees[changeTree.refId] === undefined);
31
+ if (isNewChangeTree) { this.changeTrees[changeTree.refId] = changeTree; }
32
+
33
+ const previousRefCount = this.refCount[changeTree.refId];
34
+ if (previousRefCount === 0) {
35
+ //
36
+ // When a ChangeTree is re-added, it means that it was previously removed.
37
+ // We need to re-add all changes to the `changes` map.
38
+ //
39
+ const ops = changeTree.allChanges.operations;
40
+ let len = ops.length;
41
+ while (len--) {
42
+ changeTree.indexedOperations[ops[len]] = OPERATION.ADD;
43
+ setOperationAtIndex(changeTree.changes, len);
44
+ }
45
+ }
46
+
47
+ this.refCount[changeTree.refId] = (previousRefCount || 0) + 1;
48
+
49
+ return isNewChangeTree;
50
+ }
51
+
52
+ remove(changeTree: ChangeTree) {
53
+ const refCount = (this.refCount[changeTree.refId]) - 1;
54
+
55
+ if (refCount <= 0) {
56
+ //
57
+ // Only remove "root" reference if it's the last reference
58
+ //
59
+ changeTree.root = undefined;
60
+ delete this.changeTrees[changeTree.refId];
61
+
62
+ this.removeChangeFromChangeSet("allChanges", changeTree);
63
+ this.removeChangeFromChangeSet("changes", changeTree);
64
+
65
+ if (changeTree.isFiltered || changeTree.isPartiallyFiltered) {
66
+ this.removeChangeFromChangeSet("allFilteredChanges", changeTree);
67
+ this.removeChangeFromChangeSet("filteredChanges", changeTree);
68
+ }
69
+
70
+ this.refCount[changeTree.refId] = 0;
71
+
72
+ } else {
73
+ this.refCount[changeTree.refId] = refCount;
74
+ }
75
+
76
+ changeTree.forEachChild((child, _) => this.remove(child));
77
+
78
+ return refCount;
79
+ }
80
+
81
+ removeChangeFromChangeSet(changeSetName: "allChanges" | "changes" | "filteredChanges" | "allFilteredChanges", changeTree: ChangeTree) {
82
+ const changeSet = this[changeSetName];
83
+ const index = changeSet.indexOf(changeTree);
84
+ if (index !== -1) {
85
+ spliceOne(changeSet, index);
86
+ // changeSet[index] = undefined;
87
+ }
88
+ }
89
+
90
+ clear() {
91
+ this.changes.length = 0;
92
+ }
93
+ }