@colyseus/schema 3.0.0-alpha.5 → 3.0.0-alpha.50

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 (148) hide show
  1. package/README.md +148 -62
  2. package/bin/schema-debug +94 -0
  3. package/build/cjs/index.js +2234 -1510
  4. package/build/cjs/index.js.map +1 -1
  5. package/build/esm/index.mjs +2231 -1509
  6. package/build/esm/index.mjs.map +1 -1
  7. package/build/umd/index.js +2233 -1509
  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 +2 -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 +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 +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 +75 -65
  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 +9 -8
  66. package/lib/encoder/Encoder.js +134 -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 -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 +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.js +1 -0
  98. package/lib/types/custom/CollectionSchema.js.map +1 -1
  99. package/lib/types/custom/MapSchema.d.ts +17 -15
  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 +19 -18
  115. package/src/Metadata.ts +190 -42
  116. package/src/Reflection.ts +76 -38
  117. package/src/Schema.ts +72 -70
  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/languages/haxe.ts +4 -0
  122. package/src/codegen/languages/lua.ts +19 -27
  123. package/src/codegen/parser.ts +107 -0
  124. package/src/codegen/types.ts +1 -0
  125. package/src/debug.ts +55 -0
  126. package/src/decoder/DecodeOperation.ts +46 -18
  127. package/src/decoder/Decoder.ts +17 -15
  128. package/src/decoder/ReferenceTracker.ts +5 -3
  129. package/src/decoder/strategy/StateCallbacks.ts +153 -82
  130. package/src/encoder/ChangeTree.ts +282 -209
  131. package/src/encoder/EncodeOperation.ts +78 -78
  132. package/src/encoder/Encoder.ts +161 -96
  133. package/src/encoder/Root.ts +93 -0
  134. package/src/encoder/StateView.ts +80 -88
  135. package/src/encoding/assert.ts +17 -8
  136. package/src/encoding/decode.ts +73 -93
  137. package/src/encoding/encode.ts +99 -65
  138. package/src/encoding/spec.ts +3 -5
  139. package/src/index.ts +12 -20
  140. package/src/types/HelperTypes.ts +54 -2
  141. package/src/types/TypeContext.ts +175 -0
  142. package/src/types/custom/ArraySchema.ts +49 -19
  143. package/src/types/custom/CollectionSchema.ts +1 -0
  144. package/src/types/custom/MapSchema.ts +30 -17
  145. package/src/types/custom/SetSchema.ts +1 -0
  146. package/src/types/registry.ts +22 -3
  147. package/src/types/symbols.ts +10 -7
  148. package/src/utils.ts +7 -3
@@ -1,17 +1,15 @@
1
1
  import { OPERATION } from "../encoding/spec";
2
- import { $changes } from "../types/symbols";
3
- import { getType } from "../types/registry";
2
+ import { $changes, $childType, $getByIndex } from "../types/symbols";
4
3
 
5
- import * as encode from "../encoding/encode";
6
- import { EncodeSchemaError, assertInstanceType, assertType } from "../encoding/assert";
4
+ import { encode } from "../encoding/encode";
7
5
 
8
6
  import type { ChangeTree, Ref } from "./ChangeTree";
9
7
  import type { Encoder } from "./Encoder";
10
8
  import type { Schema } from "../Schema";
11
- import type { PrimitiveType } from "../annotations";
12
9
 
13
10
  import type { Iterator } from "../encoding/decode";
14
11
  import type { ArraySchema } from "../types/custom/ArraySchema";
12
+ import type { Metadata } from "../Metadata";
15
13
 
16
14
  export type EncodeOperation<T extends Ref = any> = (
17
15
  encoder: Encoder,
@@ -22,43 +20,21 @@ export type EncodeOperation<T extends Ref = any> = (
22
20
  it: Iterator,
23
21
  isEncodeAll: boolean,
24
22
  hasView: boolean,
23
+ metadata?: Metadata,
25
24
  ) => void;
26
25
 
27
- export function encodePrimitiveType(
28
- type: PrimitiveType,
29
- bytes: Buffer,
30
- value: any,
31
- klass: Schema,
32
- field: string | number,
33
- it: Iterator,
34
- ) {
35
- assertType(value, type as string, klass, field);
36
-
37
- const encodeFunc = encode[type as string];
38
-
39
- if (encodeFunc) {
40
- encodeFunc(bytes, value, it);
41
- // encodeFunc(bytes, value);
42
-
43
- } else {
44
- throw new EncodeSchemaError(`a '${type}' was expected, but ${value} was provided in ${klass.constructor.name}#${field}`);
45
- }
46
- };
47
-
48
26
  export function encodeValue(
49
27
  encoder: Encoder,
50
28
  bytes: Buffer,
51
- ref: Ref,
52
29
  type: any,
53
30
  value: any,
54
- field: string | number,
55
31
  operation: OPERATION,
56
32
  it: Iterator,
57
33
  ) {
58
- if (type[Symbol.metadata] !== undefined) {
59
- // TODO: move this to the `@type()` annotation
60
- assertInstanceType(value, type as typeof Schema, ref as Schema, field);
34
+ if (typeof (type) === "string") {
35
+ encode[type]?.(bytes, value, it);
61
36
 
37
+ } else if (type[Symbol.metadata] !== undefined) {
62
38
  //
63
39
  // Encode refId for this instance.
64
40
  // The actual instance is going to be encoded on next `changeTree` iteration.
@@ -70,23 +46,7 @@ export function encodeValue(
70
46
  encoder.tryEncodeTypeId(bytes, type as typeof Schema, value.constructor as typeof Schema, it);
71
47
  }
72
48
 
73
- } else if (typeof (type) === "string") {
74
- //
75
- // Primitive values
76
- //
77
- encodePrimitiveType(type as PrimitiveType, bytes, value, ref as Schema, field, it);
78
-
79
49
  } else {
80
- //
81
- // Custom type (MapSchema, ArraySchema, etc)
82
- //
83
- const definition = getType(Object.keys(type)[0]);
84
-
85
- //
86
- // ensure a ArraySchema has been provided
87
- //
88
- assertInstanceType(ref[field], definition.constructor, ref as Schema, field);
89
-
90
50
  //
91
51
  // Encode refId for this instance.
92
52
  // The actual instance is going to be encoded on next `changeTree` iteration.
@@ -106,14 +66,10 @@ export const encodeSchemaOperation: EncodeOperation = function (
106
66
  index: number,
107
67
  operation: OPERATION,
108
68
  it: Iterator,
69
+ _: any,
70
+ __: any,
71
+ metadata: Metadata,
109
72
  ) {
110
- const ref = changeTree.ref;
111
- const metadata = ref['constructor'][Symbol.metadata];
112
-
113
- const field = metadata[index];
114
- const type = metadata[field].type;
115
- const value = ref[field];
116
-
117
73
  // "compress" field index + operation
118
74
  bytes[it.offset++] = (index | operation) & 255;
119
75
 
@@ -122,8 +78,18 @@ export const encodeSchemaOperation: EncodeOperation = function (
122
78
  return;
123
79
  }
124
80
 
81
+ const ref = changeTree.ref;
82
+ const field = metadata[index];
83
+
125
84
  // TODO: inline this function call small performance gain
126
- encodeValue(encoder, bytes, ref, type, value, field, operation, it);
85
+ encodeValue(
86
+ encoder,
87
+ bytes,
88
+ metadata[index].type,
89
+ ref[field.name],
90
+ operation,
91
+ it
92
+ );
127
93
  }
128
94
 
129
95
  /**
@@ -134,12 +100,10 @@ export const encodeKeyValueOperation: EncodeOperation = function (
134
100
  encoder: Encoder,
135
101
  bytes: Buffer,
136
102
  changeTree: ChangeTree,
137
- field: number,
103
+ index: number,
138
104
  operation: OPERATION,
139
105
  it: Iterator,
140
106
  ) {
141
- const ref = changeTree.ref;
142
-
143
107
  // encode operation
144
108
  bytes[it.offset++] = operation & 255;
145
109
 
@@ -149,31 +113,53 @@ export const encodeKeyValueOperation: EncodeOperation = function (
149
113
  }
150
114
 
151
115
  // encode index
152
- encode.number(bytes, field, it);
116
+ encode.number(bytes, index, it);
153
117
 
154
118
  // Do not encode value for DELETE operations
155
119
  if (operation === OPERATION.DELETE) {
156
120
  return;
157
121
  }
158
122
 
123
+ const ref = changeTree.ref;
124
+
159
125
  //
160
126
  // encode "alias" for dynamic fields (maps)
161
127
  //
162
- if ((operation & OPERATION.ADD) == OPERATION.ADD) { // ADD or DELETE_AND_ADD
128
+ if ((operation & OPERATION.ADD) === OPERATION.ADD) { // ADD or DELETE_AND_ADD
163
129
  if (typeof(ref['set']) === "function") {
164
130
  //
165
131
  // MapSchema dynamic key
166
132
  //
167
- const dynamicIndex = changeTree.ref['$indexes'].get(field);
133
+ const dynamicIndex = changeTree.ref['$indexes'].get(index);
168
134
  encode.string(bytes, dynamicIndex, it);
169
135
  }
170
136
  }
171
137
 
172
- const type = changeTree.getType(field);
173
- const value = changeTree.getValue(field);
138
+ const type = ref[$childType];
139
+ const value = ref[$getByIndex](index);
140
+
141
+ // try { throw new Error(); } catch (e) {
142
+ // // only print if not coming from Reflection.ts
143
+ // if (!e.stack.includes("src/Reflection.ts")) {
144
+ // console.log("encodeKeyValueOperation -> ", {
145
+ // ref: changeTree.ref.constructor.name,
146
+ // field,
147
+ // operation: OPERATION[operation],
148
+ // value: value?.toJSON(),
149
+ // items: ref.toJSON(),
150
+ // });
151
+ // }
152
+ // }
174
153
 
175
154
  // TODO: inline this function call small performance gain
176
- encodeValue(encoder, bytes, ref, type, value, field, operation, it);
155
+ encodeValue(
156
+ encoder,
157
+ bytes,
158
+ type,
159
+ value,
160
+ operation,
161
+ it
162
+ );
177
163
  }
178
164
 
179
165
  /**
@@ -191,30 +177,37 @@ export const encodeArray: EncodeOperation = function (
191
177
  hasView: boolean,
192
178
  ) {
193
179
  const ref = changeTree.ref;
180
+ const useOperationByRefId = hasView && changeTree.isFiltered && (typeof (changeTree.getType(field)) !== "string");
194
181
 
195
- if (
196
- hasView &&
197
- operation === OPERATION.DELETE &&
198
- typeof (changeTree.getType(field)) !== "string"
199
- ) {
200
- // encode delete by refId (array of schemas)
201
- bytes[it.offset++] = OPERATION.DELETE_BY_REFID;
202
- const value = ref['tmpItems'][field];
203
- const refId = value[$changes].refId;
204
- encode.number(bytes, refId, it);
205
- return;
182
+ let refOrIndex: number;
183
+
184
+ if (useOperationByRefId) {
185
+ refOrIndex = ref['tmpItems'][field][$changes].refId;
186
+
187
+ if (operation === OPERATION.DELETE) {
188
+ operation = OPERATION.DELETE_BY_REFID;
189
+
190
+ } else if (operation === OPERATION.ADD) {
191
+ operation = OPERATION.ADD_BY_REFID;
192
+ }
193
+
194
+ } else {
195
+ refOrIndex = field;
206
196
  }
207
197
 
208
198
  // encode operation
209
199
  bytes[it.offset++] = operation & 255;
210
200
 
211
201
  // custom operations
212
- if (operation === OPERATION.CLEAR) {
202
+ if (
203
+ operation === OPERATION.CLEAR ||
204
+ operation === OPERATION.REVERSE
205
+ ) {
213
206
  return;
214
207
  }
215
208
 
216
209
  // encode index
217
- encode.number(bytes, field, it);
210
+ encode.number(bytes, refOrIndex, it);
218
211
 
219
212
  // Do not encode value for DELETE operations
220
213
  if (operation === OPERATION.DELETE) {
@@ -233,5 +226,12 @@ export const encodeArray: EncodeOperation = function (
233
226
  // });
234
227
 
235
228
  // TODO: inline this function call small performance gain
236
- encodeValue(encoder, bytes, ref, type, value, field, operation, it);
229
+ encodeValue(
230
+ encoder,
231
+ bytes,
232
+ type,
233
+ value,
234
+ operation,
235
+ it
236
+ );
237
237
  }
@@ -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,15 @@ export class Encoder<T extends Schema = any> {
19
21
 
20
22
  root: Root;
21
23
 
22
- constructor(root: T) {
23
- this.setRoot(root);
24
-
24
+ constructor(state: T) {
25
25
  //
26
26
  // TODO: cache and restore "Context" based on root schema
27
27
  // (to avoid creating a new context for every new room)
28
28
  //
29
- this.context = new TypeContext(root.constructor as typeof Schema);
29
+ this.context = new TypeContext(state.constructor as typeof Schema);
30
+ this.root = new Root(this.context);
31
+
32
+ this.setState(state);
30
33
 
31
34
  // console.log(">>>>>>>>>>>>>>>> Encoder types");
32
35
  // this.context.schemas.forEach((id, schema) => {
@@ -34,32 +37,35 @@ export class Encoder<T extends Schema = any> {
34
37
  // });
35
38
  }
36
39
 
37
- protected setRoot(state: T) {
38
- this.root = new Root();
40
+ protected setState(state: T) {
39
41
  this.state = state;
40
- state[$changes].setRoot(this.root);
42
+ this.state[$changes].setRoot(this.root);
41
43
  }
42
44
 
43
45
  encode(
44
46
  it: Iterator = { offset: 0 },
45
47
  view?: StateView,
46
- bytes = this.sharedBuffer,
47
- changeTrees = this.root.changes
48
+ buffer = this.sharedBuffer,
49
+ changeSetName: "changes" | "allChanges" | "filteredChanges" | "allFilteredChanges" = "changes",
50
+ isEncodeAll = changeSetName === "allChanges",
51
+ initialOffset = it.offset // cache current offset in case we need to resize the buffer
48
52
  ): 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
53
  const hasView = (view !== undefined);
53
54
  const rootChangeTree = this.state[$changes];
54
55
 
55
- const changeTreesIterator = changeTrees.entries();
56
+ const shouldDiscardChanges = !isEncodeAll && !hasView;
57
+ const changeTrees = this.root[changeSetName];
58
+
59
+ for (let i = 0, numChangeTrees = changeTrees.length; i < numChangeTrees; i++) {
60
+ const changeTree = changeTrees[i];
56
61
 
57
- for (const [changeTree, changes] of changeTreesIterator) {
62
+ const operations = changeTree[changeSetName];
58
63
  const ref = changeTree.ref;
59
64
 
60
- const ctor = ref['constructor'];
65
+ const ctor = ref.constructor;
61
66
  const encoder = ctor[$encoder];
62
67
  const filter = ctor[$filter];
68
+ const metadata = ctor[Symbol.metadata];
63
69
 
64
70
  if (hasView) {
65
71
  if (!view.items.has(changeTree)) {
@@ -72,14 +78,21 @@ export class Encoder<T extends Schema = any> {
72
78
  }
73
79
 
74
80
  // 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);
81
+ // (unless it "hasView", which will need to revisit the root)
82
+ if (hasView || it.offset > initialOffset || changeTree !== rootChangeTree) {
83
+ buffer[it.offset++] = SWITCH_TO_STRUCTURE & 255;
84
+ encode.number(buffer, changeTree.refId, it);
78
85
  }
79
86
 
80
- const changesIterator = changes.entries();
87
+ for (let j = 0, numChanges = operations.operations.length; j < numChanges; j++) {
88
+ const fieldIndex = operations.operations[j];
89
+
90
+ const operation = (fieldIndex < 0)
91
+ ? Math.abs(fieldIndex) // "pure" operation without fieldIndex (e.g. CLEAR, REVERSE, etc.)
92
+ : (isEncodeAll)
93
+ ? OPERATION.ADD
94
+ : changeTree.indexedOperations[fieldIndex];
81
95
 
82
- for (const [fieldIndex, operation] of changesIterator) {
83
96
  //
84
97
  // first pass (encodeAll), identify "filtered" operations without encoding them
85
98
  // they will be encoded per client, based on their view.
@@ -87,116 +100,133 @@ export class Encoder<T extends Schema = any> {
87
100
  // TODO: how can we optimize filtering out "encode all" operations?
88
101
  // TODO: avoid checking if no view tags were defined
89
102
  //
90
- if (filter && !filter(ref, fieldIndex, view)) {
91
- // console.log("SKIP FIELD:", { ref: changeTree.ref.constructor.name, fieldIndex, })
92
-
103
+ if (fieldIndex === undefined || operation === undefined || (filter && !filter(ref, fieldIndex, view))) {
93
104
  // console.log("ADD AS INVISIBLE:", fieldIndex, changeTree.ref.constructor.name)
94
105
  // view?.invisible.add(changeTree);
95
106
  continue;
96
107
  }
97
108
 
98
- // console.log("WILL ENCODE", {
99
- // ref: changeTree.ref.constructor.name,
100
- // fieldIndex,
101
- // operation: OPERATION[operation],
102
- // });
103
-
104
- encoder(this, bytes, changeTree, fieldIndex, operation, it, isEncodeAll, hasView);
109
+ encoder(this, buffer, changeTree, fieldIndex, operation, it, isEncodeAll, hasView, metadata);
105
110
  }
111
+
112
+ // if (shouldDiscardChanges) {
113
+ // changeTree.discard();
114
+ // changeTree.isNew = false; // Not a new instance anymore
115
+ // }
106
116
  }
107
117
 
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);
118
+ if (it.offset > buffer.byteLength) {
119
+ const newSize = getNextPowerOf2(buffer.byteLength * 2);
120
+ console.warn(`@colyseus/schema buffer overflow. Encoded state is higher than default BUFFER_SIZE. Use the following to increase default BUFFER_SIZE:
121
+
122
+ import { Encoder } from "@colyseus/schema";
123
+ Encoder.BUFFER_SIZE = ${Math.round(newSize / 1024)} * 1024; // ${Math.round(newSize / 1024)} KB
124
+ `);
111
125
 
112
126
  //
113
127
  // resize buffer and re-encode (TODO: can we avoid re-encoding here?)
114
128
  //
115
- this.sharedBuffer = Buffer.allocUnsafeSlow(newSize);
116
- return this.encode({ offset: initialOffset }, view);
129
+ buffer = Buffer.alloc(newSize);
130
+
131
+ // assign resized buffer to local sharedBuffer
132
+ if (buffer === this.sharedBuffer) {
133
+ this.sharedBuffer = buffer;
134
+ }
135
+
136
+ return this.encode({ offset: initialOffset }, view, buffer, changeSetName, isEncodeAll);
117
137
 
118
138
  } else {
119
139
  //
120
140
  // only clear changes after making sure buffer resize is not required.
121
141
  //
122
- if (!isEncodeAll && !hasView) {
142
+ if (shouldDiscardChanges) {
123
143
  //
124
- // FIXME: avoid iterating over change trees twice.
144
+ // TODO: avoid iterating over change trees twice.
125
145
  //
126
- this.onEndEncode(changeTrees);
146
+ for (let i = 0, numChangeTrees = changeTrees.length; i < numChangeTrees; i++) {
147
+ const changeTree = changeTrees[i];
148
+ changeTree.discard();
149
+ changeTree.isNew = false; // Not a new instance anymore
150
+ }
127
151
  }
128
152
 
129
- // return bytes;
130
- return bytes.slice(0, it.offset);
153
+ return buffer.subarray(0, it.offset);
131
154
  }
132
155
  }
133
156
 
134
- encodeAll(it: Iterator = { offset: 0 }) {
135
- // console.log(`encodeAll(), this.$root.allChanges (${this.$root.allChanges.size})`);
136
-
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);
157
+ encodeAll(it: Iterator = { offset: 0 }, buffer: Buffer = this.sharedBuffer) {
158
+ return this.encode(it, undefined, buffer, "allChanges", true);
142
159
  }
143
160
 
144
161
  encodeAllView(view: StateView, sharedOffset: number, it: Iterator, bytes = this.sharedBuffer) {
145
162
  const viewOffset = it.offset;
146
163
 
147
- // console.log(`encodeAllView(), this.$root.allFilteredChanges (${this.$root.allFilteredChanges.size})`);
148
- // this.debugAllFilteredChanges();
149
-
150
164
  // try to encode "filtered" changes
151
- this.encode(it, view, bytes, this.root.allFilteredChanges);
165
+ this.encode(it, view, bytes, "allFilteredChanges", true, viewOffset);
152
166
 
153
167
  return Buffer.concat([
154
- bytes.slice(0, sharedOffset),
155
- bytes.slice(viewOffset, it.offset)
168
+ bytes.subarray(0, sharedOffset),
169
+ bytes.subarray(viewOffset, it.offset)
156
170
  ]);
157
171
  }
158
172
 
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
- // }
173
+ debugChanges(field: "changes" | "allFilteredChanges" | "allChanges" | "filteredChanges") {
174
+ const rootChangeSet = (typeof (field) === "string")
175
+ ? this.root[field]
176
+ : field;
177
+
178
+ rootChangeSet.forEach((changeTree) => {
179
+ const changeSet = changeTree[field];
180
+
181
+ const metadata: Metadata = changeTree.ref.constructor[Symbol.metadata];
182
+ console.log("->", { ref: changeTree.ref.constructor.name, refId: changeTree.refId, changes: Object.keys(changeSet).length });
183
+ for (const index in changeSet) {
184
+ const op = changeSet[index];
185
+ console.log(" ->", {
186
+ index,
187
+ field: metadata?.[index],
188
+ op: OPERATION[op],
189
+ });
190
+ }
191
+ });
192
+ }
170
193
 
171
194
  encodeView(view: StateView, sharedOffset: number, it: Iterator, bytes = this.sharedBuffer) {
172
195
  const viewOffset = it.offset;
173
196
 
174
- // try to encode "filtered" changes
175
- this.encode(it, view, bytes, this.root.filteredChanges);
176
-
177
197
  // 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);
198
+ const refIds = Object.keys(view.changes);
199
+
200
+ for (let i = 0, numRefIds = refIds.length; i < numRefIds; i++) {
201
+ const refId = refIds[i];
202
+ const changes = view.changes[refId];
203
+ const changeTree = this.root.changeTrees[refId];
204
+
205
+ if (
206
+ changeTree === undefined ||
207
+ Object.keys(changes).length === 0 // FIXME: avoid having empty changes if no changes were made
208
+ ) {
209
+ // console.log("changes.size === 0, skip", changeTree.ref.constructor.name);
183
210
  continue;
184
211
  }
185
212
 
186
213
  const ref = changeTree.ref;
187
214
 
188
- const ctor = ref['constructor'];
215
+ const ctor = ref.constructor;
189
216
  const encoder = ctor[$encoder];
217
+ const metadata = ctor[Symbol.metadata];
190
218
 
191
219
  bytes[it.offset++] = SWITCH_TO_STRUCTURE & 255;
192
220
  encode.number(bytes, changeTree.refId, it);
193
221
 
194
- const changesIterator = changes.entries();
222
+ const keys = Object.keys(changes);
223
+ for (let i = 0, numChanges = keys.length; i < numChanges; i++) {
224
+ const key = keys[i];
225
+ const operation = changes[key];
195
226
 
196
- for (const [fieldIndex, operation] of changesIterator) {
197
227
  // isEncodeAll = false
198
228
  // hasView = true
199
- encoder(this, bytes, changeTree, fieldIndex, operation, it, false, true);
229
+ encoder(this, bytes, changeTree, Number(key), operation, it, false, true, metadata);
200
230
  }
201
231
  }
202
232
 
@@ -205,31 +235,54 @@ export class Encoder<T extends Schema = any> {
205
235
  // (to allow re-using StateView's for multiple clients)
206
236
  //
207
237
  // clear "view" changes after encoding
208
- view.changes.clear();
238
+ view.changes = {};
239
+
240
+ // try to encode "filtered" changes
241
+ this.encode(it, view, bytes, "filteredChanges", false, viewOffset);
209
242
 
210
243
  return Buffer.concat([
211
- bytes.slice(0, sharedOffset),
212
- bytes.slice(viewOffset, it.offset)
244
+ bytes.subarray(0, sharedOffset),
245
+ bytes.subarray(viewOffset, it.offset)
213
246
  ]);
214
247
  }
215
248
 
216
249
  onEndEncode(changeTrees = this.root.changes) {
217
- const changeTreesIterator = changeTrees.entries();
218
- for (const [changeTree, _] of changeTreesIterator) {
219
- changeTree.endEncode();
220
- }
250
+ // changeTrees.forEach(function(changeTree) {
251
+ // changeTree.endEncode();
252
+ // });
253
+
254
+
255
+ // for (const refId in changeTrees) {
256
+ // const changeTree = this.root.changeTrees[refId];
257
+ // changeTree.endEncode();
258
+
259
+ // // changeTree.changes.clear();
260
+
261
+ // // // ArraySchema and MapSchema have a custom "encode end" method
262
+ // // changeTree.ref[$onEncodeEnd]?.();
263
+
264
+ // // // Not a new instance anymore
265
+ // // delete changeTree[$isNew];
266
+ // }
221
267
  }
222
268
 
223
269
  discardChanges() {
224
270
  // discard shared changes
225
- if (this.root.changes.size > 0) {
226
- this.onEndEncode(this.root.changes);
227
- this.root.changes.clear();
271
+ let length = this.root.changes.length;
272
+ if (length > 0) {
273
+ while (length--) {
274
+ this.root.changes[length]?.endEncode();
275
+ }
276
+ this.root.changes.length = 0;
228
277
  }
278
+
229
279
  // discard filtered changes
230
- if (this.root.filteredChanges.size > 0) {
231
- this.onEndEncode(this.root.filteredChanges);
232
- this.root.filteredChanges.clear();
280
+ length = this.root.filteredChanges.length;
281
+ if (length > 0) {
282
+ while (length--) {
283
+ this.root.filteredChanges[length]?.endEncode();
284
+ }
285
+ this.root.filteredChanges.length = 0;
233
286
  }
234
287
  }
235
288
 
@@ -237,9 +290,21 @@ export class Encoder<T extends Schema = any> {
237
290
  const baseTypeId = this.context.getTypeId(baseType);
238
291
  const targetTypeId = this.context.getTypeId(targetType);
239
292
 
293
+ if (targetTypeId === undefined) {
294
+ 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.`);
295
+ return;
296
+ }
297
+
240
298
  if (baseTypeId !== targetTypeId) {
241
299
  bytes[it.offset++] = TYPE_ID & 255;
242
300
  encode.number(bytes, targetTypeId, it);
243
301
  }
244
302
  }
245
- }
303
+
304
+ get hasChanges() {
305
+ return (
306
+ this.root.changes.length > 0 ||
307
+ this.root.filteredChanges.length > 0
308
+ );
309
+ }
310
+ }