@colyseus/schema 3.0.0-alpha.9 → 3.0.1

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