@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.
@@ -859,14 +859,13 @@ const Metadata = {
859
859
  fieldIndex++;
860
860
  for (const field in fields) {
861
861
  const type = fields[field];
862
- const normalizedType = getNormalizedType(type);
863
862
  // FIXME: this code is duplicated from @type() annotation
864
863
  const complexTypeKlass = (Array.isArray(type))
865
864
  ? getType("array")
866
865
  : (typeof (Object.keys(type)[0]) === "string") && getType(Object.keys(type)[0]);
867
866
  const childType = (complexTypeKlass)
868
867
  ? Object.values(type)[0]
869
- : normalizedType;
868
+ : getNormalizedType(type);
870
869
  Metadata.addField(metadata, fieldIndex, field, type, getPropertyDescriptor(`_${field}`, fieldIndex, childType, complexTypeKlass));
871
870
  fieldIndex++;
872
871
  }
@@ -1533,19 +1532,6 @@ function decodeValue(decoder, operation, ref, index, type, bytes, it, allChanges
1533
1532
  //
1534
1533
  if (operation !== exports.OPERATION.DELETE_AND_ADD) {
1535
1534
  ref[$deleteByIndex](index);
1536
- // //
1537
- // // FIXME: is this in the correct place?
1538
- // // (This is sounding like a workaround just for ArraySchema, see
1539
- // // "should splice and move" test on ArraySchema.test.ts)
1540
- // //
1541
- // allChanges.push({
1542
- // ref,
1543
- // refId: decoder.currentRefId,
1544
- // op: OPERATION.DELETE,
1545
- // field: index as unknown as string,
1546
- // value: undefined,
1547
- // previousValue,
1548
- // });
1549
1535
  }
1550
1536
  value = undefined;
1551
1537
  }
@@ -1893,7 +1879,8 @@ class ArraySchema {
1893
1879
  else {
1894
1880
  if (setValue[$changes]) {
1895
1881
  assertInstanceType(setValue, obj[$childType], obj, key);
1896
- if (obj.items[key] !== undefined) {
1882
+ const previousValue = obj.items[key];
1883
+ if (previousValue !== undefined) {
1897
1884
  if (setValue[$changes].isNew) {
1898
1885
  this[$changes].indexedOperation(Number(key), exports.OPERATION.MOVE_AND_ADD);
1899
1886
  }
@@ -1905,10 +1892,13 @@ class ArraySchema {
1905
1892
  this[$changes].indexedOperation(Number(key), exports.OPERATION.MOVE);
1906
1893
  }
1907
1894
  }
1895
+ // remove root reference from previous value
1896
+ previousValue[$changes].root?.remove(previousValue[$changes]);
1908
1897
  }
1909
1898
  else if (setValue[$changes].isNew) {
1910
1899
  this[$changes].indexedOperation(Number(key), exports.OPERATION.ADD);
1911
1900
  }
1901
+ setValue[$changes].setParent(this, obj[$changes].root, key);
1912
1902
  }
1913
1903
  else {
1914
1904
  obj.$changeAt(Number(key), setValue);
@@ -2546,33 +2536,34 @@ class MapSchema {
2546
2536
  // See: https://github.com/colyseus/colyseus/issues/561#issuecomment-1646733468
2547
2537
  key = key.toString();
2548
2538
  const changeTree = this[$changes];
2549
- // get "index" for this value.
2550
- const isReplace = typeof (changeTree.indexes[key]) !== "undefined";
2551
- const index = (isReplace)
2552
- ? changeTree.indexes[key]
2553
- : changeTree.indexes[$numFields] ?? 0;
2554
- let operation = (isReplace)
2555
- ? exports.OPERATION.REPLACE
2556
- : exports.OPERATION.ADD;
2557
2539
  const isRef = (value[$changes]) !== undefined;
2558
- //
2559
- // (encoding)
2560
- // set a unique id to relate directly with this key/value.
2561
- //
2562
- if (!isReplace) {
2540
+ let index;
2541
+ let operation;
2542
+ // IS REPLACE?
2543
+ if (typeof (changeTree.indexes[key]) !== "undefined") {
2544
+ index = changeTree.indexes[key];
2545
+ operation = exports.OPERATION.REPLACE;
2546
+ const previousValue = this.$items.get(key);
2547
+ if (previousValue === value) {
2548
+ // if value is the same, avoid re-encoding it.
2549
+ return;
2550
+ }
2551
+ else if (isRef) {
2552
+ // if is schema, force ADD operation if value differ from previous one.
2553
+ operation = exports.OPERATION.DELETE_AND_ADD;
2554
+ // remove reference from previous value
2555
+ if (previousValue !== undefined) {
2556
+ previousValue[$changes].root?.remove(previousValue[$changes]);
2557
+ }
2558
+ }
2559
+ }
2560
+ else {
2561
+ index = changeTree.indexes[$numFields] ?? 0;
2562
+ operation = exports.OPERATION.ADD;
2563
2563
  this.$indexes.set(index, key);
2564
2564
  changeTree.indexes[key] = index;
2565
2565
  changeTree.indexes[$numFields] = index + 1;
2566
2566
  }
2567
- else if (!isRef &&
2568
- this.$items.get(key) === value) {
2569
- // if value is the same, avoid re-encoding it.
2570
- return;
2571
- }
2572
- else if (isRef && // if is schema, force ADD operation if value differ from previous one.
2573
- this.$items.get(key) !== value) {
2574
- operation = exports.OPERATION.ADD;
2575
- }
2576
2567
  this.$items.set(key, value);
2577
2568
  changeTree.change(index, operation);
2578
2569
  //
@@ -2944,13 +2935,14 @@ function getPropertyDescriptor(fieldCached, fieldIndex, type, complexTypeKlass)
2944
2935
  const changeTree = this[$changes];
2945
2936
  //
2946
2937
  // Replacing existing "ref", remove it from root.
2947
- // TODO: if there are other references to this instance, we should not remove it from root.
2948
2938
  //
2949
2939
  if (previousValue !== undefined && previousValue[$changes]) {
2950
2940
  changeTree.root?.remove(previousValue[$changes]);
2941
+ this.constructor[$track](changeTree, fieldIndex, exports.OPERATION.DELETE_AND_ADD);
2942
+ }
2943
+ else {
2944
+ this.constructor[$track](changeTree, fieldIndex, exports.OPERATION.ADD);
2951
2945
  }
2952
- // flag the change for encoding.
2953
- this.constructor[$track](changeTree, fieldIndex, exports.OPERATION.ADD);
2954
2946
  //
2955
2947
  // call setParent() recursively for this and its child
2956
2948
  // structures.
@@ -3773,6 +3765,7 @@ class Root {
3773
3765
  if (spliceOne(changeSet, changeSet.indexOf(changeTree))) {
3774
3766
  changeTree[changeSetName].queueRootIndex = -1;
3775
3767
  // changeSet[index] = undefined;
3768
+ return true;
3776
3769
  }
3777
3770
  }
3778
3771
  clear() {
@@ -3808,12 +3801,6 @@ class Encoder {
3808
3801
  const changeTrees = this.root[changeSetName];
3809
3802
  for (let i = 0, numChangeTrees = changeTrees.length; i < numChangeTrees; i++) {
3810
3803
  const changeTree = changeTrees[i];
3811
- const operations = changeTree[changeSetName];
3812
- const ref = changeTree.ref;
3813
- const ctor = ref.constructor;
3814
- const encoder = ctor[$encoder];
3815
- const filter = ctor[$filter];
3816
- const metadata = ctor[Symbol.metadata];
3817
3804
  if (hasView) {
3818
3805
  if (!view.items.has(changeTree)) {
3819
3806
  view.invisible.add(changeTree);
@@ -3823,13 +3810,24 @@ class Encoder {
3823
3810
  view.invisible.delete(changeTree); // remove from invisible list
3824
3811
  }
3825
3812
  }
3813
+ const operations = changeTree[changeSetName];
3814
+ const ref = changeTree.ref;
3815
+ // TODO: avoid iterating over change tree if no changes were made
3816
+ const numChanges = operations.operations.length;
3817
+ if (numChanges === 0) {
3818
+ continue;
3819
+ }
3820
+ const ctor = ref.constructor;
3821
+ const encoder = ctor[$encoder];
3822
+ const filter = ctor[$filter];
3823
+ const metadata = ctor[Symbol.metadata];
3826
3824
  // skip root `refId` if it's the first change tree
3827
3825
  // (unless it "hasView", which will need to revisit the root)
3828
3826
  if (hasView || it.offset > initialOffset || changeTree !== rootChangeTree) {
3829
3827
  buffer[it.offset++] = SWITCH_TO_STRUCTURE & 255;
3830
3828
  encode.number(buffer, changeTree.refId, it);
3831
3829
  }
3832
- for (let j = 0, numChanges = operations.operations.length; j < numChanges; j++) {
3830
+ for (let j = 0; j < numChanges; j++) {
3833
3831
  const fieldIndex = operations.operations[j];
3834
3832
  const operation = (fieldIndex < 0)
3835
3833
  ? Math.abs(fieldIndex) // "pure" operation without fieldIndex (e.g. CLEAR, REVERSE, etc.)
@@ -3926,14 +3924,16 @@ class Encoder {
3926
3924
  encodeView(view, sharedOffset, it, bytes = this.sharedBuffer) {
3927
3925
  const viewOffset = it.offset;
3928
3926
  // encode visibility changes (add/remove for this view)
3929
- const refIds = Object.keys(view.changes);
3930
- for (let i = 0, numRefIds = refIds.length; i < numRefIds; i++) {
3931
- const refId = refIds[i];
3932
- const changes = view.changes[refId];
3927
+ for (const [refId, changes] of view.changes) {
3933
3928
  const changeTree = this.root.changeTrees[refId];
3934
- if (changeTree === undefined ||
3935
- Object.keys(changes).length === 0 // FIXME: avoid having empty changes if no changes were made
3936
- ) {
3929
+ if (changeTree === undefined) {
3930
+ // detached instance, remove from view and skip.
3931
+ view.changes.delete(refId);
3932
+ continue;
3933
+ }
3934
+ const keys = Object.keys(changes);
3935
+ if (keys.length === 0) {
3936
+ // FIXME: avoid having empty changes if no changes were made
3937
3937
  // console.log("changes.size === 0, skip", changeTree.ref.constructor.name);
3938
3938
  continue;
3939
3939
  }
@@ -3943,7 +3943,6 @@ class Encoder {
3943
3943
  const metadata = ctor[Symbol.metadata];
3944
3944
  bytes[it.offset++] = SWITCH_TO_STRUCTURE & 255;
3945
3945
  encode.number(bytes, changeTree.refId, it);
3946
- const keys = Object.keys(changes);
3947
3946
  for (let i = 0, numChanges = keys.length; i < numChanges; i++) {
3948
3947
  const key = keys[i];
3949
3948
  const operation = changes[key];
@@ -3957,7 +3956,7 @@ class Encoder {
3957
3956
  // (to allow re-using StateView's for multiple clients)
3958
3957
  //
3959
3958
  // clear "view" changes after encoding
3960
- view.changes = {};
3959
+ view.changes.clear();
3961
3960
  // try to encode "filtered" changes
3962
3961
  this.encode(it, view, bytes, "filteredChanges", false, viewOffset);
3963
3962
  return Buffer.concat([
@@ -3965,20 +3964,6 @@ class Encoder {
3965
3964
  bytes.subarray(viewOffset, it.offset)
3966
3965
  ]);
3967
3966
  }
3968
- onEndEncode(changeTrees = this.root.changes) {
3969
- // changeTrees.forEach(function(changeTree) {
3970
- // changeTree.endEncode();
3971
- // });
3972
- // for (const refId in changeTrees) {
3973
- // const changeTree = this.root.changeTrees[refId];
3974
- // changeTree.endEncode();
3975
- // // changeTree.changes.clear();
3976
- // // // ArraySchema and MapSchema have a custom "encode end" method
3977
- // // changeTree.ref[$onEncodeEnd]?.();
3978
- // // // Not a new instance anymore
3979
- // // delete changeTree[$isNew];
3980
- // }
3981
- }
3982
3967
  discardChanges() {
3983
3968
  // discard shared changes
3984
3969
  let length = this.root.changes.length;
@@ -4710,7 +4695,8 @@ class StateView {
4710
4695
  * Manual "ADD" operations for changes per ChangeTree, specific to this view.
4711
4696
  * (This is used to force encoding a property, even if it was not changed)
4712
4697
  */
4713
- this.changes = {};
4698
+ // TODO: use map here!? may fix encode ordering issue
4699
+ this.changes = new Map();
4714
4700
  }
4715
4701
  // TODO: allow to set multiple tags at once
4716
4702
  add(obj, tag = DEFAULT_VIEW_TAG, checkIncludeParent = true) {
@@ -4726,16 +4712,16 @@ class StateView {
4726
4712
  // - if it was invisible to this view
4727
4713
  // - if it were previously filtered out
4728
4714
  if (checkIncludeParent && changeTree.parent) {
4729
- this.addParent(changeTree.parent[$changes], changeTree.parentIndex, tag);
4715
+ this.addParentOf(changeTree, tag);
4730
4716
  }
4731
4717
  //
4732
4718
  // TODO: when adding an item of a MapSchema, the changes may not
4733
4719
  // be set (only the parent's changes are set)
4734
4720
  //
4735
- let changes = this.changes[changeTree.refId];
4721
+ let changes = this.changes.get(changeTree.refId);
4736
4722
  if (changes === undefined) {
4737
4723
  changes = {};
4738
- this.changes[changeTree.refId] = changes;
4724
+ this.changes.set(changeTree.refId, changes);
4739
4725
  }
4740
4726
  // set tag
4741
4727
  if (tag !== DEFAULT_VIEW_TAG) {
@@ -4791,24 +4777,29 @@ class StateView {
4791
4777
  });
4792
4778
  return this;
4793
4779
  }
4794
- addParent(changeTree, parentIndex, tag) {
4780
+ addParentOf(childChangeTree, tag) {
4781
+ const changeTree = childChangeTree.parent[$changes];
4782
+ const parentIndex = childChangeTree.parentIndex;
4795
4783
  // view must have all "changeTree" parent tree
4796
4784
  this.items.add(changeTree);
4797
4785
  // add parent's parent
4798
4786
  const parentChangeTree = changeTree.parent?.[$changes];
4799
4787
  if (parentChangeTree && (parentChangeTree.filteredChanges !== undefined)) {
4800
- this.addParent(parentChangeTree, changeTree.parentIndex, tag);
4788
+ this.addParentOf(changeTree, tag);
4801
4789
  }
4790
+ if (
4802
4791
  // parent is already available, no need to add it!
4803
- if (!this.invisible.has(changeTree)) {
4792
+ !this.invisible.has(changeTree) &&
4793
+ // item is being replaced, no need to add parent
4794
+ changeTree.indexedOperations[parentIndex] !== exports.OPERATION.DELETE_AND_ADD) {
4804
4795
  return;
4805
4796
  }
4806
4797
  // add parent's tag properties
4807
4798
  if (changeTree.getChange(parentIndex) !== exports.OPERATION.DELETE) {
4808
- let changes = this.changes[changeTree.refId];
4799
+ let changes = this.changes.get(changeTree.refId);
4809
4800
  if (changes === undefined) {
4810
4801
  changes = {};
4811
- this.changes[changeTree.refId] = changes;
4802
+ this.changes.set(changeTree.refId, changes);
4812
4803
  }
4813
4804
  if (!this.tags) {
4814
4805
  this.tags = new WeakMap();
@@ -4834,20 +4825,20 @@ class StateView {
4834
4825
  this.items.delete(changeTree);
4835
4826
  const ref = changeTree.ref;
4836
4827
  const metadata = ref.constructor[Symbol.metadata];
4837
- let changes = this.changes[changeTree.refId];
4828
+ let changes = this.changes.get(changeTree.refId);
4838
4829
  if (changes === undefined) {
4839
4830
  changes = {};
4840
- this.changes[changeTree.refId] = changes;
4831
+ this.changes.set(changeTree.refId, changes);
4841
4832
  }
4842
4833
  if (tag === DEFAULT_VIEW_TAG) {
4843
4834
  // parent is collection (Map/Array)
4844
4835
  const parent = changeTree.parent;
4845
4836
  if (!Metadata.isValidInstance(parent)) {
4846
4837
  const parentChangeTree = parent[$changes];
4847
- let changes = this.changes[parentChangeTree.refId];
4838
+ let changes = this.changes.get(parentChangeTree.refId);
4848
4839
  if (changes === undefined) {
4849
4840
  changes = {};
4850
- this.changes[parentChangeTree.refId] = changes;
4841
+ this.changes.set(parentChangeTree.refId, changes);
4851
4842
  }
4852
4843
  // DELETE / DELETE BY REF ID
4853
4844
  changes[changeTree.parentIndex] = exports.OPERATION.DELETE;