@colyseus/schema 3.0.76 → 4.0.1

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