@colyseus/schema 3.0.0-alpha.9 → 3.0.0

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 (150) 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/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 +9 -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 +35 -17
  48. package/lib/decoder/DecodeOperation.js.map +1 -1
  49. package/lib/decoder/Decoder.d.ts +5 -6
  50. package/lib/decoder/Decoder.js +10 -10
  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 +74 -64
  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 +8 -7
  66. package/lib/encoder/Encoder.js +128 -79
  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 -18
  81. package/lib/encoding/encode.js +61 -48
  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.d.ts +2 -2
  98. package/lib/types/custom/CollectionSchema.js +1 -0
  99. package/lib/types/custom/CollectionSchema.js.map +1 -1
  100. package/lib/types/custom/MapSchema.d.ts +18 -16
  101. package/lib/types/custom/MapSchema.js +12 -4
  102. package/lib/types/custom/MapSchema.js.map +1 -1
  103. package/lib/types/custom/SetSchema.d.ts +2 -2
  104. package/lib/types/custom/SetSchema.js +1 -0
  105. package/lib/types/custom/SetSchema.js.map +1 -1
  106. package/lib/types/registry.d.ts +8 -1
  107. package/lib/types/registry.js +23 -6
  108. package/lib/types/registry.js.map +1 -1
  109. package/lib/types/symbols.d.ts +8 -5
  110. package/lib/types/symbols.js +9 -6
  111. package/lib/types/symbols.js.map +1 -1
  112. package/lib/types/utils.js +1 -2
  113. package/lib/types/utils.js.map +1 -1
  114. package/lib/utils.js +9 -7
  115. package/lib/utils.js.map +1 -1
  116. package/package.json +19 -18
  117. package/src/Metadata.ts +190 -42
  118. package/src/Reflection.ts +76 -38
  119. package/src/Schema.ts +72 -70
  120. package/src/annotations.ts +156 -202
  121. package/src/bench_encode.ts +108 -0
  122. package/src/codegen/languages/csharp.ts +8 -47
  123. package/src/codegen/languages/haxe.ts +4 -0
  124. package/src/codegen/languages/lua.ts +19 -27
  125. package/src/codegen/parser.ts +107 -0
  126. package/src/codegen/types.ts +1 -0
  127. package/src/debug.ts +55 -0
  128. package/src/decoder/DecodeOperation.ts +43 -15
  129. package/src/decoder/Decoder.ts +12 -10
  130. package/src/decoder/ReferenceTracker.ts +5 -3
  131. package/src/decoder/strategy/StateCallbacks.ts +152 -81
  132. package/src/encoder/ChangeTree.ts +282 -209
  133. package/src/encoder/EncodeOperation.ts +78 -78
  134. package/src/encoder/Encoder.ts +152 -87
  135. package/src/encoder/Root.ts +93 -0
  136. package/src/encoder/StateView.ts +80 -88
  137. package/src/encoding/assert.ts +17 -8
  138. package/src/encoding/decode.ts +73 -93
  139. package/src/encoding/encode.ts +76 -45
  140. package/src/encoding/spec.ts +3 -5
  141. package/src/index.ts +12 -20
  142. package/src/types/HelperTypes.ts +54 -2
  143. package/src/types/TypeContext.ts +175 -0
  144. package/src/types/custom/ArraySchema.ts +49 -19
  145. package/src/types/custom/CollectionSchema.ts +1 -0
  146. package/src/types/custom/MapSchema.ts +30 -17
  147. package/src/types/custom/SetSchema.ts +1 -0
  148. package/src/types/registry.ts +22 -3
  149. package/src/types/symbols.ts +10 -7
  150. package/src/utils.ts +7 -3
@@ -59,16 +59,16 @@ function generateClass(klass: Class, namespace: string) {
59
59
  return `${getCommentHeader()}
60
60
 
61
61
  using Colyseus.Schema;
62
- using Action = System.Action;
62
+ #if UNITY_5_3_OR_NEWER
63
+ using UnityEngine.Scripting;
64
+ #endif
63
65
  ${namespace ? `\nnamespace ${namespace} {` : ""}
64
66
  ${indent}public partial class ${klass.name} : ${klass.extends} {
67
+ #if UNITY_5_3_OR_NEWER
68
+ [Preserve]
69
+ #endif
70
+ public ${klass.name}() { }
65
71
  ${klass.properties.map((prop) => generateProperty(prop, indent)).join("\n\n")}
66
-
67
- ${indent}\t/*
68
- ${indent}\t * Support for individual property change callbacks below...
69
- ${indent}\t */
70
-
71
- ${generateAllFieldCallbacks(klass, indent)}
72
72
  ${indent}}
73
73
  ${namespace ? "}" : ""}
74
74
  `;
@@ -119,7 +119,7 @@ function generateProperty(prop: Property, indent: string = "") {
119
119
  typeArgs += `, "${prop.childType}"`;
120
120
  }
121
121
 
122
- initializer = `new ${langType}()`;
122
+ initializer = `null`;
123
123
 
124
124
  } else {
125
125
  langType = getType(prop);
@@ -147,45 +147,6 @@ ${namespace ? "}" : ""}
147
147
  `;
148
148
  }
149
149
 
150
- function generateAllFieldCallbacks(klass: Class, indent: string) {
151
- //
152
- // TODO: improve me. It would be great to generate less boilerplate in favor
153
- // of a single implementation on C# Schema class itself.
154
- //
155
- const eventNames: string[] = [];
156
- return `${klass.properties
157
- .filter(prop => !prop.deprecated) // generate only for properties that haven't been deprecated.
158
- .map(prop => {
159
- const eventName = `__${prop.name}Change`;
160
- eventNames.push(eventName);
161
-
162
- const defaultNull = (prop.childType)
163
- ? "null"
164
- : `default(${getType(prop)})`;
165
-
166
- return `\t${indent}protected event PropertyChangeHandler<${getType(prop)}> ${eventName};
167
- \t${indent}public Action On${capitalize(prop.name)}Change(PropertyChangeHandler<${getType(prop)}> __handler, bool __immediate = true) {
168
- \t${indent}\tif (__callbacks == null) { __callbacks = new SchemaCallbacks(); }
169
- \t${indent}\t__callbacks.AddPropertyCallback(nameof(this.${prop.name}));
170
- \t${indent}\t${eventName} += __handler;
171
- \t${indent}\tif (__immediate && this.${prop.name} != ${defaultNull}) { __handler(this.${prop.name}, ${defaultNull}); }
172
- \t${indent}\treturn () => {
173
- \t${indent}\t\t__callbacks.RemovePropertyCallback(nameof(${prop.name}));
174
- \t${indent}\t\t${eventName} -= __handler;
175
- \t${indent}\t};
176
- \t${indent}}`;
177
- }).join("\n\n")}
178
-
179
- \t${indent}protected override void TriggerFieldChange(DataChange change) {
180
- \t${indent}\tswitch (change.Field) {
181
- ${klass.properties.filter(prop => !prop.deprecated).map((prop, i) => {
182
- return `\t${indent}\t\tcase nameof(${prop.name}): ${eventNames[i]}?.Invoke((${getType(prop)}) change.Value, (${getType(prop)}) change.PreviousValue); break;`;
183
- }).join("\n")}
184
- \t${indent}\t\tdefault: break;
185
- \t\t${indent}}
186
- \t${indent}}`;
187
- }
188
-
189
150
  function getChildType(prop: Property) {
190
151
  return typeMaps[prop.childType];
191
152
  }
@@ -106,5 +106,9 @@ function generateProperty(prop: Property) {
106
106
  initializer = typeInitializer[prop.type];
107
107
  }
108
108
 
109
+ // TODO: remove initializer. The callbacks at the Haxe decoder side have a
110
+ // "FIXME" comment about this on Decoder.hx
111
+
109
112
  return `\t@:type(${typeArgs})\n\tpublic var ${prop.name}: ${langType} = ${initializer};\n`
113
+ // return `\t@:type(${typeArgs})\n\tpublic var ${prop.name}: ${langType};\n`
110
114
  }
@@ -43,11 +43,14 @@ function generateClass(klass: Class, namespace: string, allClasses: Class[]) {
43
43
  }
44
44
  });
45
45
 
46
- // TOOD: inheritance
46
+ // Inheritance support
47
+ const inherits = (klass.extends !== "Schema")
48
+ ? `, ${klass.extends}`
49
+ : "";
47
50
 
48
51
  return `${getCommentHeader().replace(/\/\//mg, "--")}
49
52
 
50
- local schema = require 'colyseus.serialization.schema.schema'
53
+ local schema = require 'colyseus.serializer.schema.schema'
51
54
  ${allRefs.
52
55
  filter(ref => ref.childType && typeMaps[ref.childType] === undefined).
53
56
  map(ref => ref.childType).
@@ -56,25 +59,15 @@ ${allRefs.
56
59
  map(childType => `local ${childType} = require '${(namespace ? `${namespace}.` : '')}${childType}'`).
57
60
  join("\n")}
58
61
 
62
+ ---@class ${klass.name}: ${klass.extends}
63
+ ${klass.properties.map(prop => `---@field ${prop.name} ${getLUATypeAnnotation(prop)}`).join("\n")}
59
64
  local ${klass.name} = schema.define({
60
65
  ${klass.properties.map(prop => generatePropertyDeclaration(prop)).join(",\n")},
61
66
  ["_fields_by_index"] = { ${klass.properties.map(prop => `"${prop.name}"`).join(", ")} },
62
- })
67
+ }${inherits})
63
68
 
64
69
  return ${klass.name}
65
70
  `;
66
-
67
- // ["on_change"] = function(changes)
68
- // -- on change logic here
69
- // end,
70
-
71
- // ["on_add"] = function()
72
- // -- on add logic here
73
- // end,
74
-
75
- // ["on_remove"] = function()
76
- // -- on remove logic here
77
- // end,
78
71
  }
79
72
 
80
73
  function generatePropertyDeclaration(prop: Property) {
@@ -108,18 +101,17 @@ function generatePropertyDeclaration(prop: Property) {
108
101
  return ` ["${prop.name}"] = ${typeArgs}`;
109
102
  }
110
103
 
111
- // function generatePropertyInitializer(prop: Property) {
112
- // let initializer = "";
104
+ function getLUATypeAnnotation(prop: Property) {
105
+ if (prop.type === "ref") {
106
+ return prop.childType;
113
107
 
114
- // if(prop.type === "ref") {
115
- // initializer = `new ${prop.childType}()`;
108
+ } else if (prop.type === "array") {
109
+ return "ArraySchema";
116
110
 
117
- // } else if(prop.type === "array") {
118
- // initializer = `new schema.ArraySchema()`;
111
+ } else if (prop.type === "map") {
112
+ return "MapSchema";
119
113
 
120
- // } else if(prop.type === "map") {
121
- // initializer = `new schema.MapSchema()`;
122
- // }
123
-
124
- // return `this.${prop.name} = ${initializer}`;
125
- // }
114
+ } else {
115
+ return typeMaps[prop.type];
116
+ }
117
+ }
@@ -148,6 +148,49 @@ function inspectNode(node: ts.Node, context: Context, decoratorName: string) {
148
148
  defineProperty(property, typeArgument);
149
149
  }
150
150
 
151
+ } else if (
152
+ node.getText() === "setFields" &&
153
+ (
154
+ node.parent.kind === ts.SyntaxKind.CallExpression ||
155
+ node.parent.kind === ts.SyntaxKind.PropertyAccessExpression
156
+ )
157
+ ) {
158
+ /**
159
+ * Metadata.setFields(klassName, { ... })
160
+ */
161
+ const callExpression = (node.parent.kind === ts.SyntaxKind.PropertyAccessExpression)
162
+ ? node.parent.parent as ts.CallExpression
163
+ : node.parent as ts.CallExpression;
164
+
165
+ if (callExpression.kind !== ts.SyntaxKind.CallExpression) {
166
+ break;
167
+ }
168
+
169
+ const classNameNode = callExpression.arguments[0];
170
+ const className = ts.isClassExpression(classNameNode)
171
+ ? classNameNode.name?.escapedText.toString()
172
+ : classNameNode.getText();
173
+
174
+ // skip if no className is provided
175
+ if (!className) { break; }
176
+
177
+ if (currentStructure.name !== className) {
178
+ currentStructure = new Class();
179
+ }
180
+ context.addStructure(currentStructure);
181
+ (currentStructure as Class).extends = "Schema"; // force extends to Schema
182
+ currentStructure.name = className;
183
+
184
+ const types = callExpression.arguments[1] as any;
185
+ for (let i = 0; i < types.properties.length; i++) {
186
+ const prop = types.properties[i];
187
+
188
+ const property = currentProperty || new Property();
189
+ property.name = prop.name.escapedText;
190
+
191
+ currentStructure.addProperty(property);
192
+ defineProperty(property, prop.initializer);
193
+ }
151
194
 
152
195
  } else if (
153
196
  node.getText() === "defineTypes" &&
@@ -192,6 +235,70 @@ function inspectNode(node: ts.Node, context: Context, decoratorName: string) {
192
235
 
193
236
  break;
194
237
 
238
+ case ts.SyntaxKind.CallExpression:
239
+ /**
240
+ * Defining schema via `schema.schema({ ... })`
241
+ * - schema.schema({})
242
+ * - schema({})
243
+ * - ClassName.extends({})
244
+ */
245
+ if (
246
+ (
247
+ (
248
+ (node as ts.CallExpression).expression?.getText() === "schema.schema" ||
249
+ (node as ts.CallExpression).expression?.getText() === "schema"
250
+ ) ||
251
+ (
252
+ (node as ts.CallExpression).expression?.getText().indexOf(".extends") !== -1
253
+ )
254
+ ) &&
255
+ (node as ts.CallExpression).arguments[0].kind === ts.SyntaxKind.ObjectLiteralExpression
256
+ ) {
257
+ const callExpression = node as ts.CallExpression;
258
+
259
+ let className = callExpression.arguments[1]?.getText();
260
+
261
+ if (!className && callExpression.parent.kind === ts.SyntaxKind.VariableDeclaration) {
262
+ className = (callExpression.parent as ts.VariableDeclaration).name?.getText();
263
+ }
264
+
265
+ // skip if no className is provided
266
+ if (!className) { break; }
267
+
268
+ if (currentStructure.name !== className) {
269
+ currentStructure = new Class();
270
+ context.addStructure(currentStructure);
271
+ }
272
+
273
+ if ((node as ts.CallExpression).expression?.getText().indexOf(".extends") !== -1) {
274
+ // if it's using `.extends({})`
275
+ const extendsClass = (node as any).expression?.expression?.escapedText;
276
+
277
+ // skip if no extendsClass is provided
278
+ if (!extendsClass) { break; }
279
+ (currentStructure as Class).extends = extendsClass;
280
+
281
+ } else {
282
+ // if it's using `schema({})`
283
+ (currentStructure as Class).extends = "Schema"; // force extends to Schema
284
+ }
285
+
286
+ currentStructure.name = className;
287
+
288
+ const types = callExpression.arguments[0] as any;
289
+ for (let i = 0; i < types.properties.length; i++) {
290
+ const prop = types.properties[i];
291
+
292
+ const property = currentProperty || new Property();
293
+ property.name = prop.name.escapedText;
294
+
295
+ currentStructure.addProperty(property);
296
+ defineProperty(property, prop.initializer);
297
+ }
298
+ }
299
+
300
+ break;
301
+
195
302
  case ts.SyntaxKind.EnumMember:
196
303
  if (currentStructure instanceof Enum) {
197
304
  const initializer = (node as any).initializer?.text;
@@ -39,6 +39,7 @@ export class Context {
39
39
  }
40
40
 
41
41
  addStructure(structure: IStructure) {
42
+ if (structure.context === this) { return; } // skip if already added.
42
43
  structure.context = this;
43
44
 
44
45
  if (structure instanceof Class) {
package/src/debug.ts ADDED
@@ -0,0 +1,55 @@
1
+ import * as fs from "fs";
2
+ import { Reflection, Decoder } from "./index";
3
+
4
+ const contents = fs.readFileSync("/Users/endel/Projects/colyseus/clients/bubbits/project/@bubbits/backend/schema-debug.txt", { encoding: "utf8" }).toString();
5
+
6
+ let isCommentBlock = false;
7
+ let lastComment = "";
8
+
9
+ let decoder: Decoder;
10
+
11
+ function getBuffer(line: string) {
12
+ const start = line.lastIndexOf(":");
13
+ const buffer = Buffer.from(new Uint8Array(line.substring(start + 1).split(",").map(n => Number(n))));
14
+ console.log(`(${buffer.byteLength}) ${Array.from(buffer).join(",")}`)
15
+ // console.log("");
16
+ // console.log("");
17
+ // console.log("> ", line);
18
+ // console.log("> substring:", line.substring(start + 1))
19
+ return buffer;
20
+ }
21
+
22
+ function decode(buffer: Buffer) {
23
+ try {
24
+ decoder.decode(buffer);
25
+ } catch (e) {
26
+ console.error(e);
27
+ console.log("Last log:\n\n")
28
+ console.log(lastComment);
29
+ }
30
+ }
31
+
32
+ contents.split("\n").forEach((line) => {
33
+ if (line.startsWith("#")) {
34
+ // reset last comment.
35
+ if (isCommentBlock === false) { lastComment = ""; }
36
+
37
+ isCommentBlock = true;
38
+ lastComment += line.substring(line.indexOf(":") + 1) + "\n";
39
+ return;
40
+ }
41
+
42
+ isCommentBlock = false;
43
+
44
+ if (line.startsWith("handshake:") && !decoder) {
45
+ decoder = Reflection.decode(getBuffer(line));
46
+
47
+ } else if (line.startsWith("state:")) {
48
+ decode(getBuffer(line));
49
+
50
+ } else if (line.startsWith("patch:")) {
51
+ decode(getBuffer(line));
52
+ }
53
+ });
54
+
55
+ console.log(decoder.state.toJSON());
@@ -3,7 +3,7 @@ import { Metadata } from "../Metadata";
3
3
  import { Schema } from "../Schema";
4
4
  import type { Ref } from "../encoder/ChangeTree";
5
5
  import type { Decoder } from "./Decoder";
6
- import * as decode from "../encoding/decode";
6
+ import { Iterator, decode } from "../encoding/decode";
7
7
  import { $childType, $deleteByIndex, $getByIndex } from "../types/symbols";
8
8
 
9
9
  import type { MapSchema } from "../types/custom/MapSchema";
@@ -28,7 +28,7 @@ export const DEFINITION_MISMATCH = -1;
28
28
  export type DecodeOperation<T extends Schema = any> = (
29
29
  decoder: Decoder<T>,
30
30
  bytes: Buffer,
31
- it: decode.Iterator,
31
+ it: Iterator,
32
32
  ref: Ref,
33
33
  allChanges: DataChange[],
34
34
  ) => number | void;
@@ -40,7 +40,7 @@ export function decodeValue(
40
40
  index: number,
41
41
  type: any,
42
42
  bytes: Buffer,
43
- it: decode.Iterator,
43
+ it: Iterator,
44
44
  allChanges: DataChange[],
45
45
  ) {
46
46
  const $root = decoder.root;
@@ -105,7 +105,14 @@ export function decodeValue(
105
105
  value = decoder.createInstanceOfType(childType);
106
106
  }
107
107
 
108
- $root.addRef(refId, value, (value !== previousValue));
108
+ $root.addRef(
109
+ refId,
110
+ value,
111
+ (
112
+ value !== previousValue || // increment ref count if value has changed
113
+ (operation === OPERATION.DELETE_AND_ADD && value === previousValue) // increment ref count if the same instance is being added again
114
+ )
115
+ );
109
116
  }
110
117
 
111
118
 
@@ -169,12 +176,12 @@ export function decodeValue(
169
176
  export const decodeSchemaOperation: DecodeOperation = function (
170
177
  decoder: Decoder<any>,
171
178
  bytes: Buffer,
172
- it: decode.Iterator,
179
+ it: Iterator,
173
180
  ref: Ref,
174
181
  allChanges: DataChange[],
175
182
  ) {
176
183
  const first_byte = bytes[it.offset++];
177
- const metadata: Metadata = ref['constructor'][Symbol.metadata];
184
+ const metadata: Metadata = ref.constructor[Symbol.metadata];
178
185
 
179
186
  // "compressed" index + operation
180
187
  const operation = (first_byte >> 6) << 6
@@ -182,21 +189,24 @@ export const decodeSchemaOperation: DecodeOperation = function (
182
189
 
183
190
  // skip early if field is not defined
184
191
  const field = metadata[index];
185
- if (field === undefined) { return DEFINITION_MISMATCH; }
192
+ if (field === undefined) {
193
+ console.warn("@colyseus/schema: field not defined at", { index, ref: ref.constructor.name, metadata });
194
+ return DEFINITION_MISMATCH;
195
+ }
186
196
 
187
197
  const { value, previousValue } = decodeValue(
188
198
  decoder,
189
199
  operation,
190
200
  ref,
191
201
  index,
192
- metadata[field].type,
202
+ field.type,
193
203
  bytes,
194
204
  it,
195
205
  allChanges,
196
206
  );
197
207
 
198
208
  if (value !== null && value !== undefined) {
199
- ref[field] = value;
209
+ ref[field.name] = value;
200
210
  }
201
211
 
202
212
  // add change
@@ -205,7 +215,7 @@ export const decodeSchemaOperation: DecodeOperation = function (
205
215
  ref,
206
216
  refId: decoder.currentRefId,
207
217
  op: operation,
208
- field: field,
218
+ field: field.name,
209
219
  value,
210
220
  previousValue,
211
221
  });
@@ -215,7 +225,7 @@ export const decodeSchemaOperation: DecodeOperation = function (
215
225
  export const decodeKeyValueOperation: DecodeOperation = function (
216
226
  decoder: Decoder<any>,
217
227
  bytes: Buffer,
218
- it: decode.Iterator,
228
+ it: Iterator,
219
229
  ref: Ref,
220
230
  allChanges: DataChange[]
221
231
  ) {
@@ -251,6 +261,7 @@ export const decodeKeyValueOperation: DecodeOperation = function (
251
261
  dynamicIndex = ref['getIndex'](index);
252
262
  }
253
263
 
264
+
254
265
  const { value, previousValue } = decodeValue(
255
266
  decoder,
256
267
  operation,
@@ -298,12 +309,13 @@ export const decodeKeyValueOperation: DecodeOperation = function (
298
309
  export const decodeArray: DecodeOperation = function (
299
310
  decoder: Decoder<any>,
300
311
  bytes: Buffer,
301
- it: decode.Iterator,
312
+ it: Iterator,
302
313
  ref: ArraySchema,
303
314
  allChanges: DataChange[]
304
315
  ) {
305
316
  // "uncompressed" index + operation (array/map items)
306
- const operation = bytes[it.offset++];
317
+ let operation = bytes[it.offset++];
318
+ let index: number;
307
319
 
308
320
  if (operation === OPERATION.CLEAR) {
309
321
  //
@@ -315,11 +327,15 @@ export const decodeArray: DecodeOperation = function (
315
327
  (ref as ArraySchema).clear();
316
328
  return;
317
329
 
330
+ } else if (operation === OPERATION.REVERSE) {
331
+ (ref as ArraySchema).reverse();
332
+ return;
333
+
318
334
  } else if (operation === OPERATION.DELETE_BY_REFID) {
319
335
  // TODO: refactor here, try to follow same flow as below
320
336
  const refId = decode.number(bytes, it);
321
337
  const previousValue = decoder.root.refs.get(refId);
322
- const index = ref.findIndex((value) => value === previousValue);
338
+ index = ref.findIndex((value) => value === previousValue);
323
339
  ref[$deleteByIndex](index);
324
340
  allChanges.push({
325
341
  ref,
@@ -330,10 +346,22 @@ export const decodeArray: DecodeOperation = function (
330
346
  value: undefined,
331
347
  previousValue,
332
348
  });
349
+
333
350
  return;
351
+
352
+ } else if (operation === OPERATION.ADD_BY_REFID) {
353
+ const refId = decode.number(bytes, it);
354
+ const itemByRefId = decoder.root.refs.get(refId);
355
+
356
+ // use existing index, or push new value
357
+ index = (itemByRefId)
358
+ ? ref.findIndex((value) => value === itemByRefId)
359
+ : ref.length;
360
+
361
+ } else {
362
+ index = decode.number(bytes, it);
334
363
  }
335
364
 
336
- const index = decode.number(bytes, it);
337
365
  const type = ref[$childType];
338
366
 
339
367
  let dynamicIndex: number | string = index;
@@ -1,13 +1,13 @@
1
- import { TypeContext } from "../annotations";
1
+ import { TypeContext } from "../types/TypeContext";
2
2
  import { $changes, $childType, $decoder, $onDecodeEnd } from "../types/symbols";
3
3
  import { Schema } from "../Schema";
4
4
 
5
- import * as decode from "../encoding/decode";
5
+ import { decode } from "../encoding/decode";
6
6
  import { OPERATION, SWITCH_TO_STRUCTURE, TYPE_ID } from '../encoding/spec';
7
- import { Ref } from "../encoder/ChangeTree";
8
- import { Iterator } from "../encoding/decode";
7
+ import type { Ref } from "../encoder/ChangeTree";
8
+ import type { Iterator } from "../encoding/decode";
9
9
  import { ReferenceTracker } from "./ReferenceTracker";
10
- import { DEFINITION_MISMATCH, DataChange, DecodeOperation } from "./DecodeOperation";
10
+ import { DEFINITION_MISMATCH, type DataChange, type DecodeOperation } from "./DecodeOperation";
11
11
  import { Collection } from "../types/HelperTypes";
12
12
 
13
13
  export class Decoder<T extends Schema = any> {
@@ -21,7 +21,8 @@ export class Decoder<T extends Schema = any> {
21
21
  triggerChanges?: (allChanges: DataChange[]) => void;
22
22
 
23
23
  constructor(root: T, context?: TypeContext) {
24
- this.setRoot(root);
24
+ this.setState(root);
25
+
25
26
  this.context = context || new TypeContext(root.constructor as typeof Schema);
26
27
 
27
28
  // console.log(">>>>>>>>>>>>>>>> Decoder types");
@@ -30,7 +31,7 @@ export class Decoder<T extends Schema = any> {
30
31
  // });
31
32
  }
32
33
 
33
- protected setRoot(root: T) {
34
+ protected setState(root: T) {
34
35
  this.state = root;
35
36
  this.root = new ReferenceTracker();
36
37
  this.root.addRef(0, root);
@@ -66,7 +67,8 @@ export class Decoder<T extends Schema = any> {
66
67
  if (!nextRef) { throw new Error(`"refId" not found: ${this.currentRefId}`); }
67
68
  ref[$onDecodeEnd]?.()
68
69
  ref = nextRef;
69
- decoder = ref['constructor'][$decoder];
70
+
71
+ decoder = ref.constructor[$decoder];
70
72
 
71
73
  continue;
72
74
  }
@@ -80,9 +82,9 @@ export class Decoder<T extends Schema = any> {
80
82
  // keep skipping next bytes until reaches a known structure
81
83
  // by local decoder.
82
84
  //
83
- const nextIterator: decode.Iterator = { offset: it.offset };
85
+ const nextIterator: Iterator = { offset: it.offset };
84
86
  while (it.offset < totalBytes) {
85
- if (decode.switchStructureCheck(bytes, it)) {
87
+ if (bytes[it.offset] === SWITCH_TO_STRUCTURE) {
86
88
  nextIterator.offset = it.offset + 1;
87
89
  if ($root.refs.has(decode.number(bytes, nextIterator))) {
88
90
  break;
@@ -81,6 +81,7 @@ export class ReferenceTracker {
81
81
  clearRefs() {
82
82
  this.refs.clear();
83
83
  this.deletedRefs.clear();
84
+ this.callbacks = {};
84
85
  this.refCounts = {};
85
86
  }
86
87
 
@@ -98,8 +99,9 @@ export class ReferenceTracker {
98
99
  // Ensure child schema instances have their references removed as well.
99
100
  //
100
101
  if (Metadata.isValidInstance(ref)) {
101
- const metadata: Metadata = ref['constructor'][Symbol.metadata];
102
- for (const field in metadata) {
102
+ const metadata: Metadata = ref.constructor[Symbol.metadata];
103
+ for (const index in metadata) {
104
+ const field = metadata[index as any as number].name;
103
105
  const childRefId = typeof(ref[field]) === "object" && this.refIds.get(ref[field]);
104
106
  if (childRefId) {
105
107
  this.removeRef(childRefId);
@@ -148,4 +150,4 @@ export class ReferenceTracker {
148
150
  }
149
151
  }
150
152
 
151
- }
153
+ }