@colyseus/schema 3.0.10 → 3.0.12

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.
@@ -857,14 +857,13 @@ const Metadata = {
857
857
  fieldIndex++;
858
858
  for (const field in fields) {
859
859
  const type = fields[field];
860
- const normalizedType = getNormalizedType(type);
861
860
  // FIXME: this code is duplicated from @type() annotation
862
861
  const complexTypeKlass = (Array.isArray(type))
863
862
  ? getType("array")
864
863
  : (typeof (Object.keys(type)[0]) === "string") && getType(Object.keys(type)[0]);
865
864
  const childType = (complexTypeKlass)
866
865
  ? Object.values(type)[0]
867
- : normalizedType;
866
+ : getNormalizedType(type);
868
867
  Metadata.addField(metadata, fieldIndex, field, type, getPropertyDescriptor(`_${field}`, fieldIndex, childType, complexTypeKlass));
869
868
  fieldIndex++;
870
869
  }
@@ -1236,6 +1235,7 @@ class ChangeTree {
1236
1235
  //
1237
1236
  this.root?.remove(previousValue[$changes]);
1238
1237
  }
1238
+ deleteOperationAtIndex(this.allChanges, allChangesIndex);
1239
1239
  //
1240
1240
  // FIXME: this is looking a ugly and repeated
1241
1241
  //
@@ -1244,7 +1244,6 @@ class ChangeTree {
1244
1244
  enqueueChangeTree(this.root, this, 'filteredChanges');
1245
1245
  }
1246
1246
  else {
1247
- deleteOperationAtIndex(this.allChanges, allChangesIndex);
1248
1247
  enqueueChangeTree(this.root, this, 'changes');
1249
1248
  }
1250
1249
  }
@@ -1531,19 +1530,6 @@ function decodeValue(decoder, operation, ref, index, type, bytes, it, allChanges
1531
1530
  //
1532
1531
  if (operation !== OPERATION.DELETE_AND_ADD) {
1533
1532
  ref[$deleteByIndex](index);
1534
- // //
1535
- // // FIXME: is this in the correct place?
1536
- // // (This is sounding like a workaround just for ArraySchema, see
1537
- // // "should splice and move" test on ArraySchema.test.ts)
1538
- // //
1539
- // allChanges.push({
1540
- // ref,
1541
- // refId: decoder.currentRefId,
1542
- // op: OPERATION.DELETE,
1543
- // field: index as unknown as string,
1544
- // value: undefined,
1545
- // previousValue,
1546
- // });
1547
1533
  }
1548
1534
  value = undefined;
1549
1535
  }
@@ -1891,7 +1877,8 @@ class ArraySchema {
1891
1877
  else {
1892
1878
  if (setValue[$changes]) {
1893
1879
  assertInstanceType(setValue, obj[$childType], obj, key);
1894
- if (obj.items[key] !== undefined) {
1880
+ const previousValue = obj.items[key];
1881
+ if (previousValue !== undefined) {
1895
1882
  if (setValue[$changes].isNew) {
1896
1883
  this[$changes].indexedOperation(Number(key), OPERATION.MOVE_AND_ADD);
1897
1884
  }
@@ -1903,10 +1890,13 @@ class ArraySchema {
1903
1890
  this[$changes].indexedOperation(Number(key), OPERATION.MOVE);
1904
1891
  }
1905
1892
  }
1893
+ // remove root reference from previous value
1894
+ previousValue[$changes].root?.remove(previousValue[$changes]);
1906
1895
  }
1907
1896
  else if (setValue[$changes].isNew) {
1908
1897
  this[$changes].indexedOperation(Number(key), OPERATION.ADD);
1909
1898
  }
1899
+ setValue[$changes].setParent(this, obj[$changes].root, key);
1910
1900
  }
1911
1901
  else {
1912
1902
  obj.$changeAt(Number(key), setValue);
@@ -2544,33 +2534,34 @@ class MapSchema {
2544
2534
  // See: https://github.com/colyseus/colyseus/issues/561#issuecomment-1646733468
2545
2535
  key = key.toString();
2546
2536
  const changeTree = this[$changes];
2547
- // get "index" for this value.
2548
- const isReplace = typeof (changeTree.indexes[key]) !== "undefined";
2549
- const index = (isReplace)
2550
- ? changeTree.indexes[key]
2551
- : changeTree.indexes[$numFields] ?? 0;
2552
- let operation = (isReplace)
2553
- ? OPERATION.REPLACE
2554
- : OPERATION.ADD;
2555
2537
  const isRef = (value[$changes]) !== undefined;
2556
- //
2557
- // (encoding)
2558
- // set a unique id to relate directly with this key/value.
2559
- //
2560
- if (!isReplace) {
2538
+ let index;
2539
+ let operation;
2540
+ // IS REPLACE?
2541
+ if (typeof (changeTree.indexes[key]) !== "undefined") {
2542
+ index = changeTree.indexes[key];
2543
+ operation = OPERATION.REPLACE;
2544
+ const previousValue = this.$items.get(key);
2545
+ if (previousValue === value) {
2546
+ // if value is the same, avoid re-encoding it.
2547
+ return;
2548
+ }
2549
+ else if (isRef) {
2550
+ // if is schema, force ADD operation if value differ from previous one.
2551
+ operation = OPERATION.DELETE_AND_ADD;
2552
+ // remove reference from previous value
2553
+ if (previousValue !== undefined) {
2554
+ previousValue[$changes].root?.remove(previousValue[$changes]);
2555
+ }
2556
+ }
2557
+ }
2558
+ else {
2559
+ index = changeTree.indexes[$numFields] ?? 0;
2560
+ operation = OPERATION.ADD;
2561
2561
  this.$indexes.set(index, key);
2562
2562
  changeTree.indexes[key] = index;
2563
2563
  changeTree.indexes[$numFields] = index + 1;
2564
2564
  }
2565
- else if (!isRef &&
2566
- this.$items.get(key) === value) {
2567
- // if value is the same, avoid re-encoding it.
2568
- return;
2569
- }
2570
- else if (isRef && // if is schema, force ADD operation if value differ from previous one.
2571
- this.$items.get(key) !== value) {
2572
- operation = OPERATION.ADD;
2573
- }
2574
2565
  this.$items.set(key, value);
2575
2566
  changeTree.change(index, operation);
2576
2567
  //
@@ -2942,13 +2933,14 @@ function getPropertyDescriptor(fieldCached, fieldIndex, type, complexTypeKlass)
2942
2933
  const changeTree = this[$changes];
2943
2934
  //
2944
2935
  // Replacing existing "ref", remove it from root.
2945
- // TODO: if there are other references to this instance, we should not remove it from root.
2946
2936
  //
2947
2937
  if (previousValue !== undefined && previousValue[$changes]) {
2948
2938
  changeTree.root?.remove(previousValue[$changes]);
2939
+ this.constructor[$track](changeTree, fieldIndex, OPERATION.DELETE_AND_ADD);
2940
+ }
2941
+ else {
2942
+ this.constructor[$track](changeTree, fieldIndex, OPERATION.ADD);
2949
2943
  }
2950
- // flag the change for encoding.
2951
- this.constructor[$track](changeTree, fieldIndex, OPERATION.ADD);
2952
2944
  //
2953
2945
  // call setParent() recursively for this and its child
2954
2946
  // structures.
@@ -3745,16 +3737,33 @@ class Root {
3745
3737
  }
3746
3738
  else {
3747
3739
  this.refCount[changeTree.refId] = refCount;
3740
+ //
3741
+ // When losing a reference to an instance, it is best to move the
3742
+ // ChangeTree to the end of the encoding queue.
3743
+ //
3744
+ // This way, at decoding time, the instance that contains the
3745
+ // ChangeTree will be available before the ChangeTree itself. If the
3746
+ // containing instance is not available, the Decoder will throw
3747
+ // "refId not found" error.
3748
+ //
3749
+ if (changeTree.filteredChanges !== undefined) {
3750
+ this.removeChangeFromChangeSet("filteredChanges", changeTree);
3751
+ enqueueChangeTree(this, changeTree, "filteredChanges");
3752
+ }
3753
+ else {
3754
+ this.removeChangeFromChangeSet("changes", changeTree);
3755
+ enqueueChangeTree(this, changeTree, "changes");
3756
+ }
3748
3757
  }
3749
3758
  changeTree.forEachChild((child, _) => this.remove(child));
3750
3759
  return refCount;
3751
3760
  }
3752
3761
  removeChangeFromChangeSet(changeSetName, changeTree) {
3753
3762
  const changeSet = this[changeSetName];
3754
- const index = changeSet.indexOf(changeTree);
3755
- if (index !== -1) {
3756
- spliceOne(changeSet, index);
3763
+ if (spliceOne(changeSet, changeSet.indexOf(changeTree))) {
3764
+ changeTree[changeSetName].queueRootIndex = -1;
3757
3765
  // changeSet[index] = undefined;
3766
+ return true;
3758
3767
  }
3759
3768
  }
3760
3769
  clear() {
@@ -3790,12 +3799,6 @@ class Encoder {
3790
3799
  const changeTrees = this.root[changeSetName];
3791
3800
  for (let i = 0, numChangeTrees = changeTrees.length; i < numChangeTrees; i++) {
3792
3801
  const changeTree = changeTrees[i];
3793
- const operations = changeTree[changeSetName];
3794
- const ref = changeTree.ref;
3795
- const ctor = ref.constructor;
3796
- const encoder = ctor[$encoder];
3797
- const filter = ctor[$filter];
3798
- const metadata = ctor[Symbol.metadata];
3799
3802
  if (hasView) {
3800
3803
  if (!view.items.has(changeTree)) {
3801
3804
  view.invisible.add(changeTree);
@@ -3805,13 +3808,24 @@ class Encoder {
3805
3808
  view.invisible.delete(changeTree); // remove from invisible list
3806
3809
  }
3807
3810
  }
3811
+ const operations = changeTree[changeSetName];
3812
+ const ref = changeTree.ref;
3813
+ // TODO: avoid iterating over change tree if no changes were made
3814
+ const numChanges = operations.operations.length;
3815
+ if (numChanges === 0) {
3816
+ continue;
3817
+ }
3818
+ const ctor = ref.constructor;
3819
+ const encoder = ctor[$encoder];
3820
+ const filter = ctor[$filter];
3821
+ const metadata = ctor[Symbol.metadata];
3808
3822
  // skip root `refId` if it's the first change tree
3809
3823
  // (unless it "hasView", which will need to revisit the root)
3810
3824
  if (hasView || it.offset > initialOffset || changeTree !== rootChangeTree) {
3811
3825
  buffer[it.offset++] = SWITCH_TO_STRUCTURE & 255;
3812
3826
  encode.number(buffer, changeTree.refId, it);
3813
3827
  }
3814
- for (let j = 0, numChanges = operations.operations.length; j < numChanges; j++) {
3828
+ for (let j = 0; j < numChanges; j++) {
3815
3829
  const fieldIndex = operations.operations[j];
3816
3830
  const operation = (fieldIndex < 0)
3817
3831
  ? Math.abs(fieldIndex) // "pure" operation without fieldIndex (e.g. CLEAR, REVERSE, etc.)
@@ -3908,14 +3922,16 @@ class Encoder {
3908
3922
  encodeView(view, sharedOffset, it, bytes = this.sharedBuffer) {
3909
3923
  const viewOffset = it.offset;
3910
3924
  // encode visibility changes (add/remove for this view)
3911
- const refIds = Object.keys(view.changes);
3912
- for (let i = 0, numRefIds = refIds.length; i < numRefIds; i++) {
3913
- const refId = refIds[i];
3914
- const changes = view.changes[refId];
3925
+ for (const [refId, changes] of view.changes) {
3915
3926
  const changeTree = this.root.changeTrees[refId];
3916
- if (changeTree === undefined ||
3917
- Object.keys(changes).length === 0 // FIXME: avoid having empty changes if no changes were made
3918
- ) {
3927
+ if (changeTree === undefined) {
3928
+ // detached instance, remove from view and skip.
3929
+ view.changes.delete(refId);
3930
+ continue;
3931
+ }
3932
+ const keys = Object.keys(changes);
3933
+ if (keys.length === 0) {
3934
+ // FIXME: avoid having empty changes if no changes were made
3919
3935
  // console.log("changes.size === 0, skip", changeTree.ref.constructor.name);
3920
3936
  continue;
3921
3937
  }
@@ -3925,7 +3941,6 @@ class Encoder {
3925
3941
  const metadata = ctor[Symbol.metadata];
3926
3942
  bytes[it.offset++] = SWITCH_TO_STRUCTURE & 255;
3927
3943
  encode.number(bytes, changeTree.refId, it);
3928
- const keys = Object.keys(changes);
3929
3944
  for (let i = 0, numChanges = keys.length; i < numChanges; i++) {
3930
3945
  const key = keys[i];
3931
3946
  const operation = changes[key];
@@ -3939,7 +3954,7 @@ class Encoder {
3939
3954
  // (to allow re-using StateView's for multiple clients)
3940
3955
  //
3941
3956
  // clear "view" changes after encoding
3942
- view.changes = {};
3957
+ view.changes.clear();
3943
3958
  // try to encode "filtered" changes
3944
3959
  this.encode(it, view, bytes, "filteredChanges", false, viewOffset);
3945
3960
  return Buffer.concat([
@@ -3947,20 +3962,6 @@ class Encoder {
3947
3962
  bytes.subarray(viewOffset, it.offset)
3948
3963
  ]);
3949
3964
  }
3950
- onEndEncode(changeTrees = this.root.changes) {
3951
- // changeTrees.forEach(function(changeTree) {
3952
- // changeTree.endEncode();
3953
- // });
3954
- // for (const refId in changeTrees) {
3955
- // const changeTree = this.root.changeTrees[refId];
3956
- // changeTree.endEncode();
3957
- // // changeTree.changes.clear();
3958
- // // // ArraySchema and MapSchema have a custom "encode end" method
3959
- // // changeTree.ref[$onEncodeEnd]?.();
3960
- // // // Not a new instance anymore
3961
- // // delete changeTree[$isNew];
3962
- // }
3963
- }
3964
3965
  discardChanges() {
3965
3966
  // discard shared changes
3966
3967
  let length = this.root.changes.length;
@@ -4692,7 +4693,8 @@ class StateView {
4692
4693
  * Manual "ADD" operations for changes per ChangeTree, specific to this view.
4693
4694
  * (This is used to force encoding a property, even if it was not changed)
4694
4695
  */
4695
- this.changes = {};
4696
+ // TODO: use map here!? may fix encode ordering issue
4697
+ this.changes = new Map();
4696
4698
  }
4697
4699
  // TODO: allow to set multiple tags at once
4698
4700
  add(obj, tag = DEFAULT_VIEW_TAG, checkIncludeParent = true) {
@@ -4708,16 +4710,16 @@ class StateView {
4708
4710
  // - if it was invisible to this view
4709
4711
  // - if it were previously filtered out
4710
4712
  if (checkIncludeParent && changeTree.parent) {
4711
- this.addParent(changeTree.parent[$changes], changeTree.parentIndex, tag);
4713
+ this.addParentOf(changeTree, tag);
4712
4714
  }
4713
4715
  //
4714
4716
  // TODO: when adding an item of a MapSchema, the changes may not
4715
4717
  // be set (only the parent's changes are set)
4716
4718
  //
4717
- let changes = this.changes[changeTree.refId];
4719
+ let changes = this.changes.get(changeTree.refId);
4718
4720
  if (changes === undefined) {
4719
4721
  changes = {};
4720
- this.changes[changeTree.refId] = changes;
4722
+ this.changes.set(changeTree.refId, changes);
4721
4723
  }
4722
4724
  // set tag
4723
4725
  if (tag !== DEFAULT_VIEW_TAG) {
@@ -4773,24 +4775,29 @@ class StateView {
4773
4775
  });
4774
4776
  return this;
4775
4777
  }
4776
- addParent(changeTree, parentIndex, tag) {
4778
+ addParentOf(childChangeTree, tag) {
4779
+ const changeTree = childChangeTree.parent[$changes];
4780
+ const parentIndex = childChangeTree.parentIndex;
4777
4781
  // view must have all "changeTree" parent tree
4778
4782
  this.items.add(changeTree);
4779
4783
  // add parent's parent
4780
4784
  const parentChangeTree = changeTree.parent?.[$changes];
4781
4785
  if (parentChangeTree && (parentChangeTree.filteredChanges !== undefined)) {
4782
- this.addParent(parentChangeTree, changeTree.parentIndex, tag);
4786
+ this.addParentOf(changeTree, tag);
4783
4787
  }
4788
+ if (
4784
4789
  // parent is already available, no need to add it!
4785
- if (!this.invisible.has(changeTree)) {
4790
+ !this.invisible.has(changeTree) &&
4791
+ // item is being replaced, no need to add parent
4792
+ changeTree.indexedOperations[parentIndex] !== OPERATION.DELETE_AND_ADD) {
4786
4793
  return;
4787
4794
  }
4788
4795
  // add parent's tag properties
4789
4796
  if (changeTree.getChange(parentIndex) !== OPERATION.DELETE) {
4790
- let changes = this.changes[changeTree.refId];
4797
+ let changes = this.changes.get(changeTree.refId);
4791
4798
  if (changes === undefined) {
4792
4799
  changes = {};
4793
- this.changes[changeTree.refId] = changes;
4800
+ this.changes.set(changeTree.refId, changes);
4794
4801
  }
4795
4802
  if (!this.tags) {
4796
4803
  this.tags = new WeakMap();
@@ -4816,20 +4823,20 @@ class StateView {
4816
4823
  this.items.delete(changeTree);
4817
4824
  const ref = changeTree.ref;
4818
4825
  const metadata = ref.constructor[Symbol.metadata];
4819
- let changes = this.changes[changeTree.refId];
4826
+ let changes = this.changes.get(changeTree.refId);
4820
4827
  if (changes === undefined) {
4821
4828
  changes = {};
4822
- this.changes[changeTree.refId] = changes;
4829
+ this.changes.set(changeTree.refId, changes);
4823
4830
  }
4824
4831
  if (tag === DEFAULT_VIEW_TAG) {
4825
4832
  // parent is collection (Map/Array)
4826
4833
  const parent = changeTree.parent;
4827
4834
  if (!Metadata.isValidInstance(parent)) {
4828
4835
  const parentChangeTree = parent[$changes];
4829
- let changes = this.changes[parentChangeTree.refId];
4836
+ let changes = this.changes.get(parentChangeTree.refId);
4830
4837
  if (changes === undefined) {
4831
4838
  changes = {};
4832
- this.changes[parentChangeTree.refId] = changes;
4839
+ this.changes.set(parentChangeTree.refId, changes);
4833
4840
  }
4834
4841
  // DELETE / DELETE BY REF ID
4835
4842
  changes[changeTree.parentIndex] = OPERATION.DELETE;