@colyseus/schema 3.0.11 → 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
  }
@@ -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.
@@ -3771,6 +3763,7 @@ class Root {
3771
3763
  if (spliceOne(changeSet, changeSet.indexOf(changeTree))) {
3772
3764
  changeTree[changeSetName].queueRootIndex = -1;
3773
3765
  // changeSet[index] = undefined;
3766
+ return true;
3774
3767
  }
3775
3768
  }
3776
3769
  clear() {
@@ -3806,12 +3799,6 @@ class Encoder {
3806
3799
  const changeTrees = this.root[changeSetName];
3807
3800
  for (let i = 0, numChangeTrees = changeTrees.length; i < numChangeTrees; i++) {
3808
3801
  const changeTree = changeTrees[i];
3809
- const operations = changeTree[changeSetName];
3810
- const ref = changeTree.ref;
3811
- const ctor = ref.constructor;
3812
- const encoder = ctor[$encoder];
3813
- const filter = ctor[$filter];
3814
- const metadata = ctor[Symbol.metadata];
3815
3802
  if (hasView) {
3816
3803
  if (!view.items.has(changeTree)) {
3817
3804
  view.invisible.add(changeTree);
@@ -3821,13 +3808,24 @@ class Encoder {
3821
3808
  view.invisible.delete(changeTree); // remove from invisible list
3822
3809
  }
3823
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];
3824
3822
  // skip root `refId` if it's the first change tree
3825
3823
  // (unless it "hasView", which will need to revisit the root)
3826
3824
  if (hasView || it.offset > initialOffset || changeTree !== rootChangeTree) {
3827
3825
  buffer[it.offset++] = SWITCH_TO_STRUCTURE & 255;
3828
3826
  encode.number(buffer, changeTree.refId, it);
3829
3827
  }
3830
- for (let j = 0, numChanges = operations.operations.length; j < numChanges; j++) {
3828
+ for (let j = 0; j < numChanges; j++) {
3831
3829
  const fieldIndex = operations.operations[j];
3832
3830
  const operation = (fieldIndex < 0)
3833
3831
  ? Math.abs(fieldIndex) // "pure" operation without fieldIndex (e.g. CLEAR, REVERSE, etc.)
@@ -3924,14 +3922,16 @@ class Encoder {
3924
3922
  encodeView(view, sharedOffset, it, bytes = this.sharedBuffer) {
3925
3923
  const viewOffset = it.offset;
3926
3924
  // encode visibility changes (add/remove for this view)
3927
- const refIds = Object.keys(view.changes);
3928
- for (let i = 0, numRefIds = refIds.length; i < numRefIds; i++) {
3929
- const refId = refIds[i];
3930
- const changes = view.changes[refId];
3925
+ for (const [refId, changes] of view.changes) {
3931
3926
  const changeTree = this.root.changeTrees[refId];
3932
- if (changeTree === undefined ||
3933
- Object.keys(changes).length === 0 // FIXME: avoid having empty changes if no changes were made
3934
- ) {
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
3935
3935
  // console.log("changes.size === 0, skip", changeTree.ref.constructor.name);
3936
3936
  continue;
3937
3937
  }
@@ -3941,7 +3941,6 @@ class Encoder {
3941
3941
  const metadata = ctor[Symbol.metadata];
3942
3942
  bytes[it.offset++] = SWITCH_TO_STRUCTURE & 255;
3943
3943
  encode.number(bytes, changeTree.refId, it);
3944
- const keys = Object.keys(changes);
3945
3944
  for (let i = 0, numChanges = keys.length; i < numChanges; i++) {
3946
3945
  const key = keys[i];
3947
3946
  const operation = changes[key];
@@ -3955,7 +3954,7 @@ class Encoder {
3955
3954
  // (to allow re-using StateView's for multiple clients)
3956
3955
  //
3957
3956
  // clear "view" changes after encoding
3958
- view.changes = {};
3957
+ view.changes.clear();
3959
3958
  // try to encode "filtered" changes
3960
3959
  this.encode(it, view, bytes, "filteredChanges", false, viewOffset);
3961
3960
  return Buffer.concat([
@@ -3963,20 +3962,6 @@ class Encoder {
3963
3962
  bytes.subarray(viewOffset, it.offset)
3964
3963
  ]);
3965
3964
  }
3966
- onEndEncode(changeTrees = this.root.changes) {
3967
- // changeTrees.forEach(function(changeTree) {
3968
- // changeTree.endEncode();
3969
- // });
3970
- // for (const refId in changeTrees) {
3971
- // const changeTree = this.root.changeTrees[refId];
3972
- // changeTree.endEncode();
3973
- // // changeTree.changes.clear();
3974
- // // // ArraySchema and MapSchema have a custom "encode end" method
3975
- // // changeTree.ref[$onEncodeEnd]?.();
3976
- // // // Not a new instance anymore
3977
- // // delete changeTree[$isNew];
3978
- // }
3979
- }
3980
3965
  discardChanges() {
3981
3966
  // discard shared changes
3982
3967
  let length = this.root.changes.length;
@@ -4708,7 +4693,8 @@ class StateView {
4708
4693
  * Manual "ADD" operations for changes per ChangeTree, specific to this view.
4709
4694
  * (This is used to force encoding a property, even if it was not changed)
4710
4695
  */
4711
- this.changes = {};
4696
+ // TODO: use map here!? may fix encode ordering issue
4697
+ this.changes = new Map();
4712
4698
  }
4713
4699
  // TODO: allow to set multiple tags at once
4714
4700
  add(obj, tag = DEFAULT_VIEW_TAG, checkIncludeParent = true) {
@@ -4724,16 +4710,16 @@ class StateView {
4724
4710
  // - if it was invisible to this view
4725
4711
  // - if it were previously filtered out
4726
4712
  if (checkIncludeParent && changeTree.parent) {
4727
- this.addParent(changeTree.parent[$changes], changeTree.parentIndex, tag);
4713
+ this.addParentOf(changeTree, tag);
4728
4714
  }
4729
4715
  //
4730
4716
  // TODO: when adding an item of a MapSchema, the changes may not
4731
4717
  // be set (only the parent's changes are set)
4732
4718
  //
4733
- let changes = this.changes[changeTree.refId];
4719
+ let changes = this.changes.get(changeTree.refId);
4734
4720
  if (changes === undefined) {
4735
4721
  changes = {};
4736
- this.changes[changeTree.refId] = changes;
4722
+ this.changes.set(changeTree.refId, changes);
4737
4723
  }
4738
4724
  // set tag
4739
4725
  if (tag !== DEFAULT_VIEW_TAG) {
@@ -4789,24 +4775,29 @@ class StateView {
4789
4775
  });
4790
4776
  return this;
4791
4777
  }
4792
- addParent(changeTree, parentIndex, tag) {
4778
+ addParentOf(childChangeTree, tag) {
4779
+ const changeTree = childChangeTree.parent[$changes];
4780
+ const parentIndex = childChangeTree.parentIndex;
4793
4781
  // view must have all "changeTree" parent tree
4794
4782
  this.items.add(changeTree);
4795
4783
  // add parent's parent
4796
4784
  const parentChangeTree = changeTree.parent?.[$changes];
4797
4785
  if (parentChangeTree && (parentChangeTree.filteredChanges !== undefined)) {
4798
- this.addParent(parentChangeTree, changeTree.parentIndex, tag);
4786
+ this.addParentOf(changeTree, tag);
4799
4787
  }
4788
+ if (
4800
4789
  // parent is already available, no need to add it!
4801
- 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) {
4802
4793
  return;
4803
4794
  }
4804
4795
  // add parent's tag properties
4805
4796
  if (changeTree.getChange(parentIndex) !== OPERATION.DELETE) {
4806
- let changes = this.changes[changeTree.refId];
4797
+ let changes = this.changes.get(changeTree.refId);
4807
4798
  if (changes === undefined) {
4808
4799
  changes = {};
4809
- this.changes[changeTree.refId] = changes;
4800
+ this.changes.set(changeTree.refId, changes);
4810
4801
  }
4811
4802
  if (!this.tags) {
4812
4803
  this.tags = new WeakMap();
@@ -4832,20 +4823,20 @@ class StateView {
4832
4823
  this.items.delete(changeTree);
4833
4824
  const ref = changeTree.ref;
4834
4825
  const metadata = ref.constructor[Symbol.metadata];
4835
- let changes = this.changes[changeTree.refId];
4826
+ let changes = this.changes.get(changeTree.refId);
4836
4827
  if (changes === undefined) {
4837
4828
  changes = {};
4838
- this.changes[changeTree.refId] = changes;
4829
+ this.changes.set(changeTree.refId, changes);
4839
4830
  }
4840
4831
  if (tag === DEFAULT_VIEW_TAG) {
4841
4832
  // parent is collection (Map/Array)
4842
4833
  const parent = changeTree.parent;
4843
4834
  if (!Metadata.isValidInstance(parent)) {
4844
4835
  const parentChangeTree = parent[$changes];
4845
- let changes = this.changes[parentChangeTree.refId];
4836
+ let changes = this.changes.get(parentChangeTree.refId);
4846
4837
  if (changes === undefined) {
4847
4838
  changes = {};
4848
- this.changes[parentChangeTree.refId] = changes;
4839
+ this.changes.set(parentChangeTree.refId, changes);
4849
4840
  }
4850
4841
  // DELETE / DELETE BY REF ID
4851
4842
  changes[changeTree.parentIndex] = OPERATION.DELETE;