@colyseus/schema 3.0.35 → 3.0.37

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.
package/README.md CHANGED
@@ -246,9 +246,9 @@ const decoder = new Decoder(state);
246
246
  decoder.decode(encodedBytes);
247
247
  ```
248
248
 
249
- ### Backwards/forwards compability
249
+ ### Backwards/forwards compatibility
250
250
 
251
- Backwards/fowards compatibility is possible by declaring new fields at the
251
+ Backwards/forwards compatibility is possible by declaring new fields at the
252
252
  end of existing structures, and earlier declarations to not be removed, but
253
253
  be marked `@deprecated()` when needed.
254
254
 
@@ -307,10 +307,10 @@ Each Colyseus SDK has its own decoder implementation of the `@colyseus/schema` p
307
307
 
308
308
  ## Why
309
309
 
310
- Initial thoghts/assumptions, for Colyseus:
310
+ Initial thoughts/assumptions, for Colyseus:
311
311
  - little to no bottleneck for detecting state changes.
312
- - have a schema definition on both server and client
313
- - better experience on staticaly-typed languages (C#, C++)
312
+ - have a schema definition on both the server and the client
313
+ - better experience on statically-typed languages (C#, C++)
314
314
  - mutations should be cheap.
315
315
 
316
316
  Practical Colyseus issues this should solve:
@@ -1032,19 +1032,35 @@ class ChangeTree {
1032
1032
  setRoot(root) {
1033
1033
  this.root = root;
1034
1034
  this.checkIsFiltered(this.parent, this.parentIndex);
1035
+ //
1036
+ // TODO: refactor and possibly unify .setRoot() and .setParent()
1037
+ //
1035
1038
  // Recursively set root on child structures
1036
1039
  const metadata = this.ref.constructor[Symbol.metadata];
1037
1040
  if (metadata) {
1038
1041
  metadata[$refTypeFieldIndexes]?.forEach((index) => {
1039
1042
  const field = metadata[index];
1040
- const value = this.ref[field.name];
1041
- value?.[$changes].setRoot(root);
1043
+ const changeTree = this.ref[field.name]?.[$changes];
1044
+ if (changeTree) {
1045
+ if (changeTree.root !== root) {
1046
+ changeTree.setRoot(root);
1047
+ }
1048
+ else {
1049
+ root.add(changeTree); // increment refCount
1050
+ }
1051
+ }
1042
1052
  });
1043
1053
  }
1044
1054
  else if (this.ref[$childType] && typeof (this.ref[$childType]) !== "string") {
1045
1055
  // MapSchema / ArraySchema, etc.
1046
1056
  this.ref.forEach((value, key) => {
1047
- value[$changes].setRoot(root);
1057
+ const changeTree = value[$changes];
1058
+ if (changeTree.root !== root) {
1059
+ changeTree.setRoot(root);
1060
+ }
1061
+ else {
1062
+ root.add(changeTree); // increment refCount
1063
+ }
1048
1064
  });
1049
1065
  }
1050
1066
  }
@@ -1068,14 +1084,19 @@ class ChangeTree {
1068
1084
  if (metadata) {
1069
1085
  metadata[$refTypeFieldIndexes]?.forEach((index) => {
1070
1086
  const field = metadata[index];
1071
- const value = this.ref[field.name];
1072
- value?.[$changes].setParent(this.ref, root, index);
1087
+ const changeTree = this.ref[field.name]?.[$changes];
1088
+ if (changeTree && changeTree.root !== root) {
1089
+ changeTree.setParent(this.ref, root, index);
1090
+ }
1073
1091
  });
1074
1092
  }
1075
1093
  else if (this.ref[$childType] && typeof (this.ref[$childType]) !== "string") {
1076
1094
  // MapSchema / ArraySchema, etc.
1077
1095
  this.ref.forEach((value, key) => {
1078
- value[$changes].setParent(this.ref, root, this.indexes[key] ?? key);
1096
+ const changeTree = value[$changes];
1097
+ if (changeTree.root !== root) {
1098
+ changeTree.setParent(this.ref, root, this.indexes[key] ?? key);
1099
+ }
1079
1100
  });
1080
1101
  }
1081
1102
  }
@@ -1307,12 +1328,11 @@ class ChangeTree {
1307
1328
  this.allFilteredChanges.indexes = {};
1308
1329
  this.allFilteredChanges.operations.length = 0;
1309
1330
  }
1310
- // remove children references
1311
- this.forEachChild((changeTree, _) => this.root?.remove(changeTree));
1312
1331
  }
1313
1332
  }
1314
1333
  /**
1315
1334
  * Recursively discard all changes from this, and child structures.
1335
+ * (Used in tests only)
1316
1336
  */
1317
1337
  discardAll() {
1318
1338
  const keys = Object.keys(this.indexedOperations);
@@ -1983,8 +2003,7 @@ class ArraySchema {
1983
2003
  push(...values) {
1984
2004
  let length = this.tmpItems.length;
1985
2005
  const changeTree = this[$changes];
1986
- // values.forEach((value, i) => {
1987
- for (let i = 0, l = values.length; i < values.length; i++, length++) {
2006
+ for (let i = 0, l = values.length; i < l; i++, length++) {
1988
2007
  const value = values[i];
1989
2008
  if (value === undefined || value === null) {
1990
2009
  // skip null values
@@ -2003,8 +2022,6 @@ class ArraySchema {
2003
2022
  //
2004
2023
  value[$changes]?.setParent(this, changeTree.root, length);
2005
2024
  }
2006
- // length++;
2007
- // });
2008
2025
  return length;
2009
2026
  }
2010
2027
  /**
@@ -2083,21 +2100,9 @@ class ArraySchema {
2083
2100
  }
2084
2101
  // discard previous operations.
2085
2102
  const changeTree = this[$changes];
2086
- // discard children
2087
- changeTree.forEachChild((changeTree, _) => {
2088
- changeTree.discard(true);
2089
- //
2090
- // TODO: add tests with instance sharing + .clear()
2091
- // FIXME: this.root? is required because it is being called at decoding time.
2092
- //
2093
- // TODO: do not use [$changes] at decoding time.
2094
- //
2095
- const root = changeTree.root;
2096
- if (root !== undefined) {
2097
- root.removeChangeFromChangeSet("changes", changeTree);
2098
- root.removeChangeFromChangeSet("allChanges", changeTree);
2099
- root.removeChangeFromChangeSet("allFilteredChanges", changeTree);
2100
- }
2103
+ // remove children references
2104
+ changeTree.forEachChild((childChangeTree, _) => {
2105
+ changeTree.root?.remove(childChangeTree);
2101
2106
  });
2102
2107
  changeTree.discard(true);
2103
2108
  changeTree.operation(exports.OPERATION.CLEAR);
@@ -2682,6 +2687,10 @@ class MapSchema {
2682
2687
  // discard previous operations.
2683
2688
  changeTree.discard(true);
2684
2689
  changeTree.indexes = {};
2690
+ // remove children references
2691
+ changeTree.forEachChild((childChangeTree, _) => {
2692
+ changeTree.root?.remove(childChangeTree);
2693
+ });
2685
2694
  // clear previous indexes
2686
2695
  this.$indexes.clear();
2687
2696
  // clear items
@@ -3279,6 +3288,10 @@ class Schema {
3279
3288
  }
3280
3289
  return obj;
3281
3290
  }
3291
+ /**
3292
+ * Used in tests only
3293
+ * @internal
3294
+ */
3282
3295
  discardAllChanges() {
3283
3296
  this[$changes].discardAll();
3284
3297
  }
@@ -3301,8 +3314,11 @@ class Schema {
3301
3314
  const contents = (showContents) ? ` - ${JSON.stringify(ref.toJSON())}` : "";
3302
3315
  const changeTree = ref[$changes];
3303
3316
  const refId = changeTree.refId;
3304
- let output = "";
3305
- output += `${getIndent(level)}${ref.constructor.name} (refId: ${refId})${contents}\n`;
3317
+ // log reference count if > 1
3318
+ const refCount = (changeTree.root?.refCount?.[refId] > 1)
3319
+ ? ` [×${changeTree.root.refCount[refId]}]`
3320
+ : '';
3321
+ let output = `${getIndent(level)}${ref.constructor.name} (refId: ${refId})${refCount}${contents}\n`;
3306
3322
  changeTree.forEachChild((childChangeTree) => output += this.debugRefIds(childChangeTree.ref, showContents, level + 1));
3307
3323
  return output;
3308
3324
  }
@@ -3487,6 +3503,10 @@ class CollectionSchema {
3487
3503
  // discard previous operations.
3488
3504
  changeTree.discard(true);
3489
3505
  changeTree.indexes = {};
3506
+ // remove children references
3507
+ changeTree.forEachChild((childChangeTree, _) => {
3508
+ changeTree.root?.remove(childChangeTree);
3509
+ });
3490
3510
  // clear previous indexes
3491
3511
  this.$indexes.clear();
3492
3512
  // clear items
@@ -3819,6 +3839,7 @@ class Root {
3819
3839
  this.removeChangeFromChangeSet("filteredChanges", changeTree);
3820
3840
  }
3821
3841
  this.refCount[changeTree.refId] = 0;
3842
+ changeTree.forEachChild((child, _) => this.remove(child));
3822
3843
  }
3823
3844
  else {
3824
3845
  this.refCount[changeTree.refId] = refCount;
@@ -3840,7 +3861,6 @@ class Root {
3840
3861
  enqueueChangeTree(this, changeTree, "changes");
3841
3862
  }
3842
3863
  }
3843
- changeTree.forEachChild((child, _) => this.remove(child));
3844
3864
  return refCount;
3845
3865
  }
3846
3866
  removeChangeFromChangeSet(changeSetName, changeTree) {
@@ -4214,7 +4234,7 @@ class ReferenceTracker {
4214
4234
  }
4215
4235
  removeCallback(refId, field, callback) {
4216
4236
  const index = this.callbacks?.[refId]?.[field]?.indexOf(callback);
4217
- if (index !== -1) {
4237
+ if (index !== undefined && index !== -1) {
4218
4238
  spliceOne(this.callbacks[refId][field], index);
4219
4239
  }
4220
4240
  }