@colyseus/schema 3.0.0-alpha.3 → 3.0.0-alpha.31

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 (134) hide show
  1. package/README.md +131 -61
  2. package/build/cjs/index.js +966 -563
  3. package/build/cjs/index.js.map +1 -1
  4. package/build/esm/index.mjs +965 -562
  5. package/build/esm/index.mjs.map +1 -1
  6. package/build/umd/index.js +966 -563
  7. package/lib/Metadata.d.ts +15 -4
  8. package/lib/Metadata.js +86 -18
  9. package/lib/Metadata.js.map +1 -1
  10. package/lib/Reflection.d.ts +2 -3
  11. package/lib/Reflection.js +24 -28
  12. package/lib/Reflection.js.map +1 -1
  13. package/lib/Schema.d.ts +2 -2
  14. package/lib/Schema.js +28 -41
  15. package/lib/Schema.js.map +1 -1
  16. package/lib/annotations.d.ts +1 -21
  17. package/lib/annotations.js +73 -153
  18. package/lib/annotations.js.map +1 -1
  19. package/lib/bench_encode.d.ts +1 -0
  20. package/lib/bench_encode.js +142 -0
  21. package/lib/bench_encode.js.map +1 -0
  22. package/lib/codegen/api.js +1 -2
  23. package/lib/codegen/api.js.map +1 -1
  24. package/lib/codegen/languages/cpp.js +1 -2
  25. package/lib/codegen/languages/cpp.js.map +1 -1
  26. package/lib/codegen/languages/csharp.js +1 -2
  27. package/lib/codegen/languages/csharp.js.map +1 -1
  28. package/lib/codegen/languages/haxe.js +1 -2
  29. package/lib/codegen/languages/haxe.js.map +1 -1
  30. package/lib/codegen/languages/java.js +1 -2
  31. package/lib/codegen/languages/java.js.map +1 -1
  32. package/lib/codegen/languages/js.js +1 -2
  33. package/lib/codegen/languages/js.js.map +1 -1
  34. package/lib/codegen/languages/lua.js +1 -2
  35. package/lib/codegen/languages/lua.js.map +1 -1
  36. package/lib/codegen/languages/ts.js +1 -2
  37. package/lib/codegen/languages/ts.js.map +1 -1
  38. package/lib/codegen/parser.js +2 -3
  39. package/lib/codegen/parser.js.map +1 -1
  40. package/lib/codegen/types.js +3 -3
  41. package/lib/codegen/types.js.map +1 -1
  42. package/lib/debug.d.ts +1 -0
  43. package/lib/debug.js +52 -0
  44. package/lib/debug.js.map +1 -0
  45. package/lib/decoder/DecodeOperation.d.ts +0 -1
  46. package/lib/decoder/DecodeOperation.js +23 -11
  47. package/lib/decoder/DecodeOperation.js.map +1 -1
  48. package/lib/decoder/Decoder.d.ts +6 -7
  49. package/lib/decoder/Decoder.js +8 -8
  50. package/lib/decoder/Decoder.js.map +1 -1
  51. package/lib/decoder/ReferenceTracker.js +3 -2
  52. package/lib/decoder/ReferenceTracker.js.map +1 -1
  53. package/lib/decoder/strategy/RawChanges.js +1 -2
  54. package/lib/decoder/strategy/RawChanges.js.map +1 -1
  55. package/lib/decoder/strategy/StateCallbacks.d.ts +44 -11
  56. package/lib/decoder/strategy/StateCallbacks.js +75 -65
  57. package/lib/decoder/strategy/StateCallbacks.js.map +1 -1
  58. package/lib/encoder/ChangeTree.d.ts +9 -19
  59. package/lib/encoder/ChangeTree.js +129 -145
  60. package/lib/encoder/ChangeTree.js.map +1 -1
  61. package/lib/encoder/EncodeOperation.d.ts +1 -5
  62. package/lib/encoder/EncodeOperation.js +74 -58
  63. package/lib/encoder/EncodeOperation.js.map +1 -1
  64. package/lib/encoder/Encoder.d.ts +10 -8
  65. package/lib/encoder/Encoder.js +89 -56
  66. package/lib/encoder/Encoder.js.map +1 -1
  67. package/lib/encoder/Root.d.ts +17 -0
  68. package/lib/encoder/Root.js +44 -0
  69. package/lib/encoder/Root.js.map +1 -0
  70. package/lib/encoder/StateView.d.ts +2 -2
  71. package/lib/encoder/StateView.js +49 -59
  72. package/lib/encoder/StateView.js.map +1 -1
  73. package/lib/encoding/assert.d.ts +2 -1
  74. package/lib/encoding/assert.js +5 -5
  75. package/lib/encoding/assert.js.map +1 -1
  76. package/lib/encoding/decode.js +21 -22
  77. package/lib/encoding/decode.js.map +1 -1
  78. package/lib/encoding/encode.d.ts +2 -2
  79. package/lib/encoding/encode.js +40 -39
  80. package/lib/encoding/encode.js.map +1 -1
  81. package/lib/encoding/spec.d.ts +2 -1
  82. package/lib/encoding/spec.js +1 -0
  83. package/lib/encoding/spec.js.map +1 -1
  84. package/lib/index.d.ts +6 -3
  85. package/lib/index.js +18 -13
  86. package/lib/index.js.map +1 -1
  87. package/lib/types/TypeContext.d.ts +23 -0
  88. package/lib/types/TypeContext.js +102 -0
  89. package/lib/types/TypeContext.js.map +1 -0
  90. package/lib/types/custom/ArraySchema.d.ts +2 -2
  91. package/lib/types/custom/ArraySchema.js +6 -9
  92. package/lib/types/custom/ArraySchema.js.map +1 -1
  93. package/lib/types/custom/CollectionSchema.js +1 -0
  94. package/lib/types/custom/CollectionSchema.js.map +1 -1
  95. package/lib/types/custom/MapSchema.js +5 -0
  96. package/lib/types/custom/MapSchema.js.map +1 -1
  97. package/lib/types/custom/SetSchema.js +1 -0
  98. package/lib/types/custom/SetSchema.js.map +1 -1
  99. package/lib/types/registry.js +3 -4
  100. package/lib/types/registry.js.map +1 -1
  101. package/lib/types/symbols.d.ts +1 -0
  102. package/lib/types/symbols.js +2 -1
  103. package/lib/types/symbols.js.map +1 -1
  104. package/lib/types/utils.js +1 -2
  105. package/lib/types/utils.js.map +1 -1
  106. package/lib/utils.js +3 -4
  107. package/lib/utils.js.map +1 -1
  108. package/package.json +5 -5
  109. package/src/Metadata.ts +104 -26
  110. package/src/Reflection.ts +26 -28
  111. package/src/Schema.ts +35 -47
  112. package/src/annotations.ts +82 -176
  113. package/src/bench_encode.ts +121 -0
  114. package/src/debug.ts +56 -0
  115. package/src/decoder/DecodeOperation.ts +28 -11
  116. package/src/decoder/Decoder.ts +13 -11
  117. package/src/decoder/ReferenceTracker.ts +3 -2
  118. package/src/decoder/strategy/StateCallbacks.ts +152 -81
  119. package/src/encoder/ChangeTree.ts +147 -166
  120. package/src/encoder/EncodeOperation.ts +93 -70
  121. package/src/encoder/Encoder.ts +111 -65
  122. package/src/encoder/Root.ts +51 -0
  123. package/src/encoder/StateView.ts +53 -69
  124. package/src/encoding/assert.ts +4 -3
  125. package/src/encoding/decode.ts +1 -2
  126. package/src/encoding/encode.ts +25 -22
  127. package/src/encoding/spec.ts +1 -0
  128. package/src/index.ts +8 -14
  129. package/src/types/TypeContext.ts +122 -0
  130. package/src/types/custom/ArraySchema.ts +10 -2
  131. package/src/types/custom/CollectionSchema.ts +1 -0
  132. package/src/types/custom/MapSchema.ts +6 -0
  133. package/src/types/custom/SetSchema.ts +1 -0
  134. package/src/types/symbols.ts +2 -0
@@ -3,7 +3,7 @@ import { $changes } from "../types/symbols";
3
3
  import { getType } from "../types/registry";
4
4
 
5
5
  import * as encode from "../encoding/encode";
6
- import { EncodeSchemaError, assertInstanceType, assertType } from "../encoding/assert";
6
+ // import { EncodeSchemaError, assertInstanceType, assertType } from "../encoding/assert";
7
7
 
8
8
  import type { ChangeTree, Ref } from "./ChangeTree";
9
9
  import type { Encoder } from "./Encoder";
@@ -12,6 +12,7 @@ import type { PrimitiveType } from "../annotations";
12
12
 
13
13
  import type { Iterator } from "../encoding/decode";
14
14
  import type { ArraySchema } from "../types/custom/ArraySchema";
15
+ import type { Metadata } from "../Metadata";
15
16
 
16
17
  export type EncodeOperation<T extends Ref = any> = (
17
18
  encoder: Encoder,
@@ -24,40 +25,27 @@ export type EncodeOperation<T extends Ref = any> = (
24
25
  hasView: boolean,
25
26
  ) => void;
26
27
 
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
28
  export function encodeValue(
49
29
  encoder: Encoder,
50
30
  bytes: Buffer,
51
- ref: Ref,
31
+ // ref: Ref,
52
32
  type: any,
53
33
  value: any,
54
- field: string | number,
34
+ // field: string | number,
55
35
  operation: OPERATION,
56
36
  it: Iterator,
57
37
  ) {
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);
38
+ if (typeof (type) === "string") {
39
+ //
40
+ // Primitive values
41
+ //
42
+ // assertType(value, type as string, ref as Schema, field);
43
+
44
+ encode[type]?.(bytes, value, it);
45
+
46
+ } else if (type[Symbol.metadata] !== undefined) {
47
+ // // TODO: move this to the `@type()` annotation
48
+ // assertInstanceType(value, type as typeof Schema, ref as Schema, field);
61
49
 
62
50
  //
63
51
  // Encode refId for this instance.
@@ -70,22 +58,16 @@ export function encodeValue(
70
58
  encoder.tryEncodeTypeId(bytes, type as typeof Schema, value.constructor as typeof Schema, it);
71
59
  }
72
60
 
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
61
  } else {
80
- //
81
- // Custom type (MapSchema, ArraySchema, etc)
82
- //
83
- const definition = getType(Object.keys(type)[0]);
62
+ // //
63
+ // // Custom type (MapSchema, ArraySchema, etc)
64
+ // //
65
+ // const definition = getType(Object.keys(type)[0]);
84
66
 
85
- //
86
- // ensure a ArraySchema has been provided
87
- //
88
- assertInstanceType(ref[field], definition.constructor, ref as Schema, field);
67
+ // //
68
+ // // ensure a ArraySchema has been provided
69
+ // //
70
+ // assertInstanceType(ref[field], definition.constructor, ref as Schema, field);
89
71
 
90
72
  //
91
73
  // Encode refId for this instance.
@@ -107,13 +89,6 @@ export const encodeSchemaOperation: EncodeOperation = function (
107
89
  operation: OPERATION,
108
90
  it: Iterator,
109
91
  ) {
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
92
  // "compress" field index + operation
118
93
  bytes[it.offset++] = (index | operation) & 255;
119
94
 
@@ -122,8 +97,21 @@ export const encodeSchemaOperation: EncodeOperation = function (
122
97
  return;
123
98
  }
124
99
 
100
+ const ref = changeTree.ref;
101
+ const metadata: Metadata = ref['constructor'][Symbol.metadata];
102
+ const field = metadata[index];
103
+
125
104
  // TODO: inline this function call small performance gain
126
- encodeValue(encoder, bytes, ref, type, value, field, operation, it);
105
+ encodeValue(
106
+ encoder,
107
+ bytes,
108
+ // ref,
109
+ metadata[index].type,
110
+ ref[field.name],
111
+ // index,
112
+ operation,
113
+ it
114
+ );
127
115
  }
128
116
 
129
117
  /**
@@ -134,12 +122,10 @@ export const encodeKeyValueOperation: EncodeOperation = function (
134
122
  encoder: Encoder,
135
123
  bytes: Buffer,
136
124
  changeTree: ChangeTree,
137
- field: number,
125
+ index: number,
138
126
  operation: OPERATION,
139
127
  it: Iterator,
140
128
  ) {
141
- const ref = changeTree.ref;
142
-
143
129
  // encode operation
144
130
  bytes[it.offset++] = operation & 255;
145
131
 
@@ -149,13 +135,15 @@ export const encodeKeyValueOperation: EncodeOperation = function (
149
135
  }
150
136
 
151
137
  // encode index
152
- encode.number(bytes, field, it);
138
+ encode.number(bytes, index, it);
153
139
 
154
140
  // Do not encode value for DELETE operations
155
141
  if (operation === OPERATION.DELETE) {
156
142
  return;
157
143
  }
158
144
 
145
+ const ref = changeTree.ref;
146
+
159
147
  //
160
148
  // encode "alias" for dynamic fields (maps)
161
149
  //
@@ -164,16 +152,38 @@ export const encodeKeyValueOperation: EncodeOperation = function (
164
152
  //
165
153
  // MapSchema dynamic key
166
154
  //
167
- const dynamicIndex = changeTree.ref['$indexes'].get(field);
155
+ const dynamicIndex = changeTree.ref['$indexes'].get(index);
168
156
  encode.string(bytes, dynamicIndex, it);
169
157
  }
170
158
  }
171
159
 
172
- const type = changeTree.getType(field);
173
- const value = changeTree.getValue(field);
160
+ const type = changeTree.getType(index);
161
+ const value = changeTree.getValue(index);
162
+
163
+ // try { throw new Error(); } catch (e) {
164
+ // // only print if not coming from Reflection.ts
165
+ // if (!e.stack.includes("src/Reflection.ts")) {
166
+ // console.log("encodeKeyValueOperation -> ", {
167
+ // ref: changeTree.ref.constructor.name,
168
+ // field,
169
+ // operation: OPERATION[operation],
170
+ // value: value?.toJSON(),
171
+ // items: ref.toJSON(),
172
+ // });
173
+ // }
174
+ // }
174
175
 
175
176
  // TODO: inline this function call small performance gain
176
- encodeValue(encoder, bytes, ref, type, value, field, operation, it);
177
+ encodeValue(
178
+ encoder,
179
+ bytes,
180
+ // ref,
181
+ type,
182
+ value,
183
+ // index,
184
+ operation,
185
+ it
186
+ );
177
187
  }
178
188
 
179
189
  /**
@@ -191,18 +201,22 @@ export const encodeArray: EncodeOperation = function (
191
201
  hasView: boolean,
192
202
  ) {
193
203
  const ref = changeTree.ref;
204
+ const useOperationByRefId = hasView && changeTree.isFiltered && (typeof (changeTree.getType(field)) !== "string");
194
205
 
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;
206
+ let refOrIndex: number;
207
+
208
+ if (useOperationByRefId) {
209
+ refOrIndex = ref['tmpItems'][field][$changes].refId;
210
+
211
+ if (operation === OPERATION.DELETE) {
212
+ operation = OPERATION.DELETE_BY_REFID;
213
+
214
+ } else if (operation === OPERATION.ADD) {
215
+ operation = OPERATION.ADD_BY_REFID;
216
+ }
217
+
218
+ } else {
219
+ refOrIndex = field;
206
220
  }
207
221
 
208
222
  // encode operation
@@ -214,7 +228,7 @@ export const encodeArray: EncodeOperation = function (
214
228
  }
215
229
 
216
230
  // encode index
217
- encode.number(bytes, field, it);
231
+ encode.number(bytes, refOrIndex, it);
218
232
 
219
233
  // Do not encode value for DELETE operations
220
234
  if (operation === OPERATION.DELETE) {
@@ -233,5 +247,14 @@ export const encodeArray: EncodeOperation = function (
233
247
  // });
234
248
 
235
249
  // TODO: inline this function call small performance gain
236
- encodeValue(encoder, bytes, ref, type, value, field, operation, it);
250
+ encodeValue(
251
+ encoder,
252
+ bytes,
253
+ // ref,
254
+ type,
255
+ value,
256
+ // field,
257
+ operation,
258
+ it
259
+ );
237
260
  }
@@ -1,14 +1,17 @@
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, $isNew, $onEncodeEnd } from "../types/symbols";
4
4
 
5
5
  import * as 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";
14
+ import type { ChangeTree } from "./ChangeTree";
12
15
 
13
16
  export class Encoder<T extends Schema = any> {
14
17
  static BUFFER_SIZE = 8 * 1024;// 8KB
@@ -19,14 +22,16 @@ export class Encoder<T extends Schema = any> {
19
22
 
20
23
  root: Root;
21
24
 
22
- constructor(root: T) {
23
- this.setRoot(root);
25
+ constructor(state: T) {
24
26
 
25
27
  //
26
28
  // TODO: cache and restore "Context" based on root schema
27
29
  // (to avoid creating a new context for every new room)
28
30
  //
29
- this.context = new TypeContext(root.constructor as typeof Schema);
31
+ this.context = new TypeContext(state.constructor as typeof Schema);
32
+ this.root = new Root(this.context);
33
+
34
+ this.setState(state);
30
35
 
31
36
  // console.log(">>>>>>>>>>>>>>>> Encoder types");
32
37
  // this.context.schemas.forEach((id, schema) => {
@@ -34,33 +39,38 @@ export class Encoder<T extends Schema = any> {
34
39
  // });
35
40
  }
36
41
 
37
- protected setRoot(state: T) {
38
- this.root = new Root();
42
+ protected setState(state: T) {
39
43
  this.state = state;
40
- state[$changes].setRoot(this.root);
44
+ this.state[$changes].setRoot(this.root);
41
45
  }
42
46
 
43
47
  encode(
44
48
  it: Iterator = { offset: 0 },
45
49
  view?: StateView,
46
- bytes = this.sharedBuffer,
47
- changeTrees = this.root.changes
50
+ buffer = this.sharedBuffer,
51
+ changeTrees = this.root.changes,
52
+ isEncodeAll = this.root.allChanges === changeTrees,
53
+ initialOffset = it.offset // cache current offset in case we need to resize the buffer
48
54
  ): 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
55
  const hasView = (view !== undefined);
53
56
  const rootChangeTree = this.state[$changes];
54
57
 
55
- const changeTreesIterator = changeTrees.entries();
58
+ const shouldClearChanges = !isEncodeAll && !hasView;
56
59
 
57
- for (const [changeTree, changes] of changeTreesIterator) {
60
+ for (const [changeTree, changes] of changeTrees.entries()) {
58
61
  const ref = changeTree.ref;
59
62
 
60
63
  const ctor = ref['constructor'];
61
64
  const encoder = ctor[$encoder];
62
65
  const filter = ctor[$filter];
63
66
 
67
+ // try { throw new Error(); } catch (e) {
68
+ // // only print if not coming from Reflection.ts
69
+ // if (!e.stack.includes("src/Reflection.ts")) {
70
+ // console.log("ChangeTree:", { ref: ref.constructor.name, });
71
+ // }
72
+ // }
73
+
64
74
  if (hasView) {
65
75
  if (!view.items.has(changeTree)) {
66
76
  view.invisible.add(changeTree);
@@ -72,9 +82,10 @@ export class Encoder<T extends Schema = any> {
72
82
  }
73
83
 
74
84
  // 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);
85
+ // (unless it "hasView", which will need to revisit the root)
86
+ if (hasView || it.offset > initialOffset || changeTree !== rootChangeTree) {
87
+ buffer[it.offset++] = SWITCH_TO_STRUCTURE & 255;
88
+ encode.number(buffer, changeTree.refId, it);
78
89
  }
79
90
 
80
91
  const changesIterator = changes.entries();
@@ -88,91 +99,115 @@ export class Encoder<T extends Schema = any> {
88
99
  // TODO: avoid checking if no view tags were defined
89
100
  //
90
101
  if (filter && !filter(ref, fieldIndex, view)) {
91
- // console.log("SKIP FIELD:", { ref: changeTree.ref.constructor.name, fieldIndex, })
92
-
93
102
  // console.log("ADD AS INVISIBLE:", fieldIndex, changeTree.ref.constructor.name)
94
103
  // view?.invisible.add(changeTree);
95
104
  continue;
96
105
  }
97
106
 
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);
107
+ // try { throw new Error(); } catch (e) {
108
+ // // only print if not coming from Reflection.ts
109
+ // if (!e.stack.includes("src/Reflection.ts")) {
110
+ // console.log("WILL ENCODE", {
111
+ // ref: changeTree.ref.constructor.name,
112
+ // fieldIndex,
113
+ // operation: OPERATION[operation],
114
+ // });
115
+ // }
116
+ // }
117
+
118
+ encoder(this, buffer, changeTree, fieldIndex, operation, it, isEncodeAll, hasView);
105
119
  }
120
+
121
+ // if (shouldClearChanges) {
122
+ // changeTree.endEncode();
123
+ // }
106
124
  }
107
125
 
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);
126
+ if (it.offset > buffer.byteLength) {
127
+ const newSize = getNextPowerOf2(buffer.byteLength * 2);
128
+ console.warn(`@colyseus/schema buffer overflow. Encoded state is higher than default BUFFER_SIZE. Use the following to increase default BUFFER_SIZE:
129
+
130
+ import { Encoder } from "@colyseus/schema";
131
+ Encoder.BUFFER_SIZE = ${Math.round(newSize / 1024)} * 1024; // ${Math.round(newSize / 1024)} KB
132
+ `);
111
133
 
112
134
  //
113
135
  // resize buffer and re-encode (TODO: can we avoid re-encoding here?)
114
136
  //
115
- this.sharedBuffer = Buffer.allocUnsafeSlow(newSize);
116
- return this.encode({ offset: initialOffset }, view);
137
+ buffer = Buffer.allocUnsafeSlow(newSize);
138
+
139
+ // assign resized buffer to local sharedBuffer
140
+ if (buffer === this.sharedBuffer) {
141
+ this.sharedBuffer = buffer;
142
+ }
143
+
144
+ return this.encode({ offset: initialOffset }, view, buffer, changeTrees, isEncodeAll);
117
145
 
118
146
  } else {
119
147
  //
120
148
  // only clear changes after making sure buffer resize is not required.
121
149
  //
122
- if (!isEncodeAll && !hasView) {
150
+ if (shouldClearChanges) {
123
151
  //
124
152
  // FIXME: avoid iterating over change trees twice.
125
153
  //
126
154
  this.onEndEncode(changeTrees);
127
155
  }
128
156
 
129
- // return bytes;
130
- return bytes.slice(0, it.offset);
157
+ return buffer.subarray(0, it.offset);
131
158
  }
132
159
  }
133
160
 
134
- encodeAll(it: Iterator = { offset: 0 }) {
135
- // console.log(`encodeAll(), this.$root.allChanges (${this.$root.allChanges.size})`);
161
+ encodeAll(it: Iterator = { offset: 0 }, buffer: Buffer = this.sharedBuffer) {
162
+ // console.log(`\nencodeAll(), this.root.allChanges (${this.root.allChanges.size})`);
163
+ // this.debugChanges("allChanges");
136
164
 
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);
165
+ return this.encode(it, undefined, buffer, this.root.allChanges, true);
142
166
  }
143
167
 
144
168
  encodeAllView(view: StateView, sharedOffset: number, it: Iterator, bytes = this.sharedBuffer) {
145
169
  const viewOffset = it.offset;
146
170
 
147
- // console.log(`encodeAllView(), this.$root.allFilteredChanges (${this.$root.allFilteredChanges.size})`);
148
- // this.debugAllFilteredChanges();
171
+ // console.log(`\nencodeAllView(), this.root.allFilteredChanges (${this.root.allFilteredChanges.size})`);
172
+ // this.debugChanges("allFilteredChanges");
149
173
 
150
174
  // try to encode "filtered" changes
151
- this.encode(it, view, bytes, this.root.allFilteredChanges);
175
+ this.encode(it, view, bytes, this.root.allFilteredChanges, true, viewOffset);
152
176
 
153
177
  return Buffer.concat([
154
- bytes.slice(0, sharedOffset),
155
- bytes.slice(viewOffset, it.offset)
178
+ bytes.subarray(0, sharedOffset),
179
+ bytes.subarray(viewOffset, it.offset)
156
180
  ]);
157
181
  }
158
182
 
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
- // }
183
+ debugChanges(
184
+ field: "changes" | "allFilteredChanges" | "allChanges" | "filteredChanges" | Map<ChangeTree, Map<number, OPERATION>>
185
+ ) {
186
+ const changeSet = (typeof (field) === "string")
187
+ ? this.root[field]
188
+ : field;
189
+
190
+ Array.from(changeSet.entries()).map((item) => {
191
+ const metadata: Metadata = item[0].ref.constructor[Symbol.metadata];
192
+ console.log("->", { ref: item[0].ref.constructor.name, refId: item[0].refId, changes: item[1].size });
193
+ item[1].forEach((op, index) => {
194
+ console.log(" ->", {
195
+ index,
196
+ field: metadata?.[index],
197
+ op: OPERATION[op],
198
+ });
199
+ });
200
+ });
201
+ }
170
202
 
171
203
  encodeView(view: StateView, sharedOffset: number, it: Iterator, bytes = this.sharedBuffer) {
172
204
  const viewOffset = it.offset;
173
205
 
174
- // try to encode "filtered" changes
175
- this.encode(it, view, bytes, this.root.filteredChanges);
206
+ // console.log(`\nencodeView(), view.changes (${view.changes.size})`);
207
+ // this.debugChanges(view.changes);
208
+
209
+ // console.log(`\nencodeView(), this.root.filteredChanges (${this.root.filteredChanges.size})`);
210
+ // this.debugChanges("filteredChanges");
176
211
 
177
212
  // encode visibility changes (add/remove for this view)
178
213
  const viewChangesIterator = view.changes.entries();
@@ -207,9 +242,12 @@ export class Encoder<T extends Schema = any> {
207
242
  // clear "view" changes after encoding
208
243
  view.changes.clear();
209
244
 
245
+ // try to encode "filtered" changes
246
+ this.encode(it, view, bytes, this.root.filteredChanges, false, viewOffset);
247
+
210
248
  return Buffer.concat([
211
- bytes.slice(0, sharedOffset),
212
- bytes.slice(viewOffset, it.offset)
249
+ bytes.subarray(0, sharedOffset),
250
+ bytes.subarray(viewOffset, it.offset)
213
251
  ]);
214
252
  }
215
253
 
@@ -217,6 +255,14 @@ export class Encoder<T extends Schema = any> {
217
255
  const changeTreesIterator = changeTrees.entries();
218
256
  for (const [changeTree, _] of changeTreesIterator) {
219
257
  changeTree.endEncode();
258
+ // changeTree.changes.clear();
259
+
260
+ // // ArraySchema and MapSchema have a custom "encode end" method
261
+ // changeTree.ref[$onEncodeEnd]?.();
262
+
263
+ // // Not a new instance anymore
264
+ // delete changeTree[$isNew];
265
+
220
266
  }
221
267
  }
222
268
 
@@ -242,4 +288,4 @@ export class Encoder<T extends Schema = any> {
242
288
  encode.number(bytes, targetTypeId, it);
243
289
  }
244
290
  }
245
- }
291
+ }
@@ -0,0 +1,51 @@
1
+ import { OPERATION } from "../encoding/spec";
2
+ import { TypeContext } from "../types/TypeContext";
3
+ import { ChangeTree } from "./ChangeTree";
4
+
5
+ export class Root {
6
+ protected nextUniqueId: number = 0;
7
+ refCount = new WeakMap<ChangeTree, number>();
8
+
9
+ // all changes
10
+ allChanges = new Map<ChangeTree, Map<number, OPERATION>>();
11
+ allFilteredChanges = new Map<ChangeTree, Map<number, OPERATION>>();
12
+
13
+ // pending changes to be encoded
14
+ changes = new Map<ChangeTree, Map<number, OPERATION>>();
15
+ filteredChanges = new Map<ChangeTree, Map<number, OPERATION>>();
16
+
17
+ constructor(public types: TypeContext) { }
18
+
19
+ getNextUniqueId() {
20
+ return this.nextUniqueId++;
21
+ }
22
+
23
+ add(changeTree: ChangeTree) {
24
+ const refCount = this.refCount.get(changeTree) || 0;
25
+ this.refCount.set(changeTree, refCount + 1);
26
+ }
27
+
28
+ remove(changeTree: ChangeTree) {
29
+ const refCount = this.refCount.get(changeTree);
30
+ if (refCount <= 1) {
31
+ this.allChanges.delete(changeTree);
32
+ this.changes.delete(changeTree);
33
+
34
+ if (changeTree.isFiltered || changeTree.isPartiallyFiltered) {
35
+ this.allFilteredChanges.delete(changeTree);
36
+ this.filteredChanges.delete(changeTree);
37
+ }
38
+
39
+ this.refCount.delete(changeTree);
40
+
41
+ } else {
42
+ this.refCount.set(changeTree, refCount - 1);
43
+ }
44
+
45
+ changeTree.forEachChild((child, _) => this.remove(child));
46
+ }
47
+
48
+ clear() {
49
+ this.changes.clear();
50
+ }
51
+ }