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

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 (116) hide show
  1. package/README.md +131 -61
  2. package/build/cjs/index.js +644 -283
  3. package/build/cjs/index.js.map +1 -1
  4. package/build/esm/index.mjs +643 -282
  5. package/build/esm/index.mjs.map +1 -1
  6. package/build/umd/index.js +644 -283
  7. package/lib/Metadata.d.ts +2 -0
  8. package/lib/Metadata.js +39 -0
  9. package/lib/Metadata.js.map +1 -1
  10. package/lib/Reflection.d.ts +2 -3
  11. package/lib/Reflection.js +31 -26
  12. package/lib/Reflection.js.map +1 -1
  13. package/lib/Schema.d.ts +2 -2
  14. package/lib/Schema.js +2 -2
  15. package/lib/Schema.js.map +1 -1
  16. package/lib/annotations.d.ts +0 -19
  17. package/lib/annotations.js +15 -101
  18. package/lib/annotations.js.map +1 -1
  19. package/lib/bench_encode.d.ts +1 -0
  20. package/lib/bench_encode.js +120 -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 -7
  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/strategy/RawChanges.js +1 -2
  52. package/lib/decoder/strategy/RawChanges.js.map +1 -1
  53. package/lib/decoder/strategy/StateCallbacks.d.ts +44 -11
  54. package/lib/decoder/strategy/StateCallbacks.js +72 -63
  55. package/lib/decoder/strategy/StateCallbacks.js.map +1 -1
  56. package/lib/encoder/ChangeTree.d.ts +1 -12
  57. package/lib/encoder/ChangeTree.js +29 -58
  58. package/lib/encoder/ChangeTree.js.map +1 -1
  59. package/lib/encoder/EncodeOperation.d.ts +0 -1
  60. package/lib/encoder/EncodeOperation.js +29 -13
  61. package/lib/encoder/EncodeOperation.js.map +1 -1
  62. package/lib/encoder/Encoder.d.ts +10 -8
  63. package/lib/encoder/Encoder.js +78 -53
  64. package/lib/encoder/Encoder.js.map +1 -1
  65. package/lib/encoder/Root.d.ts +17 -0
  66. package/lib/encoder/Root.js +44 -0
  67. package/lib/encoder/Root.js.map +1 -0
  68. package/lib/encoder/StateView.d.ts +2 -2
  69. package/lib/encoder/StateView.js +48 -58
  70. package/lib/encoder/StateView.js.map +1 -1
  71. package/lib/encoding/assert.js +3 -3
  72. package/lib/encoding/assert.js.map +1 -1
  73. package/lib/encoding/decode.js +21 -22
  74. package/lib/encoding/decode.js.map +1 -1
  75. package/lib/encoding/encode.d.ts +2 -2
  76. package/lib/encoding/encode.js +40 -39
  77. package/lib/encoding/encode.js.map +1 -1
  78. package/lib/encoding/spec.d.ts +2 -1
  79. package/lib/encoding/spec.js +1 -0
  80. package/lib/encoding/spec.js.map +1 -1
  81. package/lib/index.d.ts +5 -1
  82. package/lib/index.js +8 -4
  83. package/lib/index.js.map +1 -1
  84. package/lib/types/TypeContext.d.ts +23 -0
  85. package/lib/types/TypeContext.js +109 -0
  86. package/lib/types/TypeContext.js.map +1 -0
  87. package/lib/types/custom/ArraySchema.d.ts +2 -2
  88. package/lib/types/custom/ArraySchema.js +0 -9
  89. package/lib/types/custom/ArraySchema.js.map +1 -1
  90. package/lib/types/registry.js +3 -4
  91. package/lib/types/registry.js.map +1 -1
  92. package/lib/types/utils.js +1 -2
  93. package/lib/types/utils.js.map +1 -1
  94. package/lib/utils.js +3 -4
  95. package/lib/utils.js.map +1 -1
  96. package/package.json +5 -5
  97. package/src/Metadata.ts +47 -0
  98. package/src/Reflection.ts +34 -26
  99. package/src/Schema.ts +2 -2
  100. package/src/annotations.ts +7 -109
  101. package/src/bench_encode.ts +97 -0
  102. package/src/debug.ts +56 -0
  103. package/src/decoder/DecodeOperation.ts +30 -7
  104. package/src/decoder/Decoder.ts +13 -11
  105. package/src/decoder/strategy/StateCallbacks.ts +149 -79
  106. package/src/encoder/ChangeTree.ts +36 -66
  107. package/src/encoder/EncodeOperation.ts +29 -12
  108. package/src/encoder/Encoder.ts +95 -61
  109. package/src/encoder/Root.ts +51 -0
  110. package/src/encoder/StateView.ts +51 -67
  111. package/src/encoding/decode.ts +1 -2
  112. package/src/encoding/encode.ts +25 -22
  113. package/src/encoding/spec.ts +1 -0
  114. package/src/index.ts +8 -11
  115. package/src/types/TypeContext.ts +127 -0
  116. package/src/types/custom/ArraySchema.ts +2 -2
@@ -29,6 +29,7 @@
29
29
  OPERATION[OPERATION["REVERSE"] = 15] = "REVERSE";
30
30
  OPERATION[OPERATION["MOVE"] = 32] = "MOVE";
31
31
  OPERATION[OPERATION["DELETE_BY_REFID"] = 33] = "DELETE_BY_REFID";
32
+ OPERATION[OPERATION["ADD_BY_REFID"] = 129] = "ADD_BY_REFID";
32
33
  })(exports.OPERATION || (exports.OPERATION = {}));
33
34
 
34
35
  Symbol.metadata ??= Symbol.for("Symbol.metadata");
@@ -145,6 +146,45 @@
145
146
  isDeprecated(metadata, field) {
146
147
  return metadata[field].deprecated === true;
147
148
  },
149
+ init(klass) {
150
+ //
151
+ // Used only to initialize an empty Schema (Encoder#constructor)
152
+ // TODO: remove/refactor this...
153
+ //
154
+ const metadata = {};
155
+ klass[Symbol.metadata] = metadata;
156
+ Object.defineProperty(metadata, -1, {
157
+ value: 0,
158
+ enumerable: false,
159
+ configurable: true,
160
+ });
161
+ },
162
+ initialize(constructor, parentMetadata) {
163
+ let metadata = constructor[Symbol.metadata] ?? Object.create(null);
164
+ // make sure inherited classes have their own metadata object.
165
+ if (constructor[Symbol.metadata] === parentMetadata) {
166
+ metadata = Object.create(null);
167
+ if (parentMetadata) {
168
+ // assign parent metadata to current
169
+ Object.assign(metadata, parentMetadata);
170
+ for (let i = 0; i <= parentMetadata[-1]; i++) {
171
+ Object.defineProperty(metadata, i, {
172
+ value: parentMetadata[i],
173
+ enumerable: false,
174
+ configurable: true,
175
+ });
176
+ }
177
+ Object.defineProperty(metadata, -1, {
178
+ value: parentMetadata[-1],
179
+ enumerable: false,
180
+ configurable: true,
181
+ writable: true,
182
+ });
183
+ }
184
+ }
185
+ constructor[Symbol.metadata] = metadata;
186
+ return metadata;
187
+ },
148
188
  isValidInstance(klass) {
149
189
  return (klass.constructor[Symbol.metadata] &&
150
190
  Object.prototype.hasOwnProperty.call(klass.constructor[Symbol.metadata], -1));
@@ -160,44 +200,6 @@
160
200
  };
161
201
 
162
202
  var _a$5;
163
- class Root {
164
- constructor() {
165
- this.nextUniqueId = 0;
166
- this.refCount = new WeakMap();
167
- // all changes
168
- this.allChanges = new Map();
169
- this.allFilteredChanges = new Map();
170
- // pending changes to be encoded
171
- this.changes = new Map();
172
- this.filteredChanges = new Map();
173
- }
174
- getNextUniqueId() {
175
- return this.nextUniqueId++;
176
- }
177
- add(changeTree) {
178
- const refCount = this.refCount.get(changeTree) || 0;
179
- this.refCount.set(changeTree, refCount + 1);
180
- }
181
- remove(changeTree) {
182
- const refCount = this.refCount.get(changeTree);
183
- if (refCount <= 1) {
184
- this.allChanges.delete(changeTree);
185
- this.changes.delete(changeTree);
186
- if (changeTree.isFiltered || changeTree.isPartiallyFiltered) {
187
- this.allFilteredChanges.delete(changeTree);
188
- this.filteredChanges.delete(changeTree);
189
- }
190
- this.refCount.delete(changeTree);
191
- }
192
- else {
193
- this.refCount.set(changeTree, refCount - 1);
194
- }
195
- changeTree.forEachChild((child, _) => this.remove(child));
196
- }
197
- clear() {
198
- this.changes.clear();
199
- }
200
- }
201
203
  class ChangeTree {
202
204
  static { _a$5 = $isNew; }
203
205
  ;
@@ -229,8 +231,6 @@
229
231
  if (this.isFiltered || this.isPartiallyFiltered) {
230
232
  this.root.allFilteredChanges.set(this, this.allFilteredChanges);
231
233
  this.root.filteredChanges.set(this, this.filteredChanges);
232
- // } else {
233
- // this.root.allChanges.set(this, this.allChanges);
234
234
  }
235
235
  if (!this.isFiltered) {
236
236
  this.root.allChanges.set(this, this.allChanges);
@@ -264,14 +264,12 @@
264
264
  this.checkIsFiltered(parent, parentIndex);
265
265
  if (!this.isFiltered) {
266
266
  this.root.changes.set(this, this.changes);
267
+ this.root.allChanges.set(this, this.allChanges);
267
268
  }
268
269
  if (this.isFiltered || this.isPartiallyFiltered) {
269
270
  this.root.filteredChanges.set(this, this.filteredChanges);
270
271
  this.root.allFilteredChanges.set(this, this.filteredChanges);
271
272
  }
272
- else {
273
- this.root.allChanges.set(this, this.allChanges);
274
- }
275
273
  this.ensureRefId();
276
274
  this.forEachChild((changeTree, atIndex) => {
277
275
  changeTree.setParent(this.ref, root, atIndex);
@@ -295,7 +293,7 @@
295
293
  // MapSchema / ArraySchema, etc.
296
294
  this.ref.forEach((value, key) => {
297
295
  if (Metadata.isValidInstance(value)) {
298
- callback(value[$changes], this.ref[$changes].indexes[key]);
296
+ callback(value[$changes], this.ref[$changes].indexes[key] ?? key);
299
297
  }
300
298
  });
301
299
  }
@@ -323,8 +321,9 @@
323
321
  // TODO: are DELETE operations being encoded as ADD here ??
324
322
  //
325
323
  if (isFiltered) {
326
- this.allFilteredChanges.set(index, exports.OPERATION.ADD);
327
324
  this.root?.filteredChanges.set(this, this.filteredChanges);
325
+ this.allFilteredChanges.set(index, exports.OPERATION.ADD);
326
+ this.root?.allFilteredChanges.set(this, this.allFilteredChanges);
328
327
  }
329
328
  else {
330
329
  this.allChanges.set(index, exports.OPERATION.ADD);
@@ -363,7 +362,6 @@
363
362
  }
364
363
  _shiftAllChangeIndexes(shiftIndex, startIndex = 0, allChangeSet) {
365
364
  Array.from(allChangeSet.entries()).forEach(([index, op]) => {
366
- // console.log('shiftAllChangeIndexes', index >= startIndex, { index, op, shiftIndex, startIndex })
367
365
  if (index >= startIndex) {
368
366
  allChangeSet.delete(index);
369
367
  allChangeSet.set(index + shiftIndex, op);
@@ -504,16 +502,31 @@
504
502
  }
505
503
  checkIsFiltered(parent, parentIndex) {
506
504
  // Detect if current structure has "filters" declared
507
- this.isPartiallyFiltered = this.ref['constructor']?.[Symbol.metadata]?.[-2];
508
- // TODO: support "partially filtered", where the instance is visible, but only a field is not.
509
- // Detect if parent has "filters" declared
510
- while (parent && !this.isFiltered) {
511
- const metadata = parent['constructor'][Symbol.metadata];
512
- const fieldName = metadata?.[parentIndex];
513
- const isParentOwned = metadata?.[fieldName]?.tag !== undefined;
514
- this.isFiltered = isParentOwned || parent[$changes].isFiltered; // metadata?.[-2]
515
- parent = parent[$changes].parent;
516
- }
505
+ this.isPartiallyFiltered = (this.ref['constructor']?.[Symbol.metadata]?.[-2] !== undefined);
506
+ if (parent && !Metadata.isValidInstance(parent)) {
507
+ const parentChangeTree = parent[$changes];
508
+ parent = parentChangeTree.parent;
509
+ parentIndex = parentChangeTree.parentIndex;
510
+ }
511
+ const parentMetadata = parent?.['constructor']?.[Symbol.metadata];
512
+ this.isFiltered = (parent &&
513
+ parentMetadata?.[-2]?.includes(parentIndex));
514
+ // this.isFiltered = this.ref['constructor']?.[Symbol.metadata]?.[-4];
515
+ // // Detect if parent has "filters" declared
516
+ // while (parent && !this.isFiltered) {
517
+ // const metadata: Metadata = parent['constructor'][Symbol.metadata];
518
+ // // this.isFiltered = metadata?.[-4];
519
+ // const fieldName = metadata?.[parentIndex];
520
+ // const isParentOwned = metadata?.[fieldName]?.tag !== undefined;
521
+ // this.isFiltered = isParentOwned || parent[$changes].isFiltered; // metadata?.[-2]
522
+ // parent = parent[$changes].parent;
523
+ // };
524
+ // console.log("ChangeTree.checkIsFiltered", {
525
+ // parent: parent?.constructor.name,
526
+ // ref: this.ref.constructor.name,
527
+ // isFiltered: this.isFiltered,
528
+ // isPartiallyFiltered: this.isPartiallyFiltered,
529
+ // });
517
530
  //
518
531
  // TODO: refactor this!
519
532
  //
@@ -565,26 +578,29 @@
565
578
  textEncoder = new TextEncoder();
566
579
  }
567
580
  catch (e) { }
568
- function utf8Length(str) {
569
- var c = 0, length = 0;
570
- for (var i = 0, l = str.length; i < l; i++) {
571
- c = str.charCodeAt(i);
572
- if (c < 0x80) {
573
- length += 1;
574
- }
575
- else if (c < 0x800) {
576
- length += 2;
577
- }
578
- else if (c < 0xd800 || c >= 0xe000) {
579
- length += 3;
580
- }
581
- else {
582
- i++;
583
- length += 4;
581
+ const hasBufferByteLength = (typeof Buffer !== 'undefined' && Buffer.byteLength);
582
+ const utf8Length = (hasBufferByteLength)
583
+ ? Buffer.byteLength // node
584
+ : function (str, _) {
585
+ var c = 0, length = 0;
586
+ for (var i = 0, l = str.length; i < l; i++) {
587
+ c = str.charCodeAt(i);
588
+ if (c < 0x80) {
589
+ length += 1;
590
+ }
591
+ else if (c < 0x800) {
592
+ length += 2;
593
+ }
594
+ else if (c < 0xd800 || c >= 0xe000) {
595
+ length += 3;
596
+ }
597
+ else {
598
+ i++;
599
+ length += 4;
600
+ }
584
601
  }
585
- }
586
- return length;
587
- }
602
+ return length;
603
+ };
588
604
  function utf8Write(view, str, it) {
589
605
  var c = 0;
590
606
  for (var i = 0, l = str.length; i < l; i++) {
@@ -679,8 +695,7 @@
679
695
  if (!value) {
680
696
  value = "";
681
697
  }
682
- // let length = utf8Length(value);
683
- let length = Buffer.byteLength(value, "utf8");
698
+ let length = utf8Length(value, "utf8");
684
699
  let size = 0;
685
700
  // fixstr
686
701
  if (length < 0x20) {
@@ -791,23 +806,23 @@
791
806
 
792
807
  var encode = /*#__PURE__*/Object.freeze({
793
808
  __proto__: null,
794
- utf8Length: utf8Length,
795
- utf8Write: utf8Write,
796
- int8: int8$1,
797
- uint8: uint8$1,
809
+ boolean: boolean$1,
810
+ float32: float32$1,
811
+ float64: float64$1,
798
812
  int16: int16$1,
799
- uint16: uint16$1,
800
813
  int32: int32$1,
801
- uint32: uint32$1,
802
814
  int64: int64$1,
815
+ int8: int8$1,
816
+ number: number$1,
817
+ string: string$1,
818
+ uint16: uint16$1,
819
+ uint32: uint32$1,
803
820
  uint64: uint64$1,
804
- float32: float32$1,
805
- float64: float64$1,
821
+ uint8: uint8$1,
822
+ utf8Length: utf8Length,
823
+ utf8Write: utf8Write,
806
824
  writeFloat32: writeFloat32,
807
- writeFloat64: writeFloat64,
808
- boolean: boolean$1,
809
- string: string$1,
810
- number: number$1
825
+ writeFloat64: writeFloat64
811
826
  });
812
827
 
813
828
  class EncodeSchemaError extends Error {
@@ -949,6 +964,18 @@
949
964
  }
950
965
  const type = changeTree.getType(field);
951
966
  const value = changeTree.getValue(field);
967
+ // try { throw new Error(); } catch (e) {
968
+ // // only print if not coming from Reflection.ts
969
+ // if (!e.stack.includes("src/Reflection.ts")) {
970
+ // console.log("encodeKeyValueOperation -> ", {
971
+ // ref: changeTree.ref.constructor.name,
972
+ // field,
973
+ // operation: OPERATION[operation],
974
+ // value: value?.toJSON(),
975
+ // items: ref.toJSON(),
976
+ // });
977
+ // }
978
+ // }
952
979
  // TODO: inline this function call small performance gain
953
980
  encodeValue(encoder, bytes, ref, type, value, field, operation, it);
954
981
  };
@@ -958,15 +985,19 @@
958
985
  */
959
986
  const encodeArray = function (encoder, bytes, changeTree, field, operation, it, isEncodeAll, hasView) {
960
987
  const ref = changeTree.ref;
961
- if (hasView &&
962
- operation === exports.OPERATION.DELETE &&
963
- typeof (changeTree.getType(field)) !== "string") {
964
- // encode delete by refId (array of schemas)
965
- bytes[it.offset++] = exports.OPERATION.DELETE_BY_REFID;
966
- const value = ref['tmpItems'][field];
967
- const refId = value[$changes].refId;
968
- number$1(bytes, refId, it);
969
- return;
988
+ const useOperationByRefId = hasView && changeTree.isFiltered && (typeof (changeTree.getType(field)) !== "string");
989
+ let refOrIndex;
990
+ if (useOperationByRefId) {
991
+ refOrIndex = ref['tmpItems'][field][$changes].refId;
992
+ if (operation === exports.OPERATION.DELETE) {
993
+ operation = exports.OPERATION.DELETE_BY_REFID;
994
+ }
995
+ else if (operation === exports.OPERATION.ADD) {
996
+ operation = exports.OPERATION.ADD_BY_REFID;
997
+ }
998
+ }
999
+ else {
1000
+ refOrIndex = field;
970
1001
  }
971
1002
  // encode operation
972
1003
  bytes[it.offset++] = operation & 255;
@@ -975,7 +1006,7 @@
975
1006
  return;
976
1007
  }
977
1008
  // encode index
978
- number$1(bytes, field, it);
1009
+ number$1(bytes, refOrIndex, it);
979
1010
  // Do not encode value for DELETE operations
980
1011
  if (operation === exports.OPERATION.DELETE) {
981
1012
  return;
@@ -1016,7 +1047,6 @@
1016
1047
  * SOFTWARE
1017
1048
  */
1018
1049
  function utf8Read(bytes, it, length) {
1019
- it.offset += length;
1020
1050
  var string = '', chr = 0;
1021
1051
  for (var i = it.offset, end = it.offset + length; i < end; i++) {
1022
1052
  var byte = bytes[i];
@@ -1053,6 +1083,7 @@
1053
1083
  // (do not throw error to avoid server/client from crashing due to hack attemps)
1054
1084
  // throw new Error('Invalid byte ' + byte.toString(16));
1055
1085
  }
1086
+ it.offset += length;
1056
1087
  return string;
1057
1088
  }
1058
1089
  function int8(bytes, it) {
@@ -1224,31 +1255,31 @@
1224
1255
 
1225
1256
  var decode = /*#__PURE__*/Object.freeze({
1226
1257
  __proto__: null,
1227
- utf8Read: utf8Read,
1228
- int8: int8,
1229
- uint8: uint8,
1230
- int16: int16,
1231
- uint16: uint16,
1232
- int32: int32,
1233
- uint32: uint32,
1258
+ arrayCheck: arrayCheck,
1259
+ boolean: boolean,
1234
1260
  float32: float32,
1235
1261
  float64: float64,
1262
+ int16: int16,
1263
+ int32: int32,
1236
1264
  int64: int64,
1237
- uint64: uint64,
1265
+ int8: int8,
1266
+ number: number,
1267
+ numberCheck: numberCheck,
1238
1268
  readFloat32: readFloat32,
1239
1269
  readFloat64: readFloat64,
1240
- boolean: boolean,
1241
1270
  string: string,
1242
1271
  stringCheck: stringCheck,
1243
- number: number,
1244
- numberCheck: numberCheck,
1245
- arrayCheck: arrayCheck,
1246
- switchStructureCheck: switchStructureCheck
1272
+ switchStructureCheck: switchStructureCheck,
1273
+ uint16: uint16,
1274
+ uint32: uint32,
1275
+ uint64: uint64,
1276
+ uint8: uint8,
1277
+ utf8Read: utf8Read
1247
1278
  });
1248
1279
 
1249
1280
  const DEFINITION_MISMATCH = -1;
1250
1281
  function decodeValue(decoder, operation, ref, index, type, bytes, it, allChanges) {
1251
- const $root = decoder.$root;
1282
+ const $root = decoder.root;
1252
1283
  const previousValue = ref[$getByIndex](index);
1253
1284
  let value;
1254
1285
  if ((operation & exports.OPERATION.DELETE) === exports.OPERATION.DELETE) {
@@ -1354,6 +1385,7 @@
1354
1385
  // skip early if field is not defined
1355
1386
  const field = metadata[index];
1356
1387
  if (field === undefined) {
1388
+ console.warn("@colyseus/schema: field not defined at", { index, ref: ref.constructor.name, metadata });
1357
1389
  return DEFINITION_MISMATCH;
1358
1390
  }
1359
1391
  const { value, previousValue } = decodeValue(decoder, operation, ref, index, metadata[field].type, bytes, it, allChanges);
@@ -1434,7 +1466,8 @@
1434
1466
  };
1435
1467
  const decodeArray = function (decoder, bytes, it, ref, allChanges) {
1436
1468
  // "uncompressed" index + operation (array/map items)
1437
- const operation = bytes[it.offset++];
1469
+ let operation = bytes[it.offset++];
1470
+ let index;
1438
1471
  if (operation === exports.OPERATION.CLEAR) {
1439
1472
  //
1440
1473
  // When decoding:
@@ -1448,8 +1481,8 @@
1448
1481
  else if (operation === exports.OPERATION.DELETE_BY_REFID) {
1449
1482
  // TODO: refactor here, try to follow same flow as below
1450
1483
  const refId = number(bytes, it);
1451
- const previousValue = decoder.$root.refs.get(refId);
1452
- const index = ref.findIndex((value) => value === previousValue);
1484
+ const previousValue = decoder.root.refs.get(refId);
1485
+ index = ref.findIndex((value) => value === previousValue);
1453
1486
  ref[$deleteByIndex](index);
1454
1487
  allChanges.push({
1455
1488
  ref,
@@ -1462,7 +1495,18 @@
1462
1495
  });
1463
1496
  return;
1464
1497
  }
1465
- const index = number(bytes, it);
1498
+ else if (operation === exports.OPERATION.ADD_BY_REFID) {
1499
+ // operation = OPERATION.ADD;
1500
+ const refId = number(bytes, it);
1501
+ const itemByRefId = decoder.root.refs.get(refId);
1502
+ // use existing index, or push new value
1503
+ index = (itemByRefId)
1504
+ ? ref.findIndex((value) => value === itemByRefId)
1505
+ : ref.length;
1506
+ }
1507
+ else {
1508
+ index = number(bytes, it);
1509
+ }
1466
1510
  const type = ref[$childType];
1467
1511
  let dynamicIndex = index;
1468
1512
  const { value, previousValue } = decodeValue(decoder, operation, ref, index, type, bytes, it, allChanges);
@@ -1623,7 +1667,6 @@
1623
1667
  }
1624
1668
  const changeTree = this[$changes];
1625
1669
  changeTree.indexedOperation(length, exports.OPERATION.ADD, this.items.length);
1626
- // changeTree.indexes[length] = length;
1627
1670
  this.items.push(value);
1628
1671
  this.tmpItems.push(value);
1629
1672
  //
@@ -1871,14 +1914,6 @@
1871
1914
  lastIndexOf(searchElement, fromIndex = this.length - 1) {
1872
1915
  return this.items.lastIndexOf(searchElement, fromIndex);
1873
1916
  }
1874
- /**
1875
- * Determines whether all the members of an array satisfy the specified test.
1876
- * @param callbackfn A function that accepts up to three arguments. The every method calls
1877
- * the callbackfn function for each element in the array until the callbackfn returns a value
1878
- * which is coercible to the Boolean value false, or until the end of the array.
1879
- * @param thisArg An object to which the this keyword can refer in the callbackfn function.
1880
- * If thisArg is omitted, undefined is used as the this value.
1881
- */
1882
1917
  every(callbackfn, thisArg) {
1883
1918
  return this.items.every(callbackfn, thisArg);
1884
1919
  }
@@ -2322,7 +2357,6 @@
2322
2357
  }
2323
2358
  registerType("map", { constructor: MapSchema });
2324
2359
 
2325
- const DEFAULT_VIEW_TAG = -1;
2326
2360
  class TypeContext {
2327
2361
  /**
2328
2362
  * For inheritance support
@@ -2344,6 +2378,7 @@
2344
2378
  this.types = {};
2345
2379
  this.schemas = new Map();
2346
2380
  this.hasFilters = false;
2381
+ this.parentFiltered = {};
2347
2382
  if (rootClass) {
2348
2383
  this.discoverTypes(rootClass);
2349
2384
  }
@@ -2360,32 +2395,46 @@
2360
2395
  return false;
2361
2396
  }
2362
2397
  this.types[typeid] = schema;
2398
+ //
2399
+ // Workaround to allow using an empty Schema (with no `@type()` fields)
2400
+ //
2401
+ if (schema[Symbol.metadata] === undefined) {
2402
+ Metadata.init(schema);
2403
+ }
2363
2404
  this.schemas.set(schema, typeid);
2364
2405
  return true;
2365
2406
  }
2366
2407
  getTypeId(klass) {
2367
2408
  return this.schemas.get(klass);
2368
2409
  }
2369
- discoverTypes(klass) {
2410
+ discoverTypes(klass, parentIndex, parentFieldViewTag) {
2370
2411
  if (!this.add(klass)) {
2371
2412
  return;
2372
2413
  }
2373
2414
  // add classes inherited from this base class
2374
2415
  TypeContext.inheritedTypes.get(klass)?.forEach((child) => {
2375
- this.discoverTypes(child);
2416
+ this.discoverTypes(child, parentIndex, parentFieldViewTag);
2376
2417
  });
2377
- // skip if no fields are defined for this class.
2378
- if (klass[Symbol.metadata] === undefined) {
2379
- klass[Symbol.metadata] = {};
2380
- }
2381
- // const metadata = Metadata.getFor(klass);
2382
- const metadata = klass[Symbol.metadata];
2418
+ const metadata = (klass[Symbol.metadata] ??= {});
2383
2419
  // if any schema/field has filters, mark "context" as having filters.
2384
2420
  if (metadata[-2]) {
2385
2421
  this.hasFilters = true;
2386
2422
  }
2423
+ if (parentFieldViewTag !== undefined) {
2424
+ this.parentFiltered[`${this.schemas.get(klass)}-${parentIndex}`] = true;
2425
+ }
2387
2426
  for (const field in metadata) {
2427
+ // //
2428
+ // // Modify the field's metadata to include the parent field's view tag
2429
+ // //
2430
+ // if (
2431
+ // parentFieldViewTag !== undefined &&
2432
+ // metadata[field].tag === undefined
2433
+ // ) {
2434
+ // metadata[field].tag = parentFieldViewTag;
2435
+ // }
2388
2436
  const fieldType = metadata[field].type;
2437
+ const viewTag = metadata[field].tag;
2389
2438
  if (typeof (fieldType) === "string") {
2390
2439
  continue;
2391
2440
  }
@@ -2394,10 +2443,10 @@
2394
2443
  if (type === "string") {
2395
2444
  continue;
2396
2445
  }
2397
- this.discoverTypes(type);
2446
+ this.discoverTypes(type, metadata[field].index, viewTag);
2398
2447
  }
2399
2448
  else if (typeof (fieldType) === "function") {
2400
- this.discoverTypes(fieldType);
2449
+ this.discoverTypes(fieldType, viewTag);
2401
2450
  }
2402
2451
  else {
2403
2452
  const type = Object.values(fieldType)[0];
@@ -2405,11 +2454,13 @@
2405
2454
  if (typeof (type) === "string") {
2406
2455
  continue;
2407
2456
  }
2408
- this.discoverTypes(type);
2457
+ this.discoverTypes(type, metadata[field].index, viewTag);
2409
2458
  }
2410
2459
  }
2411
2460
  }
2412
2461
  }
2462
+
2463
+ const DEFAULT_VIEW_TAG = -1;
2413
2464
  /**
2414
2465
  * [See documentation](https://docs.colyseus.io/state/schema/)
2415
2466
  *
@@ -2557,6 +2608,7 @@
2557
2608
  const constructor = target.constructor;
2558
2609
  const parentClass = Object.getPrototypeOf(constructor);
2559
2610
  const parentMetadata = parentClass[Symbol.metadata];
2611
+ // TODO: use Metadata.initialize()
2560
2612
  const metadata = (constructor[Symbol.metadata] ??= Object.assign({}, constructor[Symbol.metadata], parentMetadata ?? Object.create(null)));
2561
2613
  if (!metadata[fieldName]) {
2562
2614
  //
@@ -2581,8 +2633,8 @@
2581
2633
  // for inheritance support
2582
2634
  TypeContext.register(constructor);
2583
2635
  const parentClass = Object.getPrototypeOf(constructor);
2584
- const parentMetadata = parentClass[Symbol.metadata];
2585
- const metadata = (constructor[Symbol.metadata] ??= Object.assign({}, constructor[Symbol.metadata], parentMetadata ?? Object.create(null)));
2636
+ const parentMetadata = parentClass && parentClass[Symbol.metadata];
2637
+ const metadata = Metadata.initialize(constructor, parentMetadata);
2586
2638
  let fieldIndex;
2587
2639
  /**
2588
2640
  * skip if descriptor already exists for this field (`@deprecated()`)
@@ -2963,13 +3015,13 @@
2963
3015
  }
2964
3016
  return output;
2965
3017
  }
2966
- static debugChangesDeep(ref) {
3018
+ static debugChangesDeep(ref, changeSetName = "changes") {
2967
3019
  let output = "";
2968
3020
  const rootChangeTree = ref[$changes];
2969
3021
  const changeTrees = new Map();
2970
3022
  let totalInstances = 0;
2971
3023
  let totalOperations = 0;
2972
- for (const [changeTree, changes] of (rootChangeTree.root.changes.entries())) {
3024
+ for (const [changeTree, changes] of (rootChangeTree.root[changeSetName].entries())) {
2973
3025
  let includeChangeTree = false;
2974
3026
  let parentChangeTrees = [];
2975
3027
  let parentChangeTree = changeTree.parent?.[$changes];
@@ -3355,6 +3407,8 @@
3355
3407
  OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
3356
3408
  PERFORMANCE OF THIS SOFTWARE.
3357
3409
  ***************************************************************************** */
3410
+ /* global Reflect, Promise, SuppressedError, Symbol */
3411
+
3358
3412
 
3359
3413
  function __decorate(decorators, target, key, desc) {
3360
3414
  var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
@@ -3368,29 +3422,68 @@
3368
3422
  return e.name = "SuppressedError", e.error = error, e.suppressed = suppressed, e;
3369
3423
  };
3370
3424
 
3425
+ class Root {
3426
+ constructor(types) {
3427
+ this.types = types;
3428
+ this.nextUniqueId = 0;
3429
+ this.refCount = new WeakMap();
3430
+ // all changes
3431
+ this.allChanges = new Map();
3432
+ this.allFilteredChanges = new Map();
3433
+ // pending changes to be encoded
3434
+ this.changes = new Map();
3435
+ this.filteredChanges = new Map();
3436
+ }
3437
+ getNextUniqueId() {
3438
+ return this.nextUniqueId++;
3439
+ }
3440
+ add(changeTree) {
3441
+ const refCount = this.refCount.get(changeTree) || 0;
3442
+ this.refCount.set(changeTree, refCount + 1);
3443
+ }
3444
+ remove(changeTree) {
3445
+ const refCount = this.refCount.get(changeTree);
3446
+ if (refCount <= 1) {
3447
+ this.allChanges.delete(changeTree);
3448
+ this.changes.delete(changeTree);
3449
+ if (changeTree.isFiltered || changeTree.isPartiallyFiltered) {
3450
+ this.allFilteredChanges.delete(changeTree);
3451
+ this.filteredChanges.delete(changeTree);
3452
+ }
3453
+ this.refCount.delete(changeTree);
3454
+ }
3455
+ else {
3456
+ this.refCount.set(changeTree, refCount - 1);
3457
+ }
3458
+ changeTree.forEachChild((child, _) => this.remove(child));
3459
+ }
3460
+ clear() {
3461
+ this.changes.clear();
3462
+ }
3463
+ }
3464
+
3371
3465
  class Encoder {
3372
3466
  static { this.BUFFER_SIZE = 8 * 1024; } // 8KB
3373
- constructor(root) {
3467
+ constructor(state) {
3374
3468
  this.sharedBuffer = Buffer.allocUnsafeSlow(Encoder.BUFFER_SIZE);
3375
- this.setRoot(root);
3376
3469
  //
3377
3470
  // TODO: cache and restore "Context" based on root schema
3378
3471
  // (to avoid creating a new context for every new room)
3379
3472
  //
3380
- this.context = new TypeContext(root.constructor);
3473
+ this.context = new TypeContext(state.constructor);
3474
+ this.root = new Root(this.context);
3475
+ this.setState(state);
3381
3476
  // console.log(">>>>>>>>>>>>>>>> Encoder types");
3382
3477
  // this.context.schemas.forEach((id, schema) => {
3383
3478
  // console.log("type:", id, schema.name, Object.keys(schema[Symbol.metadata]));
3384
3479
  // });
3385
3480
  }
3386
- setRoot(state) {
3387
- this.root = new Root();
3481
+ setState(state) {
3388
3482
  this.state = state;
3389
- state[$changes].setRoot(this.root);
3483
+ this.state[$changes].setRoot(this.root);
3390
3484
  }
3391
- encode(it = { offset: 0 }, view, bytes = this.sharedBuffer, changeTrees = this.root.changes) {
3392
- const initialOffset = it.offset; // cache current offset in case we need to resize the buffer
3393
- const isEncodeAll = this.root.allChanges === changeTrees;
3485
+ encode(it = { offset: 0 }, view, buffer = this.sharedBuffer, changeTrees = this.root.changes, isEncodeAll = this.root.allChanges === changeTrees, initialOffset = it.offset // cache current offset in case we need to resize the buffer
3486
+ ) {
3394
3487
  const hasView = (view !== undefined);
3395
3488
  const rootChangeTree = this.state[$changes];
3396
3489
  const changeTreesIterator = changeTrees.entries();
@@ -3399,6 +3492,12 @@
3399
3492
  const ctor = ref['constructor'];
3400
3493
  const encoder = ctor[$encoder];
3401
3494
  const filter = ctor[$filter];
3495
+ // try { throw new Error(); } catch (e) {
3496
+ // // only print if not coming from Reflection.ts
3497
+ // if (!e.stack.includes("src/Reflection.ts")) {
3498
+ // console.log("ChangeTree:", { ref: ref.constructor.name, });
3499
+ // }
3500
+ // }
3402
3501
  if (hasView) {
3403
3502
  if (!view.items.has(changeTree)) {
3404
3503
  view.invisible.add(changeTree);
@@ -3409,9 +3508,10 @@
3409
3508
  }
3410
3509
  }
3411
3510
  // skip root `refId` if it's the first change tree
3412
- if (it.offset !== initialOffset || changeTree !== rootChangeTree) {
3413
- bytes[it.offset++] = SWITCH_TO_STRUCTURE & 255;
3414
- number$1(bytes, changeTree.refId, it);
3511
+ // (unless it "hasView", which will need to revisit the root)
3512
+ if (hasView || it.offset > initialOffset || changeTree !== rootChangeTree) {
3513
+ buffer[it.offset++] = SWITCH_TO_STRUCTURE & 255;
3514
+ number$1(buffer, changeTree.refId, it);
3415
3515
  }
3416
3516
  const changesIterator = changes.entries();
3417
3517
  for (const [fieldIndex, operation] of changesIterator) {
@@ -3423,27 +3523,39 @@
3423
3523
  // TODO: avoid checking if no view tags were defined
3424
3524
  //
3425
3525
  if (filter && !filter(ref, fieldIndex, view)) {
3426
- // console.log("SKIP FIELD:", { ref: changeTree.ref.constructor.name, fieldIndex, })
3427
3526
  // console.log("ADD AS INVISIBLE:", fieldIndex, changeTree.ref.constructor.name)
3428
3527
  // view?.invisible.add(changeTree);
3429
3528
  continue;
3430
3529
  }
3431
- // console.log("WILL ENCODE", {
3432
- // ref: changeTree.ref.constructor.name,
3433
- // fieldIndex,
3434
- // operation: OPERATION[operation],
3435
- // });
3436
- encoder(this, bytes, changeTree, fieldIndex, operation, it, isEncodeAll, hasView);
3530
+ // try { throw new Error(); } catch (e) {
3531
+ // // only print if not coming from Reflection.ts
3532
+ // if (!e.stack.includes("src/Reflection.ts")) {
3533
+ // console.log("WILL ENCODE", {
3534
+ // ref: changeTree.ref.constructor.name,
3535
+ // fieldIndex,
3536
+ // operation: OPERATION[operation],
3537
+ // });
3538
+ // }
3539
+ // }
3540
+ encoder(this, buffer, changeTree, fieldIndex, operation, it, isEncodeAll, hasView);
3437
3541
  }
3438
3542
  }
3439
- if (it.offset > bytes.byteLength) {
3440
- const newSize = getNextPowerOf2(this.sharedBuffer.byteLength * 2);
3441
- console.warn("@colyseus/schema encode buffer overflow. Current buffer size: " + bytes.byteLength + ", encoding offset: " + it.offset + ", new size: " + newSize);
3543
+ if (it.offset > buffer.byteLength) {
3544
+ const newSize = getNextPowerOf2(buffer.byteLength * 2);
3545
+ console.warn(`@colyseus/schema buffer overflow. Encoded state is higher than default BUFFER_SIZE. Use the following to increase default BUFFER_SIZE:
3546
+
3547
+ import { Encoder } from "@colyseus/schema";
3548
+ Encoder.BUFFER_SIZE = ${Math.round(newSize / 1024)} * 1024; // ${Math.round(newSize / 1024)} KB
3549
+ `);
3442
3550
  //
3443
3551
  // resize buffer and re-encode (TODO: can we avoid re-encoding here?)
3444
3552
  //
3445
- this.sharedBuffer = Buffer.allocUnsafeSlow(newSize);
3446
- return this.encode({ offset: initialOffset }, view);
3553
+ buffer = Buffer.allocUnsafeSlow(newSize);
3554
+ // assign resized buffer to local sharedBuffer
3555
+ if (buffer === this.sharedBuffer) {
3556
+ this.sharedBuffer = buffer;
3557
+ }
3558
+ return this.encode({ offset: initialOffset }, view, buffer, changeTrees, isEncodeAll);
3447
3559
  }
3448
3560
  else {
3449
3561
  //
@@ -3455,42 +3567,47 @@
3455
3567
  //
3456
3568
  this.onEndEncode(changeTrees);
3457
3569
  }
3458
- // return bytes;
3459
- return bytes.slice(0, it.offset);
3570
+ return buffer.subarray(0, it.offset);
3460
3571
  }
3461
3572
  }
3462
- encodeAll(it = { offset: 0 }) {
3463
- // console.log(`encodeAll(), this.$root.allChanges (${this.$root.allChanges.size})`);
3464
- // Array.from(this.$root.allChanges.entries()).map((item) => {
3465
- // console.log("->", item[0].refId, item[0].ref.toJSON());
3466
- // });
3467
- return this.encode(it, undefined, this.sharedBuffer, this.root.allChanges);
3573
+ encodeAll(it = { offset: 0 }, buffer = this.sharedBuffer) {
3574
+ // console.log(`\nencodeAll(), this.root.allChanges (${this.root.allChanges.size})`);
3575
+ // this.debugChanges("allChanges");
3576
+ return this.encode(it, undefined, buffer, this.root.allChanges, true);
3468
3577
  }
3469
3578
  encodeAllView(view, sharedOffset, it, bytes = this.sharedBuffer) {
3470
3579
  const viewOffset = it.offset;
3471
- // console.log(`encodeAllView(), this.$root.allFilteredChanges (${this.$root.allFilteredChanges.size})`);
3472
- // this.debugAllFilteredChanges();
3580
+ // console.log(`\nencodeAllView(), this.root.allFilteredChanges (${this.root.allFilteredChanges.size})`);
3581
+ // this.debugChanges("allFilteredChanges");
3473
3582
  // try to encode "filtered" changes
3474
- this.encode(it, view, bytes, this.root.allFilteredChanges);
3583
+ this.encode(it, view, bytes, this.root.allFilteredChanges, true, viewOffset);
3475
3584
  return Buffer.concat([
3476
- bytes.slice(0, sharedOffset),
3477
- bytes.slice(viewOffset, it.offset)
3585
+ bytes.subarray(0, sharedOffset),
3586
+ bytes.subarray(viewOffset, it.offset)
3478
3587
  ]);
3479
3588
  }
3480
- // debugAllFilteredChanges() {
3481
- // Array.from(this.$root.allFilteredChanges.entries()).map((item) => {
3482
- // console.log("->", { refId: item[0].refId }, item[0].ref.toJSON());
3483
- // if (Array.isArray(item[0].ref.toJSON())) {
3484
- // item[1].forEach((op, key) => {
3485
- // console.log(" ->", { key, op: OPERATION[op] });
3486
- // })
3487
- // }
3488
- // });
3489
- // }
3589
+ debugChanges(field) {
3590
+ const changeSet = (typeof (field) === "string")
3591
+ ? this.root[field]
3592
+ : field;
3593
+ Array.from(changeSet.entries()).map((item) => {
3594
+ const metadata = item[0].ref.constructor[Symbol.metadata];
3595
+ console.log("->", { ref: item[0].ref.constructor.name, refId: item[0].refId, changes: item[1].size });
3596
+ item[1].forEach((op, index) => {
3597
+ console.log(" ->", {
3598
+ index,
3599
+ field: metadata?.[index],
3600
+ op: exports.OPERATION[op],
3601
+ });
3602
+ });
3603
+ });
3604
+ }
3490
3605
  encodeView(view, sharedOffset, it, bytes = this.sharedBuffer) {
3491
3606
  const viewOffset = it.offset;
3492
- // try to encode "filtered" changes
3493
- this.encode(it, view, bytes, this.root.filteredChanges);
3607
+ // console.log(`\nencodeView(), view.changes (${view.changes.size})`);
3608
+ // this.debugChanges(view.changes);
3609
+ // console.log(`\nencodeView(), this.root.filteredChanges (${this.root.filteredChanges.size})`);
3610
+ // this.debugChanges("filteredChanges");
3494
3611
  // encode visibility changes (add/remove for this view)
3495
3612
  const viewChangesIterator = view.changes.entries();
3496
3613
  for (const [changeTree, changes] of viewChangesIterator) {
@@ -3517,9 +3634,11 @@
3517
3634
  //
3518
3635
  // clear "view" changes after encoding
3519
3636
  view.changes.clear();
3637
+ // try to encode "filtered" changes
3638
+ this.encode(it, view, bytes, this.root.filteredChanges, false, viewOffset);
3520
3639
  return Buffer.concat([
3521
- bytes.slice(0, sharedOffset),
3522
- bytes.slice(viewOffset, it.offset)
3640
+ bytes.subarray(0, sharedOffset),
3641
+ bytes.subarray(viewOffset, it.offset)
3523
3642
  ]);
3524
3643
  }
3525
3644
  onEndEncode(changeTrees = this.root.changes) {
@@ -3689,21 +3808,21 @@
3689
3808
  class Decoder {
3690
3809
  constructor(root, context) {
3691
3810
  this.currentRefId = 0;
3692
- this.setRoot(root);
3811
+ this.setState(root);
3693
3812
  this.context = context || new TypeContext(root.constructor);
3694
3813
  // console.log(">>>>>>>>>>>>>>>> Decoder types");
3695
3814
  // this.context.schemas.forEach((id, schema) => {
3696
3815
  // console.log("type:", id, schema.name, Object.keys(schema[Symbol.metadata]));
3697
3816
  // });
3698
3817
  }
3699
- setRoot(root) {
3818
+ setState(root) {
3700
3819
  this.state = root;
3701
- this.$root = new ReferenceTracker();
3702
- this.$root.addRef(0, root);
3820
+ this.root = new ReferenceTracker();
3821
+ this.root.addRef(0, root);
3703
3822
  }
3704
3823
  decode(bytes, it = { offset: 0 }, ref = this.state) {
3705
3824
  const allChanges = [];
3706
- const $root = this.$root;
3825
+ const $root = this.root;
3707
3826
  const totalBytes = bytes.byteLength;
3708
3827
  let decoder = ref['constructor'][$decoder];
3709
3828
  this.currentRefId = 0;
@@ -3784,7 +3903,7 @@
3784
3903
  previousValue: value
3785
3904
  });
3786
3905
  if (needRemoveRef) {
3787
- this.$root.removeRef(this.$root.refIds.get(value));
3906
+ this.root.removeRef(this.root.refIds.get(value));
3788
3907
  }
3789
3908
  });
3790
3909
  }
@@ -3824,10 +3943,8 @@
3824
3943
  super(...arguments);
3825
3944
  this.types = new ArraySchema();
3826
3945
  }
3827
- static encode(instance, context) {
3828
- if (!context) {
3829
- context = new TypeContext(instance.constructor);
3830
- }
3946
+ static encode(instance, context, it = { offset: 0 }) {
3947
+ context ??= new TypeContext(instance.constructor);
3831
3948
  const reflection = new Reflection();
3832
3949
  const encoder = new Encoder(reflection);
3833
3950
  const buildType = (currentType, metadata) => {
@@ -3881,7 +3998,6 @@
3881
3998
  }
3882
3999
  buildType(type, klass[Symbol.metadata]);
3883
4000
  }
3884
- const it = { offset: 0 };
3885
4001
  const buf = encoder.encodeAll(it);
3886
4002
  return Buffer.from(buf, 0, it.offset);
3887
4003
  }
@@ -3889,59 +4005,314 @@
3889
4005
  const reflection = new Reflection();
3890
4006
  const reflectionDecoder = new Decoder(reflection);
3891
4007
  reflectionDecoder.decode(bytes, it);
3892
- const context = new TypeContext();
3893
- const schemaTypes = reflection.types.reduce((types, reflectionType) => {
3894
- const parentKlass = types[reflectionType.extendsId] || Schema;
3895
- const schema = class _ extends parentKlass {
4008
+ const typeContext = new TypeContext();
4009
+ // 1st pass, initialize metadata + inheritance
4010
+ reflection.types.forEach((reflectionType) => {
4011
+ const parentClass = typeContext.get(reflectionType.extendsId) ?? Schema;
4012
+ const schema = class _ extends parentClass {
3896
4013
  };
3897
- // const _metadata = Object.create(_classSuper[Symbol.metadata] ?? null);
3898
- const _metadata = parentKlass && parentKlass[Symbol.metadata] || Object.create(null);
3899
- Object.defineProperty(schema, Symbol.metadata, { enumerable: true, configurable: true, writable: true, value: _metadata });
4014
+ const parentMetadata = parentClass[Symbol.metadata];
3900
4015
  // register for inheritance support
3901
4016
  TypeContext.register(schema);
3902
- const typeid = reflectionType.id;
3903
- types[typeid] = schema;
3904
- context.add(schema, typeid);
3905
- return types;
4017
+ // for inheritance support
4018
+ Metadata.initialize(schema, parentMetadata);
4019
+ typeContext.add(schema, reflectionType.id);
3906
4020
  }, {});
4021
+ // 2nd pass, set fields
3907
4022
  reflection.types.forEach((reflectionType) => {
3908
- const schemaType = schemaTypes[reflectionType.id];
4023
+ const schemaType = typeContext.get(reflectionType.id);
3909
4024
  const metadata = schemaType[Symbol.metadata];
3910
- const parentKlass = reflection.types[reflectionType.extendsId];
3911
- const parentFieldIndex = parentKlass && parentKlass.fields.length || 0;
4025
+ // FIXME: use metadata[-1] to get field count
4026
+ const parentFieldIndex = 0;
4027
+ // console.log("--------------------");
4028
+ // // console.log("reflectionType", reflectionType.toJSON());
4029
+ // console.log("reflectionType.fields", reflectionType.fields.toJSON());
4030
+ // console.log("parentFieldIndex", parentFieldIndex);
4031
+ //
4032
+ // FIXME: set fields using parentKlass as well
4033
+ // currently the fields are duplicated on inherited classes
4034
+ //
4035
+ // // const parentKlass = reflection.types[reflectionType.extendsId];
4036
+ // // parentKlass.fields
3912
4037
  reflectionType.fields.forEach((field, i) => {
3913
4038
  const fieldIndex = parentFieldIndex + i;
3914
4039
  if (field.referencedType !== undefined) {
3915
4040
  let fieldType = field.type;
3916
- let refType = schemaTypes[field.referencedType];
4041
+ let refType = typeContext.get(field.referencedType);
3917
4042
  // map or array of primitive type (-1)
3918
4043
  if (!refType) {
3919
4044
  const typeInfo = field.type.split(":");
3920
4045
  fieldType = typeInfo[0];
3921
- refType = typeInfo[1];
4046
+ refType = typeInfo[1]; // string
3922
4047
  }
3923
4048
  if (fieldType === "ref") {
3924
- // type(refType)(schemaType.prototype, field.name);
3925
4049
  Metadata.addField(metadata, fieldIndex, field.name, refType);
3926
4050
  }
3927
4051
  else {
3928
- // type({ [fieldType]: refType } as DefinitionType)(schemaType.prototype, field.name);
3929
4052
  Metadata.addField(metadata, fieldIndex, field.name, { [fieldType]: refType });
3930
4053
  }
3931
4054
  }
3932
4055
  else {
3933
- // type(field.type as PrimitiveType)(schemaType.prototype, field.name);
3934
4056
  Metadata.addField(metadata, fieldIndex, field.name, field.type);
3935
4057
  }
3936
4058
  });
3937
4059
  });
3938
- return new (schemaTypes[0])();
4060
+ // @ts-ignore
4061
+ return new (typeContext.get(0))();
3939
4062
  }
3940
4063
  }
3941
4064
  __decorate([
3942
4065
  type([ReflectionType])
3943
4066
  ], Reflection.prototype, "types", void 0);
3944
4067
 
4068
+ function getDecoderStateCallbacks(decoder) {
4069
+ const $root = decoder.root;
4070
+ const callbacks = $root.callbacks;
4071
+ const onAddCalls = new WeakMap();
4072
+ let currentOnAddCallback;
4073
+ decoder.triggerChanges = function (allChanges) {
4074
+ const uniqueRefIds = new Set();
4075
+ for (let i = 0, l = allChanges.length; i < l; i++) {
4076
+ const change = allChanges[i];
4077
+ const refId = change.refId;
4078
+ const ref = change.ref;
4079
+ const $callbacks = callbacks[refId];
4080
+ if (!$callbacks) {
4081
+ continue;
4082
+ }
4083
+ //
4084
+ // trigger onRemove on child structure.
4085
+ //
4086
+ if ((change.op & exports.OPERATION.DELETE) === exports.OPERATION.DELETE &&
4087
+ change.previousValue instanceof Schema) {
4088
+ const deleteCallbacks = callbacks[$root.refIds.get(change.previousValue)]?.[exports.OPERATION.DELETE];
4089
+ for (let i = deleteCallbacks?.length - 1; i >= 0; i--) {
4090
+ deleteCallbacks[i]();
4091
+ }
4092
+ }
4093
+ if (ref instanceof Schema) {
4094
+ //
4095
+ // Handle schema instance
4096
+ //
4097
+ if (!uniqueRefIds.has(refId)) {
4098
+ // trigger onChange
4099
+ const replaceCallbacks = $callbacks?.[exports.OPERATION.REPLACE];
4100
+ for (let i = replaceCallbacks?.length - 1; i >= 0; i--) {
4101
+ replaceCallbacks[i]();
4102
+ // try {
4103
+ // } catch (e) {
4104
+ // console.error(e);
4105
+ // }
4106
+ }
4107
+ }
4108
+ if ($callbacks.hasOwnProperty(change.field)) {
4109
+ const fieldCallbacks = $callbacks[change.field];
4110
+ for (let i = fieldCallbacks?.length - 1; i >= 0; i--) {
4111
+ fieldCallbacks[i](change.value, change.previousValue);
4112
+ // try {
4113
+ // } catch (e) {
4114
+ // console.error(e);
4115
+ // }
4116
+ }
4117
+ }
4118
+ }
4119
+ else {
4120
+ //
4121
+ // Handle collection of items
4122
+ //
4123
+ if ((change.op & exports.OPERATION.DELETE) === exports.OPERATION.DELETE) {
4124
+ //
4125
+ // FIXME: `previousValue` should always be available.
4126
+ //
4127
+ if (change.previousValue !== undefined) {
4128
+ // triger onRemove
4129
+ const deleteCallbacks = $callbacks[exports.OPERATION.DELETE];
4130
+ for (let i = deleteCallbacks?.length - 1; i >= 0; i--) {
4131
+ deleteCallbacks[i](change.previousValue, change.dynamicIndex ?? change.field);
4132
+ }
4133
+ }
4134
+ // Handle DELETE_AND_ADD operations
4135
+ if ((change.op & exports.OPERATION.ADD) === exports.OPERATION.ADD) {
4136
+ const addCallbacks = $callbacks[exports.OPERATION.ADD];
4137
+ for (let i = addCallbacks?.length - 1; i >= 0; i--) {
4138
+ addCallbacks[i](change.value, change.dynamicIndex ?? change.field);
4139
+ }
4140
+ }
4141
+ }
4142
+ else if ((change.op & exports.OPERATION.ADD) === exports.OPERATION.ADD && change.previousValue === undefined) {
4143
+ // triger onAdd
4144
+ const addCallbacks = $callbacks[exports.OPERATION.ADD];
4145
+ for (let i = addCallbacks?.length - 1; i >= 0; i--) {
4146
+ addCallbacks[i](change.value, change.dynamicIndex ?? change.field);
4147
+ }
4148
+ }
4149
+ // trigger onChange
4150
+ if (change.value !== change.previousValue) {
4151
+ const replaceCallbacks = $callbacks[exports.OPERATION.REPLACE];
4152
+ for (let i = replaceCallbacks?.length - 1; i >= 0; i--) {
4153
+ replaceCallbacks[i](change.value, change.dynamicIndex ?? change.field);
4154
+ }
4155
+ }
4156
+ }
4157
+ uniqueRefIds.add(refId);
4158
+ }
4159
+ };
4160
+ function getProxy(metadataOrType, context) {
4161
+ let metadata = context.instance?.constructor[Symbol.metadata] || metadataOrType;
4162
+ let isCollection = ((context.instance && typeof (context.instance['forEach']) === "function") ||
4163
+ (metadataOrType && typeof (metadataOrType[Symbol.metadata]) === "undefined"));
4164
+ if (metadata && !isCollection) {
4165
+ const onAddListen = function (ref, prop, callback, immediate) {
4166
+ // immediate trigger
4167
+ if (immediate &&
4168
+ context.instance[prop] !== undefined &&
4169
+ !onAddCalls.has(currentOnAddCallback) // Workaround for https://github.com/colyseus/schema/issues/147
4170
+ ) {
4171
+ callback(context.instance[prop], undefined);
4172
+ }
4173
+ return $root.addCallback($root.refIds.get(ref), prop, callback);
4174
+ };
4175
+ /**
4176
+ * Schema instances
4177
+ */
4178
+ return new Proxy({
4179
+ listen: function listen(prop, callback, immediate = true) {
4180
+ if (context.instance) {
4181
+ return onAddListen(context.instance, prop, callback, immediate);
4182
+ }
4183
+ else {
4184
+ // collection instance not received yet
4185
+ let detachCallback = () => { };
4186
+ context.onInstanceAvailable((ref, existing) => {
4187
+ detachCallback = onAddListen(ref, prop, callback, immediate && existing && !onAddCalls.has(currentOnAddCallback));
4188
+ });
4189
+ return () => detachCallback();
4190
+ }
4191
+ },
4192
+ onChange: function onChange(callback) {
4193
+ return $root.addCallback($root.refIds.get(context.instance), exports.OPERATION.REPLACE, callback);
4194
+ },
4195
+ //
4196
+ // TODO: refactor `bindTo()` implementation.
4197
+ // There is room for improvement.
4198
+ //
4199
+ bindTo: function bindTo(targetObject, properties) {
4200
+ if (!properties) {
4201
+ properties = Object.keys(metadata);
4202
+ }
4203
+ return $root.addCallback($root.refIds.get(context.instance), exports.OPERATION.REPLACE, () => {
4204
+ properties.forEach((prop) => targetObject[prop] = context.instance[prop]);
4205
+ });
4206
+ }
4207
+ }, {
4208
+ get(target, prop) {
4209
+ if (metadata[prop]) {
4210
+ const instance = context.instance?.[prop];
4211
+ const onInstanceAvailable = ((callback) => {
4212
+ const unbind = $(context.instance).listen(prop, (value, _) => {
4213
+ callback(value, false);
4214
+ // FIXME: by "unbinding" the callback here,
4215
+ // it will not support when the server
4216
+ // re-instantiates the instance.
4217
+ //
4218
+ unbind?.();
4219
+ }, false);
4220
+ // has existing value
4221
+ if ($root.refIds.get(instance) !== undefined) {
4222
+ callback(instance, true);
4223
+ }
4224
+ });
4225
+ return getProxy(metadata[prop].type, {
4226
+ // make sure refId is available, otherwise need to wait for the instance to be available.
4227
+ instance: ($root.refIds.get(instance) && instance),
4228
+ parentInstance: context.instance,
4229
+ onInstanceAvailable,
4230
+ });
4231
+ }
4232
+ else {
4233
+ // accessing the function
4234
+ return target[prop];
4235
+ }
4236
+ },
4237
+ has(target, prop) { return metadata[prop] !== undefined; },
4238
+ set(_, _1, _2) { throw new Error("not allowed"); },
4239
+ deleteProperty(_, _1) { throw new Error("not allowed"); },
4240
+ });
4241
+ }
4242
+ else {
4243
+ /**
4244
+ * Collection instances
4245
+ */
4246
+ const onAdd = function (ref, callback, immediate) {
4247
+ // Trigger callback on existing items
4248
+ if (immediate) {
4249
+ ref.forEach((v, k) => callback(v, k));
4250
+ }
4251
+ return $root.addCallback($root.refIds.get(ref), exports.OPERATION.ADD, (value, key) => {
4252
+ onAddCalls.set(callback, true);
4253
+ currentOnAddCallback = callback;
4254
+ callback(value, key);
4255
+ onAddCalls.delete(callback);
4256
+ currentOnAddCallback = undefined;
4257
+ });
4258
+ };
4259
+ const onRemove = function (ref, callback) {
4260
+ return $root.addCallback($root.refIds.get(ref), exports.OPERATION.DELETE, callback);
4261
+ };
4262
+ return new Proxy({
4263
+ onAdd: function (callback, immediate = true) {
4264
+ //
4265
+ // https://github.com/colyseus/schema/issues/147
4266
+ // If parent instance has "onAdd" registered, avoid triggering immediate callback.
4267
+ //
4268
+ if (context.instance) {
4269
+ return onAdd(context.instance, callback, immediate && !onAddCalls.has(currentOnAddCallback));
4270
+ }
4271
+ else if (context.onInstanceAvailable) {
4272
+ // collection instance not received yet
4273
+ let detachCallback = () => { };
4274
+ context.onInstanceAvailable((ref, existing) => {
4275
+ detachCallback = onAdd(ref, callback, immediate && existing && !onAddCalls.has(currentOnAddCallback));
4276
+ });
4277
+ return () => detachCallback();
4278
+ }
4279
+ },
4280
+ onRemove: function (callback) {
4281
+ if (context.onInstanceAvailable) {
4282
+ // collection instance not received yet
4283
+ let detachCallback = () => { };
4284
+ context.onInstanceAvailable((ref) => {
4285
+ detachCallback = onRemove(ref, callback);
4286
+ });
4287
+ return () => detachCallback();
4288
+ }
4289
+ else if (context.instance) {
4290
+ return onRemove(context.instance, callback);
4291
+ }
4292
+ },
4293
+ }, {
4294
+ get(target, prop) {
4295
+ if (!target[prop]) {
4296
+ throw new Error(`Can't access '${prop}' through callback proxy. access the instance directly.`);
4297
+ }
4298
+ return target[prop];
4299
+ },
4300
+ has(target, prop) { return target[prop] !== undefined; },
4301
+ set(_, _1, _2) { throw new Error("not allowed"); },
4302
+ deleteProperty(_, _1) { throw new Error("not allowed"); },
4303
+ });
4304
+ }
4305
+ }
4306
+ function $(instance) {
4307
+ return getProxy(undefined, { instance });
4308
+ }
4309
+ return $;
4310
+ }
4311
+
4312
+ function getRawChangesCallback(decoder, callback) {
4313
+ decoder.triggerChanges = callback;
4314
+ }
4315
+
3945
4316
  class StateView {
3946
4317
  constructor() {
3947
4318
  /**
@@ -3959,20 +4330,21 @@
3959
4330
  this.changes = new Map();
3960
4331
  }
3961
4332
  // TODO: allow to set multiple tags at once
3962
- add(obj, tag = DEFAULT_VIEW_TAG) {
4333
+ add(obj, tag = DEFAULT_VIEW_TAG, checkIncludeParent = true) {
3963
4334
  if (!obj[$changes]) {
3964
4335
  console.warn("StateView#add(), invalid object:", obj);
3965
4336
  return this;
3966
4337
  }
3967
- let changeTree = obj[$changes];
3968
- this.items.add(changeTree);
3969
- // Add children of this ChangeTree to this view
3970
- changeTree.forEachChild((change, _) => this.add(change.ref, tag));
3971
4338
  // FIXME: ArraySchema/MapSchema does not have metadata
3972
4339
  const metadata = obj.constructor[Symbol.metadata];
3973
- // add parent ChangeTree's, if they are invisible to this view
3974
- // TODO: REFACTOR addParent()
3975
- this.addParent(changeTree, tag);
4340
+ const changeTree = obj[$changes];
4341
+ this.items.add(changeTree);
4342
+ // add parent ChangeTree's
4343
+ // - if it was invisible to this view
4344
+ // - if it were previously filtered out
4345
+ if (checkIncludeParent && changeTree.parent) {
4346
+ this.addParent(changeTree.parent[$changes], changeTree.parentIndex, tag);
4347
+ }
3976
4348
  //
3977
4349
  // TODO: when adding an item of a MapSchema, the changes may not
3978
4350
  // be set (only the parent's changes are set)
@@ -3996,7 +4368,6 @@
3996
4368
  tags = this.tags.get(changeTree);
3997
4369
  }
3998
4370
  tags.add(tag);
3999
- // console.log("BY TAG:", tag);
4000
4371
  // Ref: add tagged properties
4001
4372
  metadata?.[-3]?.[tag]?.forEach((index) => {
4002
4373
  if (changeTree.getChange(index) !== exports.OPERATION.DELETE) {
@@ -4005,73 +4376,63 @@
4005
4376
  });
4006
4377
  }
4007
4378
  else {
4008
- // console.log("DEFAULT TAG", changeTree.allChanges);
4009
- // // add default tag properties
4010
- // metadata?.[-3]?.[DEFAULT_VIEW_TAG]?.forEach((index) => {
4011
- // if (changeTree.getChange(index) !== OPERATION.DELETE) {
4012
- // changes.set(index, OPERATION.ADD);
4013
- // }
4014
- // });
4015
- const allChangesSet = (changeTree.isFiltered || changeTree.isPartiallyFiltered)
4379
+ const isInvisible = this.invisible.has(changeTree);
4380
+ const changeSet = (changeTree.isFiltered || changeTree.isPartiallyFiltered)
4016
4381
  ? changeTree.allFilteredChanges
4017
4382
  : changeTree.allChanges;
4018
- const it = allChangesSet.keys();
4019
- const isInvisible = this.invisible.has(changeTree);
4020
- for (const index of it) {
4021
- if ((isInvisible || metadata?.[metadata?.[index]].tag === tag) &&
4022
- changeTree.getChange(index) !== exports.OPERATION.DELETE) {
4023
- changes.set(index, exports.OPERATION.ADD);
4383
+ changeSet.forEach((op, index) => {
4384
+ const tagAtIndex = metadata?.[metadata?.[index]].tag;
4385
+ if ((isInvisible || // if "invisible", include all
4386
+ tagAtIndex === undefined || // "all change" with no tag
4387
+ tagAtIndex === tag // tagged property
4388
+ ) &&
4389
+ op !== exports.OPERATION.DELETE) {
4390
+ changes.set(index, op);
4024
4391
  }
4025
- }
4026
- }
4027
- // TODO: avoid unnecessary iteration here
4028
- while (changeTree.parent &&
4029
- (changeTree = changeTree.parent[$changes]) &&
4030
- (changeTree.isFiltered || changeTree.isPartiallyFiltered)) {
4031
- this.items.add(changeTree);
4392
+ });
4032
4393
  }
4394
+ // Add children of this ChangeTree to this view
4395
+ changeTree.forEachChild((change, index) => {
4396
+ // Do not ADD children that don't have the same tag
4397
+ if (metadata && metadata[metadata[index]].tag !== tag) {
4398
+ return;
4399
+ }
4400
+ this.add(change.ref, tag, false);
4401
+ });
4033
4402
  return this;
4034
4403
  }
4035
- addParent(changeTree, tag) {
4036
- const parentRef = changeTree.parent;
4037
- if (!parentRef) {
4038
- return;
4404
+ addParent(changeTree, parentIndex, tag) {
4405
+ // view must have all "changeTree" parent tree
4406
+ this.items.add(changeTree);
4407
+ // add parent's parent
4408
+ const parentChangeTree = changeTree.parent?.[$changes];
4409
+ if (parentChangeTree && (parentChangeTree.isFiltered || parentChangeTree.isPartiallyFiltered)) {
4410
+ this.addParent(parentChangeTree, changeTree.parentIndex, tag);
4039
4411
  }
4040
- const parentChangeTree = parentRef[$changes];
4041
- const parentIndex = changeTree.parentIndex;
4042
- if (!this.invisible.has(parentChangeTree)) {
4043
- // parent is already available, no need to add it!
4412
+ // parent is already available, no need to add it!
4413
+ if (!this.invisible.has(changeTree)) {
4044
4414
  return;
4045
4415
  }
4046
- this.addParent(parentChangeTree, tag);
4047
4416
  // add parent's tag properties
4048
- if (parentChangeTree.getChange(parentIndex) !== exports.OPERATION.DELETE) {
4049
- let parentChanges = this.changes.get(parentChangeTree);
4050
- if (parentChanges === undefined) {
4051
- parentChanges = new Map();
4052
- this.changes.set(parentChangeTree, parentChanges);
4417
+ if (changeTree.getChange(parentIndex) !== exports.OPERATION.DELETE) {
4418
+ let changes = this.changes.get(changeTree);
4419
+ if (changes === undefined) {
4420
+ changes = new Map();
4421
+ this.changes.set(changeTree, changes);
4053
4422
  }
4054
- // console.log("add parent change", {
4055
- // parentIndex,
4056
- // parentChanges,
4057
- // parentChange: (
4058
- // parentChangeTree.getChange(parentIndex) &&
4059
- // OPERATION[parentChangeTree.getChange(parentIndex)]
4060
- // ),
4061
- // })
4062
4423
  if (!this.tags) {
4063
4424
  this.tags = new WeakMap();
4064
4425
  }
4065
4426
  let tags;
4066
- if (!this.tags.has(parentChangeTree)) {
4427
+ if (!this.tags.has(changeTree)) {
4067
4428
  tags = new Set();
4068
- this.tags.set(parentChangeTree, tags);
4429
+ this.tags.set(changeTree, tags);
4069
4430
  }
4070
4431
  else {
4071
- tags = this.tags.get(parentChangeTree);
4432
+ tags = this.tags.get(changeTree);
4072
4433
  }
4073
4434
  tags.add(tag);
4074
- parentChanges.set(parentIndex, exports.OPERATION.ADD);
4435
+ changes.set(parentIndex, exports.OPERATION.ADD);
4075
4436
  }
4076
4437
  }
4077
4438
  remove(obj, tag = DEFAULT_VIEW_TAG) {
@@ -4166,10 +4527,10 @@
4166
4527
  exports.encode = encode;
4167
4528
  exports.encodeKeyValueOperation = encodeArray;
4168
4529
  exports.encodeSchemaOperation = encodeSchemaOperation;
4530
+ exports.getDecoderStateCallbacks = getDecoderStateCallbacks;
4531
+ exports.getRawChangesCallback = getRawChangesCallback;
4169
4532
  exports.registerType = registerType;
4170
4533
  exports.type = type;
4171
4534
  exports.view = view;
4172
4535
 
4173
- Object.defineProperty(exports, '__esModule', { value: true });
4174
-
4175
4536
  }));