@colyseus/schema 3.0.76 → 4.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 (160) hide show
  1. package/build/cjs/index.js +781 -434
  2. package/build/cjs/index.js.map +1 -1
  3. package/build/esm/index.mjs +779 -435
  4. package/build/esm/index.mjs.map +1 -1
  5. package/build/umd/index.js +781 -434
  6. package/lib/Metadata.js +1 -5
  7. package/lib/Metadata.js.map +1 -1
  8. package/lib/Reflection.d.ts +50 -17
  9. package/lib/Reflection.js +151 -202
  10. package/lib/Reflection.js.map +1 -1
  11. package/lib/Schema.d.ts +13 -1
  12. package/lib/Schema.js +73 -9
  13. package/lib/Schema.js.map +1 -1
  14. package/lib/annotations.d.ts +6 -1
  15. package/lib/annotations.js +8 -34
  16. package/lib/annotations.js.map +1 -1
  17. package/lib/bench_encode.js +34 -1
  18. package/lib/bench_encode.js.map +1 -1
  19. package/lib/codegen/api.js +35 -2
  20. package/lib/codegen/api.js.map +1 -1
  21. package/lib/codegen/cli.js +4 -1
  22. package/lib/codegen/cli.js.map +1 -1
  23. package/lib/codegen/parser.js +35 -2
  24. package/lib/codegen/parser.js.map +1 -1
  25. package/lib/codegen/types.js +34 -1
  26. package/lib/codegen/types.js.map +1 -1
  27. package/lib/decoder/DecodeOperation.d.ts +2 -2
  28. package/lib/decoder/DecodeOperation.js +3 -3
  29. package/lib/decoder/DecodeOperation.js.map +1 -1
  30. package/lib/decoder/Decoder.d.ts +3 -3
  31. package/lib/decoder/Decoder.js +2 -2
  32. package/lib/decoder/Decoder.js.map +1 -1
  33. package/lib/decoder/ReferenceTracker.d.ts +0 -1
  34. package/lib/decoder/ReferenceTracker.js +9 -7
  35. package/lib/decoder/ReferenceTracker.js.map +1 -1
  36. package/lib/decoder/strategy/Callbacks.d.ts +154 -0
  37. package/lib/decoder/strategy/Callbacks.js +340 -0
  38. package/lib/decoder/strategy/Callbacks.js.map +1 -0
  39. package/lib/decoder/strategy/{StateCallbacks.d.ts → getDecoderStateCallbacks.d.ts} +6 -0
  40. package/lib/decoder/strategy/{StateCallbacks.js → getDecoderStateCallbacks.js} +17 -10
  41. package/lib/decoder/strategy/getDecoderStateCallbacks.js.map +1 -0
  42. package/lib/encoder/ChangeTree.d.ts +2 -2
  43. package/lib/encoder/ChangeTree.js.map +1 -1
  44. package/lib/encoder/EncodeOperation.d.ts +2 -2
  45. package/lib/encoder/EncodeOperation.js +3 -3
  46. package/lib/encoder/EncodeOperation.js.map +1 -1
  47. package/lib/encoder/Encoder.d.ts +6 -6
  48. package/lib/encoder/Encoder.js +19 -18
  49. package/lib/encoder/Encoder.js.map +1 -1
  50. package/lib/encoder/Root.js +17 -14
  51. package/lib/encoder/Root.js.map +1 -1
  52. package/lib/encoder/StateView.js +13 -12
  53. package/lib/encoder/StateView.js.map +1 -1
  54. package/lib/encoding/decode.d.ts +2 -2
  55. package/lib/encoding/encode.d.ts +3 -1
  56. package/lib/encoding/encode.js.map +1 -1
  57. package/lib/index.d.ts +3 -2
  58. package/lib/index.js +7 -3
  59. package/lib/index.js.map +1 -1
  60. package/lib/types/HelperTypes.d.ts +7 -14
  61. package/lib/types/HelperTypes.js.map +1 -1
  62. package/lib/types/custom/ArraySchema.d.ts +2 -1
  63. package/lib/types/custom/ArraySchema.js.map +1 -1
  64. package/lib/types/custom/CollectionSchema.d.ts +2 -1
  65. package/lib/types/custom/CollectionSchema.js.map +1 -1
  66. package/lib/types/custom/MapSchema.d.ts +3 -2
  67. package/lib/types/custom/MapSchema.js.map +1 -1
  68. package/lib/types/custom/SetSchema.d.ts +2 -1
  69. package/lib/types/custom/SetSchema.js.map +1 -1
  70. package/lib/types/symbols.d.ts +1 -0
  71. package/lib/types/symbols.js +2 -1
  72. package/lib/types/symbols.js.map +1 -1
  73. package/lib/utils.js +1 -1
  74. package/lib/utils.js.map +1 -1
  75. package/package.json +12 -16
  76. package/src/Metadata.ts +1 -5
  77. package/src/Reflection.ts +185 -174
  78. package/src/Schema.ts +81 -13
  79. package/src/annotations.ts +14 -40
  80. package/src/codegen/parser.ts +1 -1
  81. package/src/decoder/DecodeOperation.ts +9 -9
  82. package/src/decoder/Decoder.ts +6 -6
  83. package/src/decoder/ReferenceTracker.ts +10 -8
  84. package/src/decoder/strategy/Callbacks.ts +547 -0
  85. package/src/decoder/strategy/{StateCallbacks.ts → getDecoderStateCallbacks.ts} +17 -11
  86. package/src/encoder/ChangeTree.ts +4 -7
  87. package/src/encoder/EncodeOperation.ts +9 -9
  88. package/src/encoder/Encoder.ts +26 -18
  89. package/src/encoder/Root.ts +20 -15
  90. package/src/encoder/StateView.ts +15 -13
  91. package/src/encoding/encode.ts +1 -1
  92. package/src/index.ts +3 -2
  93. package/src/types/HelperTypes.ts +13 -11
  94. package/src/types/custom/ArraySchema.ts +2 -1
  95. package/src/types/custom/CollectionSchema.ts +4 -2
  96. package/src/types/custom/MapSchema.ts +4 -2
  97. package/src/types/custom/SetSchema.ts +3 -1
  98. package/src/types/symbols.ts +1 -0
  99. package/src/utils.ts +2 -2
  100. package/lib/Decoder.d.ts +0 -16
  101. package/lib/Decoder.js +0 -182
  102. package/lib/Decoder.js.map +0 -1
  103. package/lib/Encoder.d.ts +0 -13
  104. package/lib/Encoder.js +0 -79
  105. package/lib/Encoder.js.map +0 -1
  106. package/lib/changes/ChangeSet.d.ts +0 -12
  107. package/lib/changes/ChangeSet.js +0 -35
  108. package/lib/changes/ChangeSet.js.map +0 -1
  109. package/lib/changes/ChangeTree.d.ts +0 -53
  110. package/lib/changes/ChangeTree.js +0 -202
  111. package/lib/changes/ChangeTree.js.map +0 -1
  112. package/lib/changes/DecodeOperation.d.ts +0 -15
  113. package/lib/changes/DecodeOperation.js +0 -186
  114. package/lib/changes/DecodeOperation.js.map +0 -1
  115. package/lib/changes/EncodeOperation.d.ts +0 -18
  116. package/lib/changes/EncodeOperation.js +0 -130
  117. package/lib/changes/EncodeOperation.js.map +0 -1
  118. package/lib/changes/ReferenceTracker.d.ts +0 -14
  119. package/lib/changes/ReferenceTracker.js +0 -83
  120. package/lib/changes/ReferenceTracker.js.map +0 -1
  121. package/lib/changes/consts.d.ts +0 -14
  122. package/lib/changes/consts.js +0 -18
  123. package/lib/changes/consts.js.map +0 -1
  124. package/lib/decoder/strategy/StateCallbacks.js.map +0 -1
  125. package/lib/decoding/decode.d.ts +0 -48
  126. package/lib/decoding/decode.js +0 -267
  127. package/lib/decoding/decode.js.map +0 -1
  128. package/lib/ecs.d.ts +0 -11
  129. package/lib/ecs.js +0 -160
  130. package/lib/ecs.js.map +0 -1
  131. package/lib/filters/index.d.ts +0 -8
  132. package/lib/filters/index.js +0 -24
  133. package/lib/filters/index.js.map +0 -1
  134. package/lib/spec.d.ts +0 -13
  135. package/lib/spec.js +0 -42
  136. package/lib/spec.js.map +0 -1
  137. package/lib/types/ArraySchema.d.ts +0 -238
  138. package/lib/types/ArraySchema.js +0 -555
  139. package/lib/types/ArraySchema.js.map +0 -1
  140. package/lib/types/CollectionSchema.d.ts +0 -35
  141. package/lib/types/CollectionSchema.js +0 -150
  142. package/lib/types/CollectionSchema.js.map +0 -1
  143. package/lib/types/MapSchema.d.ts +0 -38
  144. package/lib/types/MapSchema.js +0 -215
  145. package/lib/types/MapSchema.js.map +0 -1
  146. package/lib/types/SetSchema.d.ts +0 -32
  147. package/lib/types/SetSchema.js +0 -162
  148. package/lib/types/SetSchema.js.map +0 -1
  149. package/lib/types/typeRegistry.d.ts +0 -5
  150. package/lib/types/typeRegistry.js +0 -13
  151. package/lib/types/typeRegistry.js.map +0 -1
  152. package/lib/usage.d.ts +0 -1
  153. package/lib/usage.js +0 -22
  154. package/lib/usage.js.map +0 -1
  155. package/lib/v3.d.ts +0 -1
  156. package/lib/v3.js +0 -427
  157. package/lib/v3.js.map +0 -1
  158. package/lib/v3_experiment.d.ts +0 -1
  159. package/lib/v3_experiment.js +0 -407
  160. package/lib/v3_experiment.js.map +0 -1
@@ -28,6 +28,7 @@ exports.OPERATION = void 0;
28
28
 
29
29
  Symbol.metadata ??= Symbol.for("Symbol.metadata");
30
30
 
31
+ const $refId = "~refId";
31
32
  const $track = "~track";
32
33
  const $encoder = "~encoder";
33
34
  const $decoder = "~decoder";
@@ -558,12 +559,16 @@ function defineCustomTypes(types) {
558
559
  }
559
560
 
560
561
  class TypeContext {
562
+ types = {};
563
+ schemas = new Map();
564
+ hasFilters = false;
565
+ parentFiltered = {};
561
566
  /**
562
567
  * For inheritance support
563
568
  * Keeps track of which classes extends which. (parent -> children)
564
569
  */
565
- static { this.inheritedTypes = new Map(); }
566
- static { this.cachedContexts = new Map(); }
570
+ static inheritedTypes = new Map();
571
+ static cachedContexts = new Map();
567
572
  static register(target) {
568
573
  const parent = Object.getPrototypeOf(target);
569
574
  if (parent !== Schema) {
@@ -584,10 +589,6 @@ class TypeContext {
584
589
  return context;
585
590
  }
586
591
  constructor(rootClass) {
587
- this.types = {};
588
- this.schemas = new Map();
589
- this.hasFilters = false;
590
- this.parentFiltered = {};
591
592
  if (rootClass) {
592
593
  this.discoverTypes(rootClass);
593
594
  }
@@ -928,11 +929,7 @@ const Metadata = {
928
929
  });
929
930
  }
930
931
  }
931
- Object.defineProperty(constructor, Symbol.metadata, {
932
- value: metadata,
933
- writable: false,
934
- configurable: true
935
- });
932
+ constructor[Symbol.metadata] = metadata;
936
933
  return metadata;
937
934
  },
938
935
  isValidInstance(klass) {
@@ -984,25 +981,33 @@ function deleteOperationAtIndex(changeSet, index) {
984
981
  delete changeSet.indexes[index];
985
982
  }
986
983
  class ChangeTree {
984
+ ref;
985
+ metadata;
986
+ root;
987
+ parentChain; // Linked list for tracking parents
988
+ /**
989
+ * Whether this structure is parent of a filtered structure.
990
+ */
991
+ isFiltered = false;
992
+ isVisibilitySharedWithParent; // See test case: 'should not be required to manually call view.add() items to child arrays without @view() tag'
993
+ indexedOperations = {};
994
+ //
995
+ // TODO:
996
+ // try storing the index + operation per item.
997
+ // example: 1024 & 1025 => ADD, 1026 => DELETE
998
+ //
999
+ // => https://chatgpt.com/share/67107d0c-bc20-8004-8583-83b17dd7c196
1000
+ //
1001
+ changes = { indexes: {}, operations: [] };
1002
+ allChanges = { indexes: {}, operations: [] };
1003
+ filteredChanges;
1004
+ allFilteredChanges;
1005
+ indexes; // TODO: remove this, only used by MapSchema/SetSchema/CollectionSchema (`encodeKeyValueOperation`)
1006
+ /**
1007
+ * Is this a new instance? Used on ArraySchema to determine OPERATION.MOVE_AND_ADD operation.
1008
+ */
1009
+ isNew = true;
987
1010
  constructor(ref) {
988
- /**
989
- * Whether this structure is parent of a filtered structure.
990
- */
991
- this.isFiltered = false;
992
- this.indexedOperations = {};
993
- //
994
- // TODO:
995
- // try storing the index + operation per item.
996
- // example: 1024 & 1025 => ADD, 1026 => DELETE
997
- //
998
- // => https://chatgpt.com/share/67107d0c-bc20-8004-8583-83b17dd7c196
999
- //
1000
- this.changes = { indexes: {}, operations: [] };
1001
- this.allChanges = { indexes: {}, operations: [] };
1002
- /**
1003
- * Is this a new instance? Used on ArraySchema to determine OPERATION.MOVE_AND_ADD operation.
1004
- */
1005
- this.isNew = true;
1006
1011
  this.ref = ref;
1007
1012
  this.metadata = ref.constructor[Symbol.metadata];
1008
1013
  //
@@ -1474,7 +1479,7 @@ function encodeValue(encoder, bytes, type, value, operation, it) {
1474
1479
  // Encode refId for this instance.
1475
1480
  // The actual instance is going to be encoded on next `changeTree` iteration.
1476
1481
  //
1477
- encode.number(bytes, value[$changes].refId, it);
1482
+ encode.number(bytes, value[$refId], it);
1478
1483
  // Try to encode inherited TYPE_ID if it's an ADD operation.
1479
1484
  if ((operation & exports.OPERATION.ADD) === exports.OPERATION.ADD) {
1480
1485
  encoder.tryEncodeTypeId(bytes, type, value.constructor, it);
@@ -1485,7 +1490,7 @@ function encodeValue(encoder, bytes, type, value, operation, it) {
1485
1490
  // Encode refId for this instance.
1486
1491
  // The actual instance is going to be encoded on next `changeTree` iteration.
1487
1492
  //
1488
- encode.number(bytes, value[$changes].refId, it);
1493
+ encode.number(bytes, value[$refId], it);
1489
1494
  }
1490
1495
  }
1491
1496
  /**
@@ -1561,7 +1566,7 @@ const encodeArray = function (encoder, bytes, changeTree, field, operation, it,
1561
1566
  if (!item) {
1562
1567
  return;
1563
1568
  }
1564
- refOrIndex = item[$changes].refId;
1569
+ refOrIndex = item[$refId];
1565
1570
  if (operation === exports.OPERATION.DELETE) {
1566
1571
  operation = exports.OPERATION.DELETE_BY_REFID;
1567
1572
  }
@@ -1601,7 +1606,7 @@ function decodeValue(decoder, operation, ref, index, type, bytes, it, allChanges
1601
1606
  let value;
1602
1607
  if ((operation & exports.OPERATION.DELETE) === exports.OPERATION.DELETE) {
1603
1608
  // Flag `refId` for garbage collection.
1604
- const previousRefId = $root.refIds.get(previousValue);
1609
+ const previousRefId = previousValue?.[$refId];
1605
1610
  if (previousRefId !== undefined) {
1606
1611
  $root.removeRef(previousRefId);
1607
1612
  }
@@ -1642,7 +1647,7 @@ function decodeValue(decoder, operation, ref, index, type, bytes, it, allChanges
1642
1647
  value = valueRef.clone(true);
1643
1648
  value[$childType] = Object.values(type)[0]; // cache childType for ArraySchema and MapSchema
1644
1649
  if (previousValue) {
1645
- let previousRefId = $root.refIds.get(previousValue);
1650
+ let previousRefId = previousValue[$refId];
1646
1651
  if (previousRefId !== undefined && refId !== previousRefId) {
1647
1652
  //
1648
1653
  // enqueue onRemove if structure has been replaced.
@@ -1653,7 +1658,7 @@ function decodeValue(decoder, operation, ref, index, type, bytes, it, allChanges
1653
1658
  const [key, value] = iter.value;
1654
1659
  // if value is a schema, remove its reference
1655
1660
  if (typeof (value) === "object") {
1656
- previousRefId = $root.refIds.get(value);
1661
+ previousRefId = value[$refId];
1657
1662
  $root.removeRef(previousRefId);
1658
1663
  }
1659
1664
  allChanges.push({
@@ -1882,7 +1887,6 @@ function assertInstanceType(value, type, instance, field) {
1882
1887
  }
1883
1888
  }
1884
1889
 
1885
- var _a$4, _b$4;
1886
1890
  const DEFAULT_SORT = (a, b) => {
1887
1891
  const A = a.toString();
1888
1892
  const B = b.toString();
@@ -1894,8 +1898,15 @@ const DEFAULT_SORT = (a, b) => {
1894
1898
  return 0;
1895
1899
  };
1896
1900
  class ArraySchema {
1897
- static { this[_a$4] = encodeArray; }
1898
- static { this[_b$4] = decodeArray; }
1901
+ [$changes];
1902
+ [$refId];
1903
+ [$childType];
1904
+ items = [];
1905
+ tmpItems = [];
1906
+ deletedIndexes = {};
1907
+ isMovingItems = false;
1908
+ static [$encoder] = encodeArray;
1909
+ static [$decoder] = decodeArray;
1899
1910
  /**
1900
1911
  * Determine if a property must be filtered.
1901
1912
  * - If returns false, the property is NOT going to be encoded.
@@ -1905,7 +1916,7 @@ class ArraySchema {
1905
1916
  * - First, the encoder iterates over all "not owned" properties and encodes them.
1906
1917
  * - Then, the encoder iterates over all "owned" properties per instance and encodes them.
1907
1918
  */
1908
- static [(_a$4 = $encoder, _b$4 = $decoder, $filter)](ref, index, view) {
1919
+ static [$filter](ref, index, view) {
1909
1920
  return (!view ||
1910
1921
  typeof (ref[$childType]) === "string" ||
1911
1922
  view.isChangeTreeVisible(ref['tmpItems'][index]?.[$changes]));
@@ -1921,10 +1932,6 @@ class ArraySchema {
1921
1932
  return new ArraySchema(...Array.from(iterable));
1922
1933
  }
1923
1934
  constructor(...items) {
1924
- this.items = [];
1925
- this.tmpItems = [];
1926
- this.deletedIndexes = {};
1927
- this.isMovingItems = false;
1928
1935
  Object.defineProperty(this, $childType, {
1929
1936
  value: undefined,
1930
1937
  enumerable: false,
@@ -2441,6 +2448,10 @@ class ArraySchema {
2441
2448
  static get [Symbol.species]() {
2442
2449
  return ArraySchema;
2443
2450
  }
2451
+ // WORKAROUND for compatibility
2452
+ // - TypeScript 4 defines @@unscopables as a function
2453
+ // - TypeScript 5 defines @@unscopables as an object
2454
+ [Symbol.unscopables];
2444
2455
  /**
2445
2456
  * Returns an iterable of key, value pairs for every entry in the array
2446
2457
  */
@@ -2550,7 +2561,7 @@ class ArraySchema {
2550
2561
  this.isMovingItems = false;
2551
2562
  return this;
2552
2563
  }
2553
- [($getByIndex)](index, isEncodeAll = false) {
2564
+ [$getByIndex](index, isEncodeAll = false) {
2554
2565
  //
2555
2566
  // TODO: avoid unecessary `this.tmpItems` check during decoding.
2556
2567
  //
@@ -2605,10 +2616,16 @@ class ArraySchema {
2605
2616
  }
2606
2617
  registerType("array", { constructor: ArraySchema });
2607
2618
 
2608
- var _a$3, _b$3;
2609
2619
  class MapSchema {
2610
- static { this[_a$3] = encodeKeyValueOperation; }
2611
- static { this[_b$3] = decodeKeyValueOperation; }
2620
+ [$changes];
2621
+ [$refId];
2622
+ childType;
2623
+ [$childType];
2624
+ $items = new Map();
2625
+ $indexes = new Map();
2626
+ deletedItems = {};
2627
+ static [$encoder] = encodeKeyValueOperation;
2628
+ static [$decoder] = decodeKeyValueOperation;
2612
2629
  /**
2613
2630
  * Determine if a property must be filtered.
2614
2631
  * - If returns false, the property is NOT going to be encoded.
@@ -2618,7 +2635,7 @@ class MapSchema {
2618
2635
  * - First, the encoder iterates over all "not owned" properties and encodes them.
2619
2636
  * - Then, the encoder iterates over all "owned" properties per instance and encodes them.
2620
2637
  */
2621
- static [(_a$3 = $encoder, _b$3 = $decoder, $filter)](ref, index, view) {
2638
+ static [$filter](ref, index, view) {
2622
2639
  return (!view ||
2623
2640
  typeof (ref[$childType]) === "string" ||
2624
2641
  view.isChangeTreeVisible((ref[$getByIndex](index) ?? ref.deletedItems[index])[$changes]));
@@ -2627,9 +2644,6 @@ class MapSchema {
2627
2644
  return type['map'] !== undefined;
2628
2645
  }
2629
2646
  constructor(initialValues) {
2630
- this.$items = new Map();
2631
- this.$indexes = new Map();
2632
- this.deletedItems = {};
2633
2647
  const changeTree = new ChangeTree(this);
2634
2648
  changeTree.indexes = {};
2635
2649
  Object.defineProperty(this, $changes, {
@@ -2820,10 +2834,16 @@ class MapSchema {
2820
2834
  }
2821
2835
  registerType("map", { constructor: MapSchema });
2822
2836
 
2823
- var _a$2, _b$2;
2824
2837
  class CollectionSchema {
2825
- static { this[_a$2] = encodeKeyValueOperation; }
2826
- static { this[_b$2] = decodeKeyValueOperation; }
2838
+ [$changes];
2839
+ [$refId];
2840
+ [$childType];
2841
+ $items = new Map();
2842
+ $indexes = new Map();
2843
+ deletedItems = {};
2844
+ $refId = 0;
2845
+ static [$encoder] = encodeKeyValueOperation;
2846
+ static [$decoder] = decodeKeyValueOperation;
2827
2847
  /**
2828
2848
  * Determine if a property must be filtered.
2829
2849
  * - If returns false, the property is NOT going to be encoded.
@@ -2833,7 +2853,7 @@ class CollectionSchema {
2833
2853
  * - First, the encoder iterates over all "not owned" properties and encodes them.
2834
2854
  * - Then, the encoder iterates over all "owned" properties per instance and encodes them.
2835
2855
  */
2836
- static [(_a$2 = $encoder, _b$2 = $decoder, $filter)](ref, index, view) {
2856
+ static [$filter](ref, index, view) {
2837
2857
  return (!view ||
2838
2858
  typeof (ref[$childType]) === "string" ||
2839
2859
  view.isChangeTreeVisible((ref[$getByIndex](index) ?? ref.deletedItems[index])[$changes]));
@@ -2842,10 +2862,6 @@ class CollectionSchema {
2842
2862
  return type['collection'] !== undefined;
2843
2863
  }
2844
2864
  constructor(initialValues) {
2845
- this.$items = new Map();
2846
- this.$indexes = new Map();
2847
- this.deletedItems = {};
2848
- this.$refId = 0;
2849
2865
  this[$changes] = new ChangeTree(this);
2850
2866
  this[$changes].indexes = {};
2851
2867
  if (initialValues) {
@@ -2984,10 +3000,16 @@ class CollectionSchema {
2984
3000
  }
2985
3001
  registerType("collection", { constructor: CollectionSchema, });
2986
3002
 
2987
- var _a$1, _b$1;
2988
3003
  class SetSchema {
2989
- static { this[_a$1] = encodeKeyValueOperation; }
2990
- static { this[_b$1] = decodeKeyValueOperation; }
3004
+ [$changes];
3005
+ [$refId];
3006
+ [$childType];
3007
+ $items = new Map();
3008
+ $indexes = new Map();
3009
+ deletedItems = {};
3010
+ $refId = 0;
3011
+ static [$encoder] = encodeKeyValueOperation;
3012
+ static [$decoder] = decodeKeyValueOperation;
2991
3013
  /**
2992
3014
  * Determine if a property must be filtered.
2993
3015
  * - If returns false, the property is NOT going to be encoded.
@@ -2997,7 +3019,7 @@ class SetSchema {
2997
3019
  * - First, the encoder iterates over all "not owned" properties and encodes them.
2998
3020
  * - Then, the encoder iterates over all "owned" properties per instance and encodes them.
2999
3021
  */
3000
- static [(_a$1 = $encoder, _b$1 = $decoder, $filter)](ref, index, view) {
3022
+ static [$filter](ref, index, view) {
3001
3023
  return (!view ||
3002
3024
  typeof (ref[$childType]) === "string" ||
3003
3025
  view.visible.has((ref[$getByIndex](index) ?? ref.deletedItems[index])[$changes]));
@@ -3006,10 +3028,6 @@ class SetSchema {
3006
3028
  return type['set'] !== undefined;
3007
3029
  }
3008
3030
  constructor(initialValues) {
3009
- this.$items = new Map();
3010
- this.$indexes = new Map();
3011
- this.deletedItems = {};
3012
- this.$refId = 0;
3013
3031
  this[$changes] = new ChangeTree(this);
3014
3032
  this[$changes].indexes = {};
3015
3033
  if (initialValues) {
@@ -3508,7 +3526,10 @@ function schema(fieldsAndMethods, name, inherits = Schema) {
3508
3526
  ? DEFAULT_VIEW_TAG
3509
3527
  : value['view'];
3510
3528
  }
3511
- fields[fieldName] = getNormalizedType(value);
3529
+ // allow to define a field as not synced
3530
+ if (value['sync'] !== false) {
3531
+ fields[fieldName] = getNormalizedType(value);
3532
+ }
3512
3533
  // If no explicit default provided, handle automatic instantiation for collection types
3513
3534
  if (!Object.prototype.hasOwnProperty.call(value, 'default')) {
3514
3535
  // TODO: remove Array.isArray() check. Use ['array'] !== undefined only.
@@ -3530,12 +3551,7 @@ function schema(fieldsAndMethods, name, inherits = Schema) {
3530
3551
  }
3531
3552
  else if (value['type'] !== undefined && Schema.is(value['type'])) {
3532
3553
  // Direct Schema type: Type → new Type()
3533
- if (!value['type'].prototype.initialize || value['type'].prototype.initialize.length === 0) {
3534
- // only auto-initialize Schema instances if:
3535
- // - they don't have an initialize method
3536
- // - or initialize method doesn't accept any parameters
3537
- defaultValues[fieldName] = new value['type']();
3538
- }
3554
+ defaultValues[fieldName] = new value['type']();
3539
3555
  }
3540
3556
  }
3541
3557
  else {
@@ -3545,12 +3561,7 @@ function schema(fieldsAndMethods, name, inherits = Schema) {
3545
3561
  else if (typeof (value) === "function") {
3546
3562
  if (Schema.is(value)) {
3547
3563
  // Direct Schema type: Type → new Type()
3548
- if (!value.prototype.initialize || value.prototype.initialize.length === 0) {
3549
- // only auto-initialize Schema instances if:
3550
- // - they don't have an initialize method
3551
- // - or initialize method doesn't accept any parameters
3552
- defaultValues[fieldName] = new value();
3553
- }
3564
+ defaultValues[fieldName] = new value();
3554
3565
  fields[fieldName] = getNormalizedType(value);
3555
3566
  }
3556
3567
  else {
@@ -3577,32 +3588,13 @@ function schema(fieldsAndMethods, name, inherits = Schema) {
3577
3588
  }
3578
3589
  return defaults;
3579
3590
  };
3580
- const getParentProps = (props) => {
3581
- const fieldNames = Object.keys(fields);
3582
- const parentProps = {};
3583
- for (const key in props) {
3584
- if (!fieldNames.includes(key)) {
3585
- parentProps[key] = props[key];
3586
- }
3587
- }
3588
- return parentProps;
3589
- };
3590
3591
  /** @codegen-ignore */
3591
3592
  const klass = Metadata.setFields(class extends inherits {
3592
3593
  constructor(...args) {
3594
+ super(Object.assign({}, getDefaultValues(), args[0] || {}));
3593
3595
  // call initialize method
3594
3596
  if (methods.initialize && typeof methods.initialize === 'function') {
3595
- super(Object.assign({}, getDefaultValues(), getParentProps(args[0] || {})));
3596
- /**
3597
- * only call initialize() in the current class, not the parent ones.
3598
- * see "should not call initialize automatically when creating an instance of inherited Schema"
3599
- */
3600
- if (new.target === klass) {
3601
- methods.initialize.apply(this, args);
3602
- }
3603
- }
3604
- else {
3605
- super(Object.assign({}, getDefaultValues(), args[0] || {}));
3597
+ methods.initialize.apply(this, args);
3606
3598
  }
3607
3599
  }
3608
3600
  }, fields);
@@ -3639,7 +3631,7 @@ function dumpChanges(schema) {
3639
3631
  continue;
3640
3632
  }
3641
3633
  const changes = changeTree.indexedOperations;
3642
- dump.refs.push(`refId#${changeTree.refId}`);
3634
+ dump.refs.push(`refId#${changeTree.ref[$refId]}`);
3643
3635
  for (const index in changes) {
3644
3636
  const op = changes[index];
3645
3637
  const opName = exports.OPERATION[op];
@@ -3653,13 +3645,14 @@ function dumpChanges(schema) {
3653
3645
  return dump;
3654
3646
  }
3655
3647
 
3656
- var _a, _b;
3657
3648
  /**
3658
3649
  * Schema encoder / decoder
3659
3650
  */
3660
3651
  class Schema {
3661
- static { this[_a] = encodeSchemaOperation; }
3662
- static { this[_b] = decodeSchemaOperation; }
3652
+ static [Symbol.metadata];
3653
+ static [$encoder] = encodeSchemaOperation;
3654
+ static [$decoder] = decodeSchemaOperation;
3655
+ [$refId];
3663
3656
  /**
3664
3657
  * Assign the property descriptors required to track changes on this instance.
3665
3658
  * @param instance
@@ -3678,7 +3671,7 @@ class Schema {
3678
3671
  /**
3679
3672
  * Track property changes
3680
3673
  */
3681
- static [(_a = $encoder, _b = $decoder, $track)](changeTree, index, operation = exports.OPERATION.ADD) {
3674
+ static [$track](changeTree, index, operation = exports.OPERATION.ADD) {
3682
3675
  changeTree.change(index, operation);
3683
3676
  }
3684
3677
  /**
@@ -3725,10 +3718,74 @@ class Schema {
3725
3718
  Object.assign(this, arg);
3726
3719
  }
3727
3720
  }
3721
+ /**
3722
+ * Assign properties to the instance.
3723
+ * @param props Properties to assign to the instance
3724
+ * @returns
3725
+ */
3728
3726
  assign(props) {
3729
3727
  Object.assign(this, props);
3730
3728
  return this;
3731
3729
  }
3730
+ /**
3731
+ * Restore the instance from JSON data.
3732
+ * @param jsonData JSON data to restore the instance from
3733
+ * @returns
3734
+ */
3735
+ restore(jsonData) {
3736
+ const metadata = this.constructor[Symbol.metadata];
3737
+ for (const fieldIndex in metadata) {
3738
+ const field = metadata[fieldIndex];
3739
+ const fieldName = field.name;
3740
+ const fieldType = field.type;
3741
+ const value = jsonData[fieldName];
3742
+ if (value === undefined || value === null) {
3743
+ continue;
3744
+ }
3745
+ if (typeof fieldType === "string") {
3746
+ // Primitive type: assign directly
3747
+ this[fieldName] = value;
3748
+ }
3749
+ else if (Schema.is(fieldType)) {
3750
+ // Schema type: create instance and restore
3751
+ const instance = new fieldType();
3752
+ instance.restore(value);
3753
+ this[fieldName] = instance;
3754
+ }
3755
+ else if (typeof fieldType === "object") {
3756
+ // Collection types: { map: ... }, { array: ... }, etc.
3757
+ const collectionType = Object.keys(fieldType)[0];
3758
+ const childType = fieldType[collectionType];
3759
+ if (collectionType === "map") {
3760
+ const mapSchema = this[fieldName];
3761
+ for (const key in value) {
3762
+ if (Schema.is(childType)) {
3763
+ const childInstance = new childType();
3764
+ childInstance.restore(value[key]);
3765
+ mapSchema.set(key, childInstance);
3766
+ }
3767
+ else {
3768
+ mapSchema.set(key, value[key]);
3769
+ }
3770
+ }
3771
+ }
3772
+ else if (collectionType === "array") {
3773
+ const arraySchema = this[fieldName];
3774
+ for (let i = 0; i < value.length; i++) {
3775
+ if (Schema.is(childType)) {
3776
+ const childInstance = new childType();
3777
+ childInstance.restore(value[i]);
3778
+ arraySchema.push(childInstance);
3779
+ }
3780
+ else {
3781
+ arraySchema.push(value[i]);
3782
+ }
3783
+ }
3784
+ }
3785
+ }
3786
+ }
3787
+ return this;
3788
+ }
3732
3789
  /**
3733
3790
  * (Server-side): Flag a property to be encoded for the next patch.
3734
3791
  * @param instance Schema instance
@@ -3801,7 +3858,7 @@ class Schema {
3801
3858
  static debugRefIds(ref, showContents = false, level = 0, decoder, keyPrefix = "") {
3802
3859
  const contents = (showContents) ? ` - ${JSON.stringify(ref.toJSON())}` : "";
3803
3860
  const changeTree = ref[$changes];
3804
- const refId = (decoder) ? decoder.root.refIds.get(ref) : changeTree.refId;
3861
+ const refId = ref[$refId];
3805
3862
  const root = (decoder) ? decoder.root : changeTree.root;
3806
3863
  // log reference count if > 1
3807
3864
  const refCount = (root?.refCount?.[refId] > 1)
@@ -3824,7 +3881,7 @@ class Schema {
3824
3881
  let current = ref[$changes].root[changeSet].next;
3825
3882
  while (current) {
3826
3883
  if (current.changeTree) {
3827
- encodeOrder.push(current.changeTree.refId);
3884
+ encodeOrder.push(current.changeTree.ref[$refId]);
3828
3885
  }
3829
3886
  current = current.next;
3830
3887
  }
@@ -3845,7 +3902,7 @@ class Schema {
3845
3902
  const changeTree = instance[$changes];
3846
3903
  const changeSet = (isEncodeAll) ? changeTree.allChanges : changeTree.changes;
3847
3904
  const changeSetName = (isEncodeAll) ? "allChanges" : "changes";
3848
- let output = `${instance.constructor.name} (${changeTree.refId}) -> .${changeSetName}:\n`;
3905
+ let output = `${instance.constructor.name} (${instance[$refId]}) -> .${changeSetName}:\n`;
3849
3906
  function dumpChangeSet(changeSet) {
3850
3907
  changeSet.operations
3851
3908
  .filter(op => op)
@@ -3859,14 +3916,14 @@ class Schema {
3859
3916
  if (!isEncodeAll &&
3860
3917
  changeTree.filteredChanges &&
3861
3918
  (changeTree.filteredChanges.operations).filter(op => op).length > 0) {
3862
- output += `${instance.constructor.name} (${changeTree.refId}) -> .filteredChanges:\n`;
3919
+ output += `${instance.constructor.name} (${instance[$refId]}) -> .filteredChanges:\n`;
3863
3920
  dumpChangeSet(changeTree.filteredChanges);
3864
3921
  }
3865
3922
  // display filtered changes
3866
3923
  if (isEncodeAll &&
3867
3924
  changeTree.allFilteredChanges &&
3868
3925
  (changeTree.allFilteredChanges.operations).filter(op => op).length > 0) {
3869
- output += `${instance.constructor.name} (${changeTree.refId}) -> .allFilteredChanges:\n`;
3926
+ output += `${instance.constructor.name} (${instance[$refId]}) -> .allFilteredChanges:\n`;
3870
3927
  dumpChangeSet(changeTree.allFilteredChanges);
3871
3928
  }
3872
3929
  return output;
@@ -3901,13 +3958,13 @@ class Schema {
3901
3958
  }
3902
3959
  }
3903
3960
  if (includeChangeTree) {
3904
- instanceRefIds.push(changeTree.refId);
3961
+ instanceRefIds.push(changeTree.ref[$refId]);
3905
3962
  totalOperations += Object.keys(changes).length;
3906
3963
  changeTrees.set(changeTree, parentChangeTrees.reverse());
3907
3964
  }
3908
3965
  }
3909
3966
  output += "---\n";
3910
- output += `root refId: ${rootChangeTree.refId}\n`;
3967
+ output += `root refId: ${rootChangeTree.ref[$refId]}\n`;
3911
3968
  output += `Total instances: ${instanceRefIds.length} (refIds: ${instanceRefIds.join(", ")})\n`;
3912
3969
  output += `Total changes: ${totalOperations}\n`;
3913
3970
  output += "---\n";
@@ -3916,7 +3973,7 @@ class Schema {
3916
3973
  for (const [changeTree, parentChangeTrees] of changeTrees.entries()) {
3917
3974
  parentChangeTrees.forEach((parentChangeTree, level) => {
3918
3975
  if (!visitedParents.has(parentChangeTree)) {
3919
- output += `${getIndent(level)}${parentChangeTree.ref.constructor.name} (refId: ${parentChangeTree.refId})\n`;
3976
+ output += `${getIndent(level)}${parentChangeTree.ref.constructor.name} (refId: ${parentChangeTree.ref[$refId]})\n`;
3920
3977
  visitedParents.add(parentChangeTree);
3921
3978
  }
3922
3979
  });
@@ -3924,7 +3981,7 @@ class Schema {
3924
3981
  const level = parentChangeTrees.length;
3925
3982
  const indent = getIndent(level);
3926
3983
  const parentIndex = (level > 0) ? `(${changeTree.parentIndex}) ` : "";
3927
- output += `${indent}${parentIndex}${changeTree.ref.constructor.name} (refId: ${changeTree.refId}) - changes: ${Object.keys(changes).length}\n`;
3984
+ output += `${indent}${parentIndex}${changeTree.ref.constructor.name} (refId: ${changeTree.ref[$refId]}) - changes: ${Object.keys(changes).length}\n`;
3928
3985
  for (const index in changes) {
3929
3986
  const operation = changes[index];
3930
3987
  output += `${getIndent(level + 1)}${exports.OPERATION[operation]}: ${index}\n`;
@@ -3934,61 +3991,35 @@ class Schema {
3934
3991
  }
3935
3992
  }
3936
3993
 
3937
- /******************************************************************************
3938
- Copyright (c) Microsoft Corporation.
3939
-
3940
- Permission to use, copy, modify, and/or distribute this software for any
3941
- purpose with or without fee is hereby granted.
3942
-
3943
- THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
3944
- REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
3945
- AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
3946
- INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
3947
- LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
3948
- OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
3949
- PERFORMANCE OF THIS SOFTWARE.
3950
- ***************************************************************************** */
3951
- /* global Reflect, Promise, SuppressedError, Symbol, Iterator */
3952
-
3953
-
3954
- function __decorate(decorators, target, key, desc) {
3955
- var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
3956
- if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
3957
- else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
3958
- return c > 3 && r && Object.defineProperty(target, key, r), r;
3959
- }
3960
-
3961
- typeof SuppressedError === "function" ? SuppressedError : function (error, suppressed, message) {
3962
- var e = new Error(message);
3963
- return e.name = "SuppressedError", e.error = error, e.suppressed = suppressed, e;
3964
- };
3965
-
3966
3994
  class Root {
3995
+ types;
3996
+ nextUniqueId = 0;
3997
+ refCount = {};
3998
+ changeTrees = {};
3999
+ // all changes
4000
+ allChanges = createChangeTreeList();
4001
+ allFilteredChanges = createChangeTreeList(); // TODO: do not initialize it if filters are not used
4002
+ // pending changes to be encoded
4003
+ changes = createChangeTreeList();
4004
+ filteredChanges = createChangeTreeList(); // TODO: do not initialize it if filters are not used
3967
4005
  constructor(types) {
3968
4006
  this.types = types;
3969
- this.nextUniqueId = 0;
3970
- this.refCount = {};
3971
- this.changeTrees = {};
3972
- // all changes
3973
- this.allChanges = createChangeTreeList();
3974
- this.allFilteredChanges = createChangeTreeList(); // TODO: do not initialize it if filters are not used
3975
- // pending changes to be encoded
3976
- this.changes = createChangeTreeList();
3977
- this.filteredChanges = createChangeTreeList(); // TODO: do not initialize it if filters are not used
3978
4007
  }
3979
4008
  getNextUniqueId() {
3980
4009
  return this.nextUniqueId++;
3981
4010
  }
3982
4011
  add(changeTree) {
3983
- // Assign unique `refId` to changeTree if it doesn't have one yet.
3984
- if (changeTree.refId === undefined) {
3985
- changeTree.refId = this.getNextUniqueId();
4012
+ const ref = changeTree.ref;
4013
+ // Assign unique `refId` to ref if it doesn't have one yet.
4014
+ if (ref[$refId] === undefined) {
4015
+ ref[$refId] = this.getNextUniqueId();
3986
4016
  }
3987
- const isNewChangeTree = (this.changeTrees[changeTree.refId] === undefined);
4017
+ const refId = ref[$refId];
4018
+ const isNewChangeTree = (this.changeTrees[refId] === undefined);
3988
4019
  if (isNewChangeTree) {
3989
- this.changeTrees[changeTree.refId] = changeTree;
4020
+ this.changeTrees[refId] = changeTree;
3990
4021
  }
3991
- const previousRefCount = this.refCount[changeTree.refId];
4022
+ const previousRefCount = this.refCount[refId];
3992
4023
  if (previousRefCount === 0) {
3993
4024
  //
3994
4025
  // When a ChangeTree is re-added, it means that it was previously removed.
@@ -4001,30 +4032,31 @@ class Root {
4001
4032
  setOperationAtIndex(changeTree.changes, len);
4002
4033
  }
4003
4034
  }
4004
- this.refCount[changeTree.refId] = (previousRefCount || 0) + 1;
4005
- // console.log("ADD", { refId: changeTree.refId, ref: changeTree.ref.constructor.name, refCount: this.refCount[changeTree.refId], isNewChangeTree });
4035
+ this.refCount[refId] = (previousRefCount || 0) + 1;
4036
+ // console.log("ADD", { refId, ref: ref.constructor.name, refCount: this.refCount[refId], isNewChangeTree });
4006
4037
  return isNewChangeTree;
4007
4038
  }
4008
4039
  remove(changeTree) {
4009
- const refCount = (this.refCount[changeTree.refId]) - 1;
4010
- // console.log("REMOVE", { refId: changeTree.refId, ref: changeTree.ref.constructor.name, refCount, needRemove: refCount <= 0 });
4040
+ const refId = changeTree.ref[$refId];
4041
+ const refCount = (this.refCount[refId]) - 1;
4042
+ // console.log("REMOVE", { refId, ref: changeTree.ref.constructor.name, refCount, needRemove: refCount <= 0 });
4011
4043
  if (refCount <= 0) {
4012
4044
  //
4013
4045
  // Only remove "root" reference if it's the last reference
4014
4046
  //
4015
4047
  changeTree.root = undefined;
4016
- delete this.changeTrees[changeTree.refId];
4048
+ delete this.changeTrees[refId];
4017
4049
  this.removeChangeFromChangeSet("allChanges", changeTree);
4018
4050
  this.removeChangeFromChangeSet("changes", changeTree);
4019
4051
  if (changeTree.filteredChanges) {
4020
4052
  this.removeChangeFromChangeSet("allFilteredChanges", changeTree);
4021
4053
  this.removeChangeFromChangeSet("filteredChanges", changeTree);
4022
4054
  }
4023
- this.refCount[changeTree.refId] = 0;
4055
+ this.refCount[refId] = 0;
4024
4056
  changeTree.forEachChild((child, _) => {
4025
4057
  if (child.removeParent(changeTree.ref)) {
4026
4058
  if ((child.parentChain === undefined || // no parent, remove it
4027
- (child.parentChain && this.refCount[child.refId] > 0) // parent is still in use, but has more than one reference, remove it
4059
+ (child.parentChain && this.refCount[child.ref[$refId]] > 0) // parent is still in use, but has more than one reference, remove it
4028
4060
  )) {
4029
4061
  this.remove(child);
4030
4062
  }
@@ -4036,7 +4068,7 @@ class Root {
4036
4068
  });
4037
4069
  }
4038
4070
  else {
4039
- this.refCount[changeTree.refId] = refCount;
4071
+ this.refCount[refId] = refCount;
4040
4072
  //
4041
4073
  // When losing a reference to an instance, it is best to move the
4042
4074
  // ChangeTree next to its parent in the encoding queue.
@@ -4186,10 +4218,19 @@ class Root {
4186
4218
  }
4187
4219
  }
4188
4220
 
4221
+ function concatBytes(a, b) {
4222
+ const result = new Uint8Array(a.length + b.length);
4223
+ result.set(a, 0);
4224
+ result.set(b, a.length);
4225
+ return result;
4226
+ }
4189
4227
  class Encoder {
4190
- static { this.BUFFER_SIZE = (typeof (Buffer) !== "undefined") && Buffer.poolSize || 8 * 1024; } // 8KB
4228
+ static BUFFER_SIZE = 8 * 1024; // 8KB
4229
+ sharedBuffer = new Uint8Array(Encoder.BUFFER_SIZE);
4230
+ context;
4231
+ state;
4232
+ root;
4191
4233
  constructor(state) {
4192
- this.sharedBuffer = Buffer.allocUnsafe(Encoder.BUFFER_SIZE);
4193
4234
  //
4194
4235
  // Use .cache() here to avoid re-creating a new context for every new room instance.
4195
4236
  //
@@ -4217,7 +4258,7 @@ class Encoder {
4217
4258
  const changeTree = current.changeTree;
4218
4259
  if (hasView) {
4219
4260
  if (!view.isChangeTreeVisible(changeTree)) {
4220
- // console.log("MARK AS INVISIBLE:", { ref: changeTree.ref.constructor.name, refId: changeTree.refId, raw: changeTree.ref.toJSON() });
4261
+ // console.log("MARK AS INVISIBLE:", { ref: changeTree.ref.constructor.name, refId: changeTree.ref[$refId], raw: changeTree.ref.toJSON() });
4221
4262
  view.invisible.add(changeTree);
4222
4263
  continue; // skip this change tree
4223
4264
  }
@@ -4238,7 +4279,7 @@ class Encoder {
4238
4279
  // (unless it "hasView", which will need to revisit the root)
4239
4280
  if (hasView || it.offset > initialOffset || changeTree !== rootChangeTree) {
4240
4281
  buffer[it.offset++] = SWITCH_TO_STRUCTURE & 255;
4241
- encode.number(buffer, changeTree.refId, it);
4282
+ encode.number(buffer, ref[$refId], it);
4242
4283
  }
4243
4284
  for (let j = 0; j < numChanges; j++) {
4244
4285
  const fieldIndex = changeSet.operations[j];
@@ -4267,10 +4308,9 @@ class Encoder {
4267
4308
  }
4268
4309
  }
4269
4310
  if (it.offset > buffer.byteLength) {
4270
- // we can assume that n + 1 poolSize will suffice given that we are likely done with encoding at this point
4271
- // multiples of poolSize are faster to allocate than arbitrary sizes
4272
- // if we are on an older platform that doesn't implement pooling use 8kb as poolSize (that's the default for node)
4273
- const newSize = Math.ceil(it.offset / (Buffer.poolSize ?? 8 * 1024)) * (Buffer.poolSize ?? 8 * 1024);
4311
+ // we can assume that n + 1 BUFFER_SIZE will suffice given that we are likely done with encoding at this point
4312
+ // multiples of BUFFER_SIZE are faster to allocate than arbitrary sizes
4313
+ const newSize = Math.ceil(it.offset / Encoder.BUFFER_SIZE) * Encoder.BUFFER_SIZE;
4274
4314
  console.warn(`@colyseus/schema buffer overflow. Encoded state is higher than default BUFFER_SIZE. Use the following to increase default BUFFER_SIZE:
4275
4315
 
4276
4316
  import { Encoder } from "@colyseus/schema";
@@ -4280,7 +4320,9 @@ class Encoder {
4280
4320
  // resize buffer and re-encode (TODO: can we avoid re-encoding here?)
4281
4321
  // -> No we probably can't unless we catch the need for resize before encoding which is likely more computationally expensive than resizing on demand
4282
4322
  //
4283
- buffer = Buffer.alloc(newSize, buffer); // fill with buffer here to memcpy previous encoding steps beyond the initialOffset
4323
+ const newBuffer = new Uint8Array(newSize);
4324
+ newBuffer.set(buffer); // copy previous encoding steps beyond the initialOffset
4325
+ buffer = newBuffer;
4284
4326
  // assign resized buffer to local sharedBuffer
4285
4327
  if (buffer === this.sharedBuffer) {
4286
4328
  this.sharedBuffer = buffer;
@@ -4298,10 +4340,7 @@ class Encoder {
4298
4340
  const viewOffset = it.offset;
4299
4341
  // try to encode "filtered" changes
4300
4342
  this.encode(it, view, bytes, "allFilteredChanges", true, viewOffset);
4301
- return Buffer.concat([
4302
- bytes.subarray(0, sharedOffset),
4303
- bytes.subarray(viewOffset, it.offset)
4304
- ]);
4343
+ return concatBytes(bytes.subarray(0, sharedOffset), bytes.subarray(viewOffset, it.offset));
4305
4344
  }
4306
4345
  encodeView(view, sharedOffset, it, bytes = this.sharedBuffer) {
4307
4346
  const viewOffset = it.offset;
@@ -4325,7 +4364,7 @@ class Encoder {
4325
4364
  const encoder = ctor[$encoder];
4326
4365
  const metadata = ctor[Symbol.metadata];
4327
4366
  bytes[it.offset++] = SWITCH_TO_STRUCTURE & 255;
4328
- encode.number(bytes, changeTree.refId, it);
4367
+ encode.number(bytes, ref[$refId], it);
4329
4368
  for (let i = 0, numChanges = keys.length; i < numChanges; i++) {
4330
4369
  const index = Number(keys[i]);
4331
4370
  // workaround when using view.add() on item that has been deleted from state (see test "adding to view item that has been removed from state")
@@ -4344,10 +4383,7 @@ class Encoder {
4344
4383
  view.changes.clear();
4345
4384
  // try to encode "filtered" changes
4346
4385
  this.encode(it, view, bytes, "filteredChanges", false, viewOffset);
4347
- return Buffer.concat([
4348
- bytes.subarray(0, sharedOffset),
4349
- bytes.subarray(viewOffset, it.offset)
4350
- ]);
4386
+ return concatBytes(bytes.subarray(0, sharedOffset), bytes.subarray(viewOffset, it.offset));
4351
4387
  }
4352
4388
  discardChanges() {
4353
4389
  // discard shared changes
@@ -4403,25 +4439,22 @@ class DecodingWarning extends Error {
4403
4439
  }
4404
4440
  }
4405
4441
  class ReferenceTracker {
4406
- constructor() {
4407
- //
4408
- // Relation of refId => Schema structure
4409
- // For direct access of structures during decoding time.
4410
- //
4411
- this.refs = new Map();
4412
- this.refIds = new WeakMap();
4413
- this.refCount = {};
4414
- this.deletedRefs = new Set();
4415
- this.callbacks = {};
4416
- this.nextUniqueId = 0;
4417
- }
4442
+ //
4443
+ // Relation of refId => Schema structure
4444
+ // For direct access of structures during decoding time.
4445
+ //
4446
+ refs = new Map();
4447
+ refCount = {};
4448
+ deletedRefs = new Set();
4449
+ callbacks = {};
4450
+ nextUniqueId = 0;
4418
4451
  getNextUniqueId() {
4419
4452
  return this.nextUniqueId++;
4420
4453
  }
4421
4454
  // for decoding
4422
4455
  addRef(refId, ref, incrementCount = true) {
4423
4456
  this.refs.set(refId, ref);
4424
- this.refIds.set(ref, refId);
4457
+ ref[$refId] = refId;
4425
4458
  if (incrementCount) {
4426
4459
  this.refCount[refId] = (this.refCount[refId] || 0) + 1;
4427
4460
  }
@@ -4478,9 +4511,12 @@ class ReferenceTracker {
4478
4511
  const metadata = ref.constructor[Symbol.metadata];
4479
4512
  for (const index in metadata) {
4480
4513
  const field = metadata[index].name;
4481
- const childRefId = typeof (ref[field]) === "object" && this.refIds.get(ref[field]);
4482
- if (childRefId && !this.deletedRefs.has(childRefId)) {
4483
- this.removeRef(childRefId);
4514
+ const child = ref[field];
4515
+ if (typeof (child) === "object" && child) {
4516
+ const childRefId = child[$refId];
4517
+ if (childRefId !== undefined && !this.deletedRefs.has(childRefId)) {
4518
+ this.removeRef(childRefId);
4519
+ }
4484
4520
  }
4485
4521
  }
4486
4522
  }
@@ -4488,8 +4524,8 @@ class ReferenceTracker {
4488
4524
  if (typeof (ref[$childType]) === "function") {
4489
4525
  Array.from(ref.values())
4490
4526
  .forEach((child) => {
4491
- const childRefId = this.refIds.get(child);
4492
- if (!this.deletedRefs.has(childRefId)) {
4527
+ const childRefId = child[$refId];
4528
+ if (childRefId !== undefined && !this.deletedRefs.has(childRefId)) {
4493
4529
  this.removeRef(childRefId);
4494
4530
  }
4495
4531
  });
@@ -4527,8 +4563,12 @@ class ReferenceTracker {
4527
4563
  }
4528
4564
 
4529
4565
  class Decoder {
4566
+ context;
4567
+ state;
4568
+ root;
4569
+ currentRefId = 0;
4570
+ triggerChanges;
4530
4571
  constructor(root, context) {
4531
- this.currentRefId = 0;
4532
4572
  this.setState(root);
4533
4573
  this.context = context || new TypeContext(root.constructor);
4534
4574
  // console.log(">>>>>>>>>>>>>>>> Decoder types");
@@ -4617,7 +4657,7 @@ class Decoder {
4617
4657
  }
4618
4658
  removeChildRefs(ref, allChanges) {
4619
4659
  const needRemoveRef = typeof (ref[$childType]) !== "string";
4620
- const refId = this.root.refIds.get(ref);
4660
+ const refId = ref[$refId];
4621
4661
  ref.forEach((value, key) => {
4622
4662
  allChanges.push({
4623
4663
  ref: ref,
@@ -4628,7 +4668,7 @@ class Decoder {
4628
4668
  previousValue: value
4629
4669
  });
4630
4670
  if (needRemoveRef) {
4631
- this.root.removeRef(this.root.refIds.get(value));
4671
+ this.root.removeRef(value[$refId]);
4632
4672
  }
4633
4673
  });
4634
4674
  }
@@ -4637,217 +4677,182 @@ class Decoder {
4637
4677
  /**
4638
4678
  * Reflection
4639
4679
  */
4640
- class ReflectionField extends Schema {
4641
- }
4642
- __decorate([
4643
- type("string")
4644
- ], ReflectionField.prototype, "name", void 0);
4645
- __decorate([
4646
- type("string")
4647
- ], ReflectionField.prototype, "type", void 0);
4648
- __decorate([
4649
- type("number")
4650
- ], ReflectionField.prototype, "referencedType", void 0);
4651
- class ReflectionType extends Schema {
4652
- constructor() {
4653
- super(...arguments);
4654
- this.fields = new ArraySchema();
4655
- }
4656
- }
4657
- __decorate([
4658
- type("number")
4659
- ], ReflectionType.prototype, "id", void 0);
4660
- __decorate([
4661
- type("number")
4662
- ], ReflectionType.prototype, "extendsId", void 0);
4663
- __decorate([
4664
- type([ReflectionField])
4665
- ], ReflectionType.prototype, "fields", void 0);
4666
- class Reflection extends Schema {
4667
- constructor() {
4668
- super(...arguments);
4669
- this.types = new ArraySchema();
4670
- }
4671
- /**
4672
- * Encodes the TypeContext of an Encoder into a buffer.
4673
- *
4674
- * @param encoder Encoder instance
4675
- * @param it
4676
- * @returns
4677
- */
4678
- static encode(encoder, it = { offset: 0 }) {
4679
- const context = encoder.context;
4680
- const reflection = new Reflection();
4681
- const reflectionEncoder = new Encoder(reflection);
4682
- // rootType is usually the first schema passed to the Encoder
4683
- // (unless it inherits from another schema)
4684
- const rootType = context.schemas.get(encoder.state.constructor);
4685
- if (rootType > 0) {
4686
- reflection.rootType = rootType;
4687
- }
4688
- const includedTypeIds = new Set();
4689
- const pendingReflectionTypes = {};
4690
- // add type to reflection in a way that respects inheritance
4691
- // (parent types should be added before their children)
4692
- const addType = (type) => {
4693
- if (type.extendsId === undefined || includedTypeIds.has(type.extendsId)) {
4694
- includedTypeIds.add(type.id);
4695
- reflection.types.push(type);
4696
- const deps = pendingReflectionTypes[type.id];
4697
- if (deps !== undefined) {
4698
- delete pendingReflectionTypes[type.id];
4699
- deps.forEach((childType) => addType(childType));
4700
- }
4680
+ const ReflectionField = schema({
4681
+ name: "string",
4682
+ type: "string",
4683
+ referencedType: "number",
4684
+ });
4685
+ const ReflectionType = schema({
4686
+ id: "number",
4687
+ extendsId: "number",
4688
+ fields: [ReflectionField],
4689
+ });
4690
+ const Reflection = schema({
4691
+ types: [ReflectionType],
4692
+ rootType: "number",
4693
+ });
4694
+ Reflection.encode = function (encoder, it = { offset: 0 }) {
4695
+ const context = encoder.context;
4696
+ const reflection = new Reflection();
4697
+ const reflectionEncoder = new Encoder(reflection);
4698
+ // rootType is usually the first schema passed to the Encoder
4699
+ // (unless it inherits from another schema)
4700
+ const rootType = context.schemas.get(encoder.state.constructor);
4701
+ if (rootType > 0) {
4702
+ reflection.rootType = rootType;
4703
+ }
4704
+ const includedTypeIds = new Set();
4705
+ const pendingReflectionTypes = {};
4706
+ // add type to reflection in a way that respects inheritance
4707
+ // (parent types should be added before their children)
4708
+ const addType = (type) => {
4709
+ if (type.extendsId === undefined || includedTypeIds.has(type.extendsId)) {
4710
+ includedTypeIds.add(type.id);
4711
+ reflection.types.push(type);
4712
+ const deps = pendingReflectionTypes[type.id];
4713
+ if (deps !== undefined) {
4714
+ delete pendingReflectionTypes[type.id];
4715
+ deps.forEach((childType) => addType(childType));
4701
4716
  }
4702
- else {
4703
- if (pendingReflectionTypes[type.extendsId] === undefined) {
4704
- pendingReflectionTypes[type.extendsId] = [];
4705
- }
4706
- pendingReflectionTypes[type.extendsId].push(type);
4717
+ }
4718
+ else {
4719
+ if (pendingReflectionTypes[type.extendsId] === undefined) {
4720
+ pendingReflectionTypes[type.extendsId] = [];
4707
4721
  }
4708
- };
4709
- context.schemas.forEach((typeid, klass) => {
4710
- const type = new ReflectionType();
4711
- type.id = Number(typeid);
4712
- // support inheritance
4713
- const inheritFrom = Object.getPrototypeOf(klass);
4714
- if (inheritFrom !== Schema) {
4715
- type.extendsId = context.schemas.get(inheritFrom);
4716
- }
4717
- const metadata = klass[Symbol.metadata];
4718
- //
4719
- // FIXME: this is a workaround for inherited types without additional fields
4720
- // if metadata is the same reference as the parent class - it means the class has no own metadata
4721
- //
4722
- if (metadata !== inheritFrom[Symbol.metadata]) {
4723
- for (const fieldIndex in metadata) {
4724
- const index = Number(fieldIndex);
4725
- const fieldName = metadata[index].name;
4726
- // skip fields from parent classes
4727
- if (!Object.prototype.hasOwnProperty.call(metadata, fieldName)) {
4728
- continue;
4729
- }
4730
- const reflectionField = new ReflectionField();
4731
- reflectionField.name = fieldName;
4732
- let fieldType;
4733
- const field = metadata[index];
4734
- if (typeof (field.type) === "string") {
4735
- fieldType = field.type;
4722
+ pendingReflectionTypes[type.extendsId].push(type);
4723
+ }
4724
+ };
4725
+ context.schemas.forEach((typeid, klass) => {
4726
+ const type = new ReflectionType();
4727
+ type.id = Number(typeid);
4728
+ // support inheritance
4729
+ const inheritFrom = Object.getPrototypeOf(klass);
4730
+ if (inheritFrom !== Schema) {
4731
+ type.extendsId = context.schemas.get(inheritFrom);
4732
+ }
4733
+ const metadata = klass[Symbol.metadata];
4734
+ //
4735
+ // FIXME: this is a workaround for inherited types without additional fields
4736
+ // if metadata is the same reference as the parent class - it means the class has no own metadata
4737
+ //
4738
+ if (metadata !== inheritFrom[Symbol.metadata]) {
4739
+ for (const fieldIndex in metadata) {
4740
+ const index = Number(fieldIndex);
4741
+ const fieldName = metadata[index].name;
4742
+ // skip fields from parent classes
4743
+ if (!Object.prototype.hasOwnProperty.call(metadata, fieldName)) {
4744
+ continue;
4745
+ }
4746
+ const reflectionField = new ReflectionField();
4747
+ reflectionField.name = fieldName;
4748
+ let fieldType;
4749
+ const field = metadata[index];
4750
+ if (typeof (field.type) === "string") {
4751
+ fieldType = field.type;
4752
+ }
4753
+ else {
4754
+ let childTypeSchema;
4755
+ //
4756
+ // TODO: refactor below.
4757
+ //
4758
+ if (Schema.is(field.type)) {
4759
+ fieldType = "ref";
4760
+ childTypeSchema = field.type;
4736
4761
  }
4737
4762
  else {
4738
- let childTypeSchema;
4739
- //
4740
- // TODO: refactor below.
4741
- //
4742
- if (Schema.is(field.type)) {
4743
- fieldType = "ref";
4744
- childTypeSchema = field.type;
4763
+ fieldType = Object.keys(field.type)[0];
4764
+ if (typeof (field.type[fieldType]) === "string") {
4765
+ fieldType += ":" + field.type[fieldType]; // array:string
4745
4766
  }
4746
4767
  else {
4747
- fieldType = Object.keys(field.type)[0];
4748
- if (typeof (field.type[fieldType]) === "string") {
4749
- fieldType += ":" + field.type[fieldType]; // array:string
4750
- }
4751
- else {
4752
- childTypeSchema = field.type[fieldType];
4753
- }
4768
+ childTypeSchema = field.type[fieldType];
4754
4769
  }
4755
- reflectionField.referencedType = (childTypeSchema)
4756
- ? context.getTypeId(childTypeSchema)
4757
- : -1;
4758
4770
  }
4759
- reflectionField.type = fieldType;
4760
- type.fields.push(reflectionField);
4771
+ reflectionField.referencedType = (childTypeSchema)
4772
+ ? context.getTypeId(childTypeSchema)
4773
+ : -1;
4761
4774
  }
4775
+ reflectionField.type = fieldType;
4776
+ type.fields.push(reflectionField);
4762
4777
  }
4763
- addType(type);
4764
- });
4765
- // in case there are types that were not added due to inheritance
4766
- for (const typeid in pendingReflectionTypes) {
4767
- pendingReflectionTypes[typeid].forEach((type) => reflection.types.push(type));
4768
4778
  }
4769
- const buf = reflectionEncoder.encodeAll(it);
4770
- return buf.slice(0, it.offset);
4771
- // return Buffer.from(buf, 0, it.offset);
4779
+ addType(type);
4780
+ });
4781
+ // in case there are types that were not added due to inheritance
4782
+ for (const typeid in pendingReflectionTypes) {
4783
+ pendingReflectionTypes[typeid].forEach((type) => reflection.types.push(type));
4772
4784
  }
4773
- /**
4774
- * Decodes the TypeContext from a buffer into a Decoder instance.
4775
- *
4776
- * @param bytes Reflection.encode() output
4777
- * @param it
4778
- * @returns Decoder instance
4779
- */
4780
- static decode(bytes, it) {
4781
- const reflection = new Reflection();
4782
- const reflectionDecoder = new Decoder(reflection);
4783
- reflectionDecoder.decode(bytes, it);
4784
- const typeContext = new TypeContext();
4785
- // 1st pass, initialize metadata + inheritance
4786
- reflection.types.forEach((reflectionType) => {
4787
- const parentClass = typeContext.get(reflectionType.extendsId) ?? Schema;
4788
- const schema = class _ extends parentClass {
4789
- };
4790
- // register for inheritance support
4791
- TypeContext.register(schema);
4792
- // // for inheritance support
4793
- // Metadata.initialize(schema);
4794
- typeContext.add(schema, reflectionType.id);
4795
- }, {});
4796
- // define fields
4797
- const addFields = (metadata, reflectionType, parentFieldIndex) => {
4798
- reflectionType.fields.forEach((field, i) => {
4799
- const fieldIndex = parentFieldIndex + i;
4800
- if (field.referencedType !== undefined) {
4801
- let fieldType = field.type;
4802
- let refType = typeContext.get(field.referencedType);
4803
- // map or array of primitive type (-1)
4804
- if (!refType) {
4805
- const typeInfo = field.type.split(":");
4806
- fieldType = typeInfo[0];
4807
- refType = typeInfo[1]; // string
4808
- }
4809
- if (fieldType === "ref") {
4810
- Metadata.addField(metadata, fieldIndex, field.name, refType);
4811
- }
4812
- else {
4813
- Metadata.addField(metadata, fieldIndex, field.name, { [fieldType]: refType });
4814
- }
4785
+ const buf = reflectionEncoder.encodeAll(it);
4786
+ return buf.slice(0, it.offset);
4787
+ };
4788
+ Reflection.decode = function (bytes, it) {
4789
+ const reflection = new Reflection();
4790
+ const reflectionDecoder = new Decoder(reflection);
4791
+ reflectionDecoder.decode(bytes, it);
4792
+ const typeContext = new TypeContext();
4793
+ // 1st pass, initialize metadata + inheritance
4794
+ reflection.types.forEach((reflectionType) => {
4795
+ const parentClass = typeContext.get(reflectionType.extendsId) ?? Schema;
4796
+ const schema = class _ extends parentClass {
4797
+ };
4798
+ // register for inheritance support
4799
+ TypeContext.register(schema);
4800
+ typeContext.add(schema, reflectionType.id);
4801
+ }, {});
4802
+ // define fields
4803
+ const addFields = (metadata, reflectionType, parentFieldIndex) => {
4804
+ reflectionType.fields.forEach((field, i) => {
4805
+ const fieldIndex = parentFieldIndex + i;
4806
+ if (field.referencedType !== undefined) {
4807
+ let fieldType = field.type;
4808
+ let refType = typeContext.get(field.referencedType);
4809
+ // map or array of primitive type (-1)
4810
+ if (!refType) {
4811
+ const typeInfo = field.type.split(":");
4812
+ fieldType = typeInfo[0];
4813
+ refType = typeInfo[1]; // string
4814
+ }
4815
+ if (fieldType === "ref") {
4816
+ Metadata.addField(metadata, fieldIndex, field.name, refType);
4815
4817
  }
4816
4818
  else {
4817
- Metadata.addField(metadata, fieldIndex, field.name, field.type);
4819
+ Metadata.addField(metadata, fieldIndex, field.name, { [fieldType]: refType });
4818
4820
  }
4819
- });
4820
- };
4821
- // 2nd pass, set fields
4822
- reflection.types.forEach((reflectionType) => {
4823
- const schema = typeContext.get(reflectionType.id);
4824
- // for inheritance support
4825
- const metadata = Metadata.initialize(schema);
4826
- const inheritedTypes = [];
4827
- let parentType = reflectionType;
4828
- do {
4829
- inheritedTypes.push(parentType);
4830
- parentType = reflection.types.find((t) => t.id === parentType.extendsId);
4831
- } while (parentType);
4832
- let parentFieldIndex = 0;
4833
- inheritedTypes.reverse().forEach((reflectionType) => {
4834
- // add fields from all inherited classes
4835
- // TODO: refactor this to avoid adding fields from parent classes
4836
- addFields(metadata, reflectionType, parentFieldIndex);
4837
- parentFieldIndex += reflectionType.fields.length;
4838
- });
4821
+ }
4822
+ else {
4823
+ Metadata.addField(metadata, fieldIndex, field.name, field.type);
4824
+ }
4839
4825
  });
4840
- const state = new (typeContext.get(reflection.rootType || 0))();
4841
- return new Decoder(state, typeContext);
4842
- }
4843
- }
4844
- __decorate([
4845
- type([ReflectionType])
4846
- ], Reflection.prototype, "types", void 0);
4847
- __decorate([
4848
- type("number")
4849
- ], Reflection.prototype, "rootType", void 0);
4826
+ };
4827
+ // 2nd pass, set fields
4828
+ reflection.types.forEach((reflectionType) => {
4829
+ const schema = typeContext.get(reflectionType.id);
4830
+ // for inheritance support
4831
+ const metadata = Metadata.initialize(schema);
4832
+ const inheritedTypes = [];
4833
+ let parentType = reflectionType;
4834
+ do {
4835
+ inheritedTypes.push(parentType);
4836
+ parentType = reflection.types.find((t) => t.id === parentType.extendsId);
4837
+ } while (parentType);
4838
+ let parentFieldIndex = 0;
4839
+ inheritedTypes.reverse().forEach((reflectionType) => {
4840
+ // add fields from all inherited classes
4841
+ // TODO: refactor this to avoid adding fields from parent classes
4842
+ addFields(metadata, reflectionType, parentFieldIndex);
4843
+ parentFieldIndex += reflectionType.fields.length;
4844
+ });
4845
+ });
4846
+ const state = new (typeContext.get(reflection.rootType || 0))();
4847
+ return new Decoder(state, typeContext);
4848
+ };
4850
4849
 
4850
+ /**
4851
+ * Legacy callback system
4852
+ *
4853
+ * @param decoder
4854
+ * @returns
4855
+ */
4851
4856
  function getDecoderStateCallbacks(decoder) {
4852
4857
  const $root = decoder.root;
4853
4858
  const callbacks = $root.callbacks;
@@ -4868,7 +4873,7 @@ function getDecoderStateCallbacks(decoder) {
4868
4873
  //
4869
4874
  if ((change.op & exports.OPERATION.DELETE) === exports.OPERATION.DELETE &&
4870
4875
  change.previousValue instanceof Schema) {
4871
- const deleteCallbacks = callbacks[$root.refIds.get(change.previousValue)]?.[exports.OPERATION.DELETE];
4876
+ const deleteCallbacks = callbacks[change.previousValue[$refId]]?.[exports.OPERATION.DELETE];
4872
4877
  for (let i = deleteCallbacks?.length - 1; i >= 0; i--) {
4873
4878
  deleteCallbacks[i]();
4874
4879
  }
@@ -4957,7 +4962,7 @@ function getDecoderStateCallbacks(decoder) {
4957
4962
  ) {
4958
4963
  callback(context.instance[prop], undefined);
4959
4964
  }
4960
- return $root.addCallback($root.refIds.get(ref), prop, callback);
4965
+ return $root.addCallback(ref[$refId], prop, callback);
4961
4966
  };
4962
4967
  /**
4963
4968
  * Schema instances
@@ -4977,7 +4982,7 @@ function getDecoderStateCallbacks(decoder) {
4977
4982
  }
4978
4983
  },
4979
4984
  onChange: function onChange(callback) {
4980
- return $root.addCallback($root.refIds.get(context.instance), exports.OPERATION.REPLACE, callback);
4985
+ return $root.addCallback(context.instance[$refId], exports.OPERATION.REPLACE, callback);
4981
4986
  },
4982
4987
  //
4983
4988
  // TODO: refactor `bindTo()` implementation.
@@ -4987,7 +4992,7 @@ function getDecoderStateCallbacks(decoder) {
4987
4992
  if (!properties) {
4988
4993
  properties = Object.keys(metadata).map((index) => metadata[index].name);
4989
4994
  }
4990
- return $root.addCallback($root.refIds.get(context.instance), exports.OPERATION.REPLACE, () => {
4995
+ return $root.addCallback(context.instance[$refId], exports.OPERATION.REPLACE, () => {
4991
4996
  properties.forEach((prop) => targetObject[prop] = context.instance[prop]);
4992
4997
  });
4993
4998
  }
@@ -5006,13 +5011,13 @@ function getDecoderStateCallbacks(decoder) {
5006
5011
  unbind?.();
5007
5012
  }, false);
5008
5013
  // has existing value
5009
- if ($root.refIds.get(instance) !== undefined) {
5014
+ if (instance?.[$refId] !== undefined) {
5010
5015
  callback(instance, true);
5011
5016
  }
5012
5017
  });
5013
5018
  return getProxy(metadataField.type, {
5014
5019
  // make sure refId is available, otherwise need to wait for the instance to be available.
5015
- instance: ($root.refIds.get(instance) && instance),
5020
+ instance: (instance?.[$refId] !== undefined && instance),
5016
5021
  parentInstance: context.instance,
5017
5022
  onInstanceAvailable,
5018
5023
  });
@@ -5036,7 +5041,7 @@ function getDecoderStateCallbacks(decoder) {
5036
5041
  if (immediate) {
5037
5042
  ref.forEach((v, k) => callback(v, k));
5038
5043
  }
5039
- return $root.addCallback($root.refIds.get(ref), exports.OPERATION.ADD, (value, key) => {
5044
+ return $root.addCallback(ref[$refId], exports.OPERATION.ADD, (value, key) => {
5040
5045
  onAddCalls.set(callback, true);
5041
5046
  currentOnAddCallback = callback;
5042
5047
  callback(value, key);
@@ -5045,10 +5050,10 @@ function getDecoderStateCallbacks(decoder) {
5045
5050
  });
5046
5051
  };
5047
5052
  const onRemove = function (ref, callback) {
5048
- return $root.addCallback($root.refIds.get(ref), exports.OPERATION.DELETE, callback);
5053
+ return $root.addCallback(ref[$refId], exports.OPERATION.DELETE, callback);
5049
5054
  };
5050
5055
  const onChange = function (ref, callback) {
5051
- return $root.addCallback($root.refIds.get(ref), exports.OPERATION.REPLACE, callback);
5056
+ return $root.addCallback(ref[$refId], exports.OPERATION.REPLACE, callback);
5052
5057
  };
5053
5058
  return new Proxy({
5054
5059
  onAdd: function (callback, immediate = true) {
@@ -5117,22 +5122,360 @@ function getRawChangesCallback(decoder, callback) {
5117
5122
  decoder.triggerChanges = callback;
5118
5123
  }
5119
5124
 
5125
+ /**
5126
+ * State Callbacks handler
5127
+ *
5128
+ * Usage:
5129
+ * ```ts
5130
+ * const $ = Callbacks.get(decoder);
5131
+ *
5132
+ * // Listen to property changes
5133
+ * $.listen("currentTurn", (currentValue, previousValue) => { ... });
5134
+ *
5135
+ * // Listen to collection additions
5136
+ * $.onAdd("entities", (sessionId, entity) => {
5137
+ * // Nested property listening
5138
+ * $.listen(entity, "hp", (currentHp, previousHp) => { ... });
5139
+ * });
5140
+ *
5141
+ * // Listen to collection removals
5142
+ * $.onRemove("entities", (sessionId, entity) => { ... });
5143
+ *
5144
+ * // Listen to any property change on an instance
5145
+ * $.onChange(entity, () => { ... });
5146
+ *
5147
+ * // Bind properties to another object
5148
+ * $.bindTo(player, playerVisual);
5149
+ * ```
5150
+ */
5151
+ class StateCallbackStrategy {
5152
+ decoder;
5153
+ uniqueRefIds = new Set();
5154
+ isTriggering = false;
5155
+ constructor(decoder) {
5156
+ this.decoder = decoder;
5157
+ this.decoder.triggerChanges = this.triggerChanges.bind(this);
5158
+ }
5159
+ get callbacks() {
5160
+ return this.decoder.root.callbacks;
5161
+ }
5162
+ get state() {
5163
+ return this.decoder.state;
5164
+ }
5165
+ addCallback(refId, operationOrProperty, handler) {
5166
+ const $root = this.decoder.root;
5167
+ return $root.addCallback(refId, operationOrProperty, handler);
5168
+ }
5169
+ addCallbackOrWaitCollectionAvailable(instance, propertyName, operation, handler, immediate = true) {
5170
+ let removeHandler = () => { };
5171
+ const removeOnAdd = () => removeHandler();
5172
+ const collection = instance[propertyName];
5173
+ // Collection not available yet. Listen for its availability before attaching the handler.
5174
+ if (collection === null || collection === undefined) {
5175
+ removeHandler = this.addCallback(instance[$refId], propertyName, (value, _) => {
5176
+ if (value !== null && value !== undefined) {
5177
+ removeHandler = this.addCallback(value[$refId], operation, handler);
5178
+ }
5179
+ });
5180
+ return removeOnAdd;
5181
+ }
5182
+ else {
5183
+ //
5184
+ // Call immediately if collection is already available, if it's an ADD operation.
5185
+ //
5186
+ immediate = immediate && this.isTriggering === false;
5187
+ if (operation === exports.OPERATION.ADD && immediate) {
5188
+ collection.forEach((value, key) => {
5189
+ handler(key, value);
5190
+ });
5191
+ }
5192
+ return this.addCallback(collection[$refId], operation, handler);
5193
+ }
5194
+ }
5195
+ listen(...args) {
5196
+ if (typeof args[0] === 'string') {
5197
+ // listen(property, handler, immediate?)
5198
+ return this.listenInstance(this.state, args[0], args[1], args[2]);
5199
+ }
5200
+ else {
5201
+ // listen(instance, property, handler, immediate?)
5202
+ return this.listenInstance(args[0], args[1], args[2], args[3]);
5203
+ }
5204
+ }
5205
+ listenInstance(instance, propertyName, handler, immediate = true) {
5206
+ immediate = immediate && this.isTriggering === false;
5207
+ //
5208
+ // Call handler immediately if property is already available.
5209
+ //
5210
+ const currentValue = instance[propertyName];
5211
+ if (immediate && currentValue !== null && currentValue !== undefined) {
5212
+ handler(currentValue, undefined);
5213
+ }
5214
+ return this.addCallback(instance[$refId], propertyName, handler);
5215
+ }
5216
+ onChange(...args) {
5217
+ if (args.length === 2 && typeof args[0] !== 'string') {
5218
+ // onChange(instance, handler) - instance change
5219
+ const instance = args[0];
5220
+ const handler = args[1];
5221
+ return this.addCallback(instance[$refId], exports.OPERATION.REPLACE, handler);
5222
+ }
5223
+ if (typeof args[0] === 'string') {
5224
+ // onChange(property, handler) - collection on root state
5225
+ return this.addCallbackOrWaitCollectionAvailable(this.state, args[0], exports.OPERATION.REPLACE, args[1]);
5226
+ }
5227
+ else {
5228
+ // onChange(instance, property, handler) - nested collection
5229
+ return this.addCallbackOrWaitCollectionAvailable(args[0], args[1], exports.OPERATION.REPLACE, args[2]);
5230
+ }
5231
+ }
5232
+ onAdd(...args) {
5233
+ if (typeof args[0] === 'string') {
5234
+ // onAdd(property, handler, immediate?) - collection on root state
5235
+ return this.addCallbackOrWaitCollectionAvailable(this.state, args[0], exports.OPERATION.ADD, args[1], args[2] !== false);
5236
+ }
5237
+ else {
5238
+ // onAdd(instance, property, handler, immediate?) - nested collection
5239
+ return this.addCallbackOrWaitCollectionAvailable(args[0], args[1], exports.OPERATION.ADD, args[2], args[3] !== false);
5240
+ }
5241
+ }
5242
+ onRemove(...args) {
5243
+ if (typeof args[0] === 'string') {
5244
+ // onRemove(property, handler) - collection on root state
5245
+ return this.addCallbackOrWaitCollectionAvailable(this.state, args[0], exports.OPERATION.DELETE, args[1]);
5246
+ }
5247
+ else {
5248
+ // onRemove(instance, property, handler) - nested collection
5249
+ return this.addCallbackOrWaitCollectionAvailable(args[0], args[1], exports.OPERATION.DELETE, args[2]);
5250
+ }
5251
+ }
5252
+ /**
5253
+ * Bind properties from a Schema instance to a target object.
5254
+ * Changes will be automatically reflected on the target object.
5255
+ */
5256
+ bindTo(from, to, properties, immediate = true) {
5257
+ const metadata = from.constructor[Symbol.metadata];
5258
+ // If no properties specified, bind all properties
5259
+ if (!properties) {
5260
+ properties = Object.keys(metadata)
5261
+ .filter(key => !isNaN(Number(key)))
5262
+ .map((index) => metadata[index].name);
5263
+ }
5264
+ const action = () => {
5265
+ for (const prop of properties) {
5266
+ const fromValue = from[prop];
5267
+ if (fromValue !== undefined) {
5268
+ to[prop] = fromValue;
5269
+ }
5270
+ }
5271
+ };
5272
+ if (immediate) {
5273
+ action();
5274
+ }
5275
+ return this.addCallback(from[$refId], exports.OPERATION.REPLACE, action);
5276
+ }
5277
+ triggerChanges(allChanges) {
5278
+ this.uniqueRefIds.clear();
5279
+ for (let i = 0, l = allChanges.length; i < l; i++) {
5280
+ const change = allChanges[i];
5281
+ const refId = change.refId;
5282
+ const ref = change.ref;
5283
+ const $callbacks = this.callbacks[refId];
5284
+ if (!$callbacks) {
5285
+ continue;
5286
+ }
5287
+ //
5288
+ // trigger onRemove on child structure.
5289
+ //
5290
+ if ((change.op & exports.OPERATION.DELETE) === exports.OPERATION.DELETE &&
5291
+ change.previousValue instanceof Schema) {
5292
+ const childRefId = change.previousValue[$refId];
5293
+ const deleteCallbacks = this.callbacks[childRefId]?.[exports.OPERATION.DELETE];
5294
+ if (deleteCallbacks) {
5295
+ for (let j = deleteCallbacks.length - 1; j >= 0; j--) {
5296
+ deleteCallbacks[j]();
5297
+ }
5298
+ }
5299
+ }
5300
+ if (ref instanceof Schema) {
5301
+ //
5302
+ // Handle Schema instance
5303
+ //
5304
+ if (!this.uniqueRefIds.has(refId)) {
5305
+ // trigger onChange
5306
+ const replaceCallbacks = $callbacks[exports.OPERATION.REPLACE];
5307
+ if (replaceCallbacks) {
5308
+ for (let j = replaceCallbacks.length - 1; j >= 0; j--) {
5309
+ try {
5310
+ replaceCallbacks[j]();
5311
+ }
5312
+ catch (e) {
5313
+ console.error(e);
5314
+ }
5315
+ }
5316
+ }
5317
+ }
5318
+ // trigger field callbacks
5319
+ const fieldCallbacks = $callbacks[change.field];
5320
+ if (fieldCallbacks) {
5321
+ for (let j = fieldCallbacks.length - 1; j >= 0; j--) {
5322
+ try {
5323
+ this.isTriggering = true;
5324
+ fieldCallbacks[j](change.value, change.previousValue);
5325
+ }
5326
+ catch (e) {
5327
+ console.error(e);
5328
+ }
5329
+ finally {
5330
+ this.isTriggering = false;
5331
+ }
5332
+ }
5333
+ }
5334
+ }
5335
+ else {
5336
+ //
5337
+ // Handle collection of items
5338
+ //
5339
+ const dynamicIndex = change.dynamicIndex ?? change.field;
5340
+ if ((change.op & exports.OPERATION.DELETE) === exports.OPERATION.DELETE) {
5341
+ //
5342
+ // FIXME: `previousValue` should always be available.
5343
+ //
5344
+ if (change.previousValue !== undefined) {
5345
+ // trigger onRemove (key, value)
5346
+ const deleteCallbacks = $callbacks[exports.OPERATION.DELETE];
5347
+ if (deleteCallbacks) {
5348
+ for (let j = deleteCallbacks.length - 1; j >= 0; j--) {
5349
+ deleteCallbacks[j](dynamicIndex, change.previousValue);
5350
+ }
5351
+ }
5352
+ }
5353
+ // Handle DELETE_AND_ADD operation
5354
+ if ((change.op & exports.OPERATION.ADD) === exports.OPERATION.ADD) {
5355
+ const addCallbacks = $callbacks[exports.OPERATION.ADD];
5356
+ if (addCallbacks) {
5357
+ this.isTriggering = true;
5358
+ for (let j = addCallbacks.length - 1; j >= 0; j--) {
5359
+ addCallbacks[j](dynamicIndex, change.value);
5360
+ }
5361
+ this.isTriggering = false;
5362
+ }
5363
+ }
5364
+ }
5365
+ else if ((change.op & exports.OPERATION.ADD) === exports.OPERATION.ADD &&
5366
+ change.previousValue !== change.value) {
5367
+ // trigger onAdd (key, value)
5368
+ const addCallbacks = $callbacks[exports.OPERATION.ADD];
5369
+ if (addCallbacks) {
5370
+ this.isTriggering = true;
5371
+ for (let j = addCallbacks.length - 1; j >= 0; j--) {
5372
+ addCallbacks[j](dynamicIndex, change.value);
5373
+ }
5374
+ this.isTriggering = false;
5375
+ }
5376
+ }
5377
+ // trigger onChange (key, value)
5378
+ if (change.value !== change.previousValue) {
5379
+ const replaceCallbacks = $callbacks[exports.OPERATION.REPLACE];
5380
+ if (replaceCallbacks) {
5381
+ for (let j = replaceCallbacks.length - 1; j >= 0; j--) {
5382
+ replaceCallbacks[j](dynamicIndex, change.value);
5383
+ }
5384
+ }
5385
+ }
5386
+ }
5387
+ this.uniqueRefIds.add(refId);
5388
+ }
5389
+ }
5390
+ }
5391
+ /**
5392
+ * Factory class for retrieving the callbacks API.
5393
+ */
5394
+ const Callbacks = {
5395
+ /**
5396
+ * Get the new callbacks standard API.
5397
+ *
5398
+ * Usage:
5399
+ * ```ts
5400
+ * const callbacks = Callbacks.get(roomOrDecoder);
5401
+ *
5402
+ * // Listen to property changes
5403
+ * callbacks.listen("currentTurn", (currentValue, previousValue) => { ... });
5404
+ *
5405
+ * // Listen to collection additions
5406
+ * callbacks.onAdd("entities", (sessionId, entity) => {
5407
+ * // Nested property listening
5408
+ * callbacks.listen(entity, "hp", (currentHp, previousHp) => { ... });
5409
+ * });
5410
+ *
5411
+ * // Listen to collection removals
5412
+ * callbacks.onRemove("entities", (sessionId, entity) => { ... });
5413
+ *
5414
+ * // Listen to any property change on an instance
5415
+ * callbacks.onChange(entity, () => { ... });
5416
+ *
5417
+ * // Bind properties to another object
5418
+ * callbacks.bindTo(player, playerVisual);
5419
+ * ```
5420
+ *
5421
+ * @param roomOrDecoder - Room or Decoder instance to get the callbacks for.
5422
+ * @returns the new callbacks standard API.
5423
+ */
5424
+ get(roomOrDecoder) {
5425
+ if (roomOrDecoder instanceof Decoder) {
5426
+ return new StateCallbackStrategy(roomOrDecoder);
5427
+ }
5428
+ else if (roomOrDecoder.serializer.decoder) {
5429
+ return new StateCallbackStrategy(roomOrDecoder.serializer.decoder);
5430
+ }
5431
+ else {
5432
+ throw new Error('Invalid room or decoder');
5433
+ }
5434
+ },
5435
+ /**
5436
+ * Get the legacy callbacks API.
5437
+ *
5438
+ * We aim to deprecate this API on 1.0, and iterate on improving Callbacks.get() API.
5439
+ *
5440
+ * @param roomOrDecoder - Room or Decoder instance to get the legacy callbacks for.
5441
+ * @returns the legacy callbacks API.
5442
+ */
5443
+ getLegacy(roomOrDecoder) {
5444
+ if (roomOrDecoder instanceof Decoder) {
5445
+ return getDecoderStateCallbacks(roomOrDecoder);
5446
+ }
5447
+ else if (roomOrDecoder.serializer.decoder) {
5448
+ return getDecoderStateCallbacks(roomOrDecoder.serializer.decoder);
5449
+ }
5450
+ },
5451
+ getRawChanges(decoder, callback) {
5452
+ return getRawChangesCallback(decoder, callback);
5453
+ }
5454
+ };
5455
+
5120
5456
  class StateView {
5457
+ iterable;
5458
+ /**
5459
+ * Iterable list of items that are visible to this view
5460
+ * (Available only if constructed with `iterable: true`)
5461
+ */
5462
+ items;
5463
+ /**
5464
+ * List of ChangeTree's that are visible to this view
5465
+ */
5466
+ visible = new WeakSet();
5467
+ /**
5468
+ * List of ChangeTree's that are invisible to this view
5469
+ */
5470
+ invisible = new WeakSet();
5471
+ tags; // TODO: use bit manipulation instead of Set<number> ()
5472
+ /**
5473
+ * Manual "ADD" operations for changes per ChangeTree, specific to this view.
5474
+ * (This is used to force encoding a property, even if it was not changed)
5475
+ */
5476
+ changes = new Map();
5121
5477
  constructor(iterable = false) {
5122
5478
  this.iterable = iterable;
5123
- /**
5124
- * List of ChangeTree's that are visible to this view
5125
- */
5126
- this.visible = new WeakSet();
5127
- /**
5128
- * List of ChangeTree's that are invisible to this view
5129
- */
5130
- this.invisible = new WeakSet();
5131
- /**
5132
- * Manual "ADD" operations for changes per ChangeTree, specific to this view.
5133
- * (This is used to force encoding a property, even if it was not changed)
5134
- */
5135
- this.changes = new Map();
5136
5479
  if (iterable) {
5137
5480
  this.items = [];
5138
5481
  }
@@ -5146,7 +5489,7 @@ class StateView {
5146
5489
  return false;
5147
5490
  }
5148
5491
  else if (!parentChangeTree &&
5149
- changeTree.refId !== 0 // allow root object
5492
+ obj[$refId] !== 0 // allow root object
5150
5493
  ) {
5151
5494
  /**
5152
5495
  * TODO: can we avoid this?
@@ -5170,11 +5513,11 @@ class StateView {
5170
5513
  if (checkIncludeParent && parentChangeTree) {
5171
5514
  this.addParentOf(changeTree, tag);
5172
5515
  }
5173
- let changes = this.changes.get(changeTree.refId);
5516
+ let changes = this.changes.get(obj[$refId]);
5174
5517
  if (changes === undefined) {
5175
5518
  changes = {};
5176
5519
  // FIXME / OPTIMIZE: do not add if no changes are needed
5177
- this.changes.set(changeTree.refId, changes);
5520
+ this.changes.set(obj[$refId], changes);
5178
5521
  }
5179
5522
  let isChildAdded = false;
5180
5523
  //
@@ -5254,10 +5597,10 @@ class StateView {
5254
5597
  }
5255
5598
  // add parent's tag properties
5256
5599
  if (changeTree.getChange(parentIndex) !== exports.OPERATION.DELETE) {
5257
- let changes = this.changes.get(changeTree.refId);
5600
+ let changes = this.changes.get(changeTree.ref[$refId]);
5258
5601
  if (changes === undefined) {
5259
5602
  changes = {};
5260
- this.changes.set(changeTree.refId, changes);
5603
+ this.changes.set(changeTree.ref[$refId], changes);
5261
5604
  }
5262
5605
  if (!this.tags) {
5263
5606
  this.tags = new WeakMap();
@@ -5289,27 +5632,28 @@ class StateView {
5289
5632
  }
5290
5633
  const ref = changeTree.ref;
5291
5634
  const metadata = ref.constructor[Symbol.metadata]; // ArraySchema/MapSchema do not have metadata
5292
- let changes = this.changes.get(changeTree.refId);
5635
+ const refId = ref[$refId];
5636
+ let changes = this.changes.get(refId);
5293
5637
  if (changes === undefined) {
5294
5638
  changes = {};
5295
- this.changes.set(changeTree.refId, changes);
5639
+ this.changes.set(refId, changes);
5296
5640
  }
5297
5641
  if (tag === DEFAULT_VIEW_TAG) {
5298
5642
  // parent is collection (Map/Array)
5299
5643
  const parent = changeTree.parent;
5300
5644
  if (parent && !Metadata.isValidInstance(parent) && changeTree.isFiltered) {
5301
- const parentChangeTree = parent[$changes];
5302
- let changes = this.changes.get(parentChangeTree.refId);
5645
+ const parentRefId = parent[$refId];
5646
+ let changes = this.changes.get(parentRefId);
5303
5647
  if (changes === undefined) {
5304
5648
  changes = {};
5305
- this.changes.set(parentChangeTree.refId, changes);
5649
+ this.changes.set(parentRefId, changes);
5306
5650
  }
5307
5651
  else if (changes[changeTree.parentIndex] === exports.OPERATION.ADD) {
5308
5652
  //
5309
5653
  // SAME PATCH ADD + REMOVE:
5310
5654
  // The 'changes' of deleted structure should be ignored.
5311
5655
  //
5312
- this.changes.delete(changeTree.refId);
5656
+ this.changes.delete(refId);
5313
5657
  }
5314
5658
  // DELETE / DELETE BY REF ID
5315
5659
  changes[changeTree.parentIndex] = exports.OPERATION.DELETE;
@@ -5369,7 +5713,7 @@ class StateView {
5369
5713
  if (!isVisible && changeTree.isVisibilitySharedWithParent) {
5370
5714
  // console.log("CHECK AGAINST PARENT...", {
5371
5715
  // ref: changeTree.ref.constructor.name,
5372
- // refId: changeTree.refId,
5716
+ // refId: changeTree.ref[$refId],
5373
5717
  // parent: changeTree.parent.constructor.name,
5374
5718
  // });
5375
5719
  if (this.visible.has(changeTree.parent[$changes])) {
@@ -5399,8 +5743,10 @@ exports.$deleteByIndex = $deleteByIndex;
5399
5743
  exports.$encoder = $encoder;
5400
5744
  exports.$filter = $filter;
5401
5745
  exports.$getByIndex = $getByIndex;
5746
+ exports.$refId = $refId;
5402
5747
  exports.$track = $track;
5403
5748
  exports.ArraySchema = ArraySchema;
5749
+ exports.Callbacks = Callbacks;
5404
5750
  exports.ChangeTree = ChangeTree;
5405
5751
  exports.CollectionSchema = CollectionSchema;
5406
5752
  exports.Decoder = Decoder;
@@ -5412,6 +5758,7 @@ exports.ReflectionField = ReflectionField;
5412
5758
  exports.ReflectionType = ReflectionType;
5413
5759
  exports.Schema = Schema;
5414
5760
  exports.SetSchema = SetSchema;
5761
+ exports.StateCallbackStrategy = StateCallbackStrategy;
5415
5762
  exports.StateView = StateView;
5416
5763
  exports.TypeContext = TypeContext;
5417
5764
  exports.decode = decode;