@colyseus/schema 3.0.11 → 3.0.13

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 (38) hide show
  1. package/build/cjs/index.js +99 -108
  2. package/build/cjs/index.js.map +1 -1
  3. package/build/esm/index.mjs +99 -108
  4. package/build/esm/index.mjs.map +1 -1
  5. package/build/umd/index.js +99 -108
  6. package/lib/Metadata.js +1 -2
  7. package/lib/Metadata.js.map +1 -1
  8. package/lib/annotations.js +4 -3
  9. package/lib/annotations.js.map +1 -1
  10. package/lib/decoder/DecodeOperation.js +0 -13
  11. package/lib/decoder/DecodeOperation.js.map +1 -1
  12. package/lib/encoder/ChangeTree.d.ts +1 -1
  13. package/lib/encoder/ChangeTree.js +1 -0
  14. package/lib/encoder/ChangeTree.js.map +1 -1
  15. package/lib/encoder/Encoder.d.ts +0 -1
  16. package/lib/encoder/Encoder.js +27 -33
  17. package/lib/encoder/Encoder.js.map +1 -1
  18. package/lib/encoder/Root.d.ts +1 -1
  19. package/lib/encoder/Root.js +1 -0
  20. package/lib/encoder/Root.js.map +1 -1
  21. package/lib/encoder/StateView.d.ts +4 -4
  22. package/lib/encoder/StateView.js +33 -21
  23. package/lib/encoder/StateView.js.map +1 -1
  24. package/lib/types/custom/ArraySchema.js +5 -1
  25. package/lib/types/custom/ArraySchema.js.map +1 -1
  26. package/lib/types/custom/MapSchema.d.ts +3 -0
  27. package/lib/types/custom/MapSchema.js +28 -35
  28. package/lib/types/custom/MapSchema.js.map +1 -1
  29. package/package.json +1 -1
  30. package/src/Metadata.ts +1 -2
  31. package/src/annotations.ts +4 -4
  32. package/src/decoder/DecodeOperation.ts +0 -14
  33. package/src/encoder/ChangeTree.ts +2 -2
  34. package/src/encoder/Encoder.ts +31 -44
  35. package/src/encoder/Root.ts +1 -0
  36. package/src/encoder/StateView.ts +34 -22
  37. package/src/types/custom/ArraySchema.ts +8 -1
  38. package/src/types/custom/MapSchema.ts +29 -43
@@ -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
  }
@@ -1247,6 +1246,7 @@ class ChangeTree {
1247
1246
  else {
1248
1247
  enqueueChangeTree(this.root, this, 'changes');
1249
1248
  }
1249
+ return previousValue;
1250
1250
  }
1251
1251
  endEncode() {
1252
1252
  this.indexedOperations = {};
@@ -1531,19 +1531,6 @@ function decodeValue(decoder, operation, ref, index, type, bytes, it, allChanges
1531
1531
  //
1532
1532
  if (operation !== OPERATION.DELETE_AND_ADD) {
1533
1533
  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
1534
  }
1548
1535
  value = undefined;
1549
1536
  }
@@ -1891,7 +1878,8 @@ class ArraySchema {
1891
1878
  else {
1892
1879
  if (setValue[$changes]) {
1893
1880
  assertInstanceType(setValue, obj[$childType], obj, key);
1894
- if (obj.items[key] !== undefined) {
1881
+ const previousValue = obj.items[key];
1882
+ if (previousValue !== undefined) {
1895
1883
  if (setValue[$changes].isNew) {
1896
1884
  this[$changes].indexedOperation(Number(key), OPERATION.MOVE_AND_ADD);
1897
1885
  }
@@ -1903,10 +1891,13 @@ class ArraySchema {
1903
1891
  this[$changes].indexedOperation(Number(key), OPERATION.MOVE);
1904
1892
  }
1905
1893
  }
1894
+ // remove root reference from previous value
1895
+ previousValue[$changes].root?.remove(previousValue[$changes]);
1906
1896
  }
1907
1897
  else if (setValue[$changes].isNew) {
1908
1898
  this[$changes].indexedOperation(Number(key), OPERATION.ADD);
1909
1899
  }
1900
+ setValue[$changes].setParent(this, obj[$changes].root, key);
1910
1901
  }
1911
1902
  else {
1912
1903
  obj.$changeAt(Number(key), setValue);
@@ -2501,7 +2492,7 @@ class MapSchema {
2501
2492
  static [(_a$3 = $encoder, _b$3 = $decoder, $filter)](ref, index, view) {
2502
2493
  return (!view ||
2503
2494
  typeof (ref[$childType]) === "string" ||
2504
- view.items.has(ref[$getByIndex](index)[$changes]));
2495
+ view.items.has((ref[$getByIndex](index) ?? ref.deletedItems[index])[$changes]));
2505
2496
  }
2506
2497
  static is(type) {
2507
2498
  return type['map'] !== undefined;
@@ -2509,6 +2500,7 @@ class MapSchema {
2509
2500
  constructor(initialValues) {
2510
2501
  this.$items = new Map();
2511
2502
  this.$indexes = new Map();
2503
+ this.deletedItems = {};
2512
2504
  this[$changes] = new ChangeTree(this);
2513
2505
  this[$changes].indexes = {};
2514
2506
  if (initialValues) {
@@ -2544,33 +2536,34 @@ class MapSchema {
2544
2536
  // See: https://github.com/colyseus/colyseus/issues/561#issuecomment-1646733468
2545
2537
  key = key.toString();
2546
2538
  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
2539
  const isRef = (value[$changes]) !== undefined;
2556
- //
2557
- // (encoding)
2558
- // set a unique id to relate directly with this key/value.
2559
- //
2560
- 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 = 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 = 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 = OPERATION.ADD;
2561
2563
  this.$indexes.set(index, key);
2562
2564
  changeTree.indexes[key] = index;
2563
2565
  changeTree.indexes[$numFields] = index + 1;
2564
2566
  }
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
2567
  this.$items.set(key, value);
2575
2568
  changeTree.change(index, operation);
2576
2569
  //
@@ -2587,7 +2580,7 @@ class MapSchema {
2587
2580
  }
2588
2581
  delete(key) {
2589
2582
  const index = this[$changes].indexes[key];
2590
- this[$changes].delete(index);
2583
+ this.deletedItems[index] = this[$changes].delete(index);
2591
2584
  return this.$items.delete(key);
2592
2585
  }
2593
2586
  clear() {
@@ -2634,17 +2627,7 @@ class MapSchema {
2634
2627
  this.$indexes.delete(index);
2635
2628
  }
2636
2629
  [$onEncodeEnd]() {
2637
- const changeTree = this[$changes];
2638
- const keys = Object.keys(changeTree.indexedOperations);
2639
- for (let i = 0, len = keys.length; i < len; i++) {
2640
- const key = keys[i];
2641
- const fieldIndex = Number(key);
2642
- const operation = changeTree.indexedOperations[key];
2643
- if (operation === OPERATION.DELETE) {
2644
- const index = this[$getByIndex](fieldIndex);
2645
- delete changeTree.indexes[index];
2646
- }
2647
- }
2630
+ this.deletedItems = {};
2648
2631
  }
2649
2632
  toJSON() {
2650
2633
  const map = {};
@@ -2942,13 +2925,14 @@ function getPropertyDescriptor(fieldCached, fieldIndex, type, complexTypeKlass)
2942
2925
  const changeTree = this[$changes];
2943
2926
  //
2944
2927
  // 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
2928
  //
2947
2929
  if (previousValue !== undefined && previousValue[$changes]) {
2948
2930
  changeTree.root?.remove(previousValue[$changes]);
2931
+ this.constructor[$track](changeTree, fieldIndex, OPERATION.DELETE_AND_ADD);
2932
+ }
2933
+ else {
2934
+ this.constructor[$track](changeTree, fieldIndex, OPERATION.ADD);
2949
2935
  }
2950
- // flag the change for encoding.
2951
- this.constructor[$track](changeTree, fieldIndex, OPERATION.ADD);
2952
2936
  //
2953
2937
  // call setParent() recursively for this and its child
2954
2938
  // structures.
@@ -3771,6 +3755,7 @@ class Root {
3771
3755
  if (spliceOne(changeSet, changeSet.indexOf(changeTree))) {
3772
3756
  changeTree[changeSetName].queueRootIndex = -1;
3773
3757
  // changeSet[index] = undefined;
3758
+ return true;
3774
3759
  }
3775
3760
  }
3776
3761
  clear() {
@@ -3806,12 +3791,6 @@ class Encoder {
3806
3791
  const changeTrees = this.root[changeSetName];
3807
3792
  for (let i = 0, numChangeTrees = changeTrees.length; i < numChangeTrees; i++) {
3808
3793
  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
3794
  if (hasView) {
3816
3795
  if (!view.items.has(changeTree)) {
3817
3796
  view.invisible.add(changeTree);
@@ -3821,13 +3800,24 @@ class Encoder {
3821
3800
  view.invisible.delete(changeTree); // remove from invisible list
3822
3801
  }
3823
3802
  }
3803
+ const operations = changeTree[changeSetName];
3804
+ const ref = changeTree.ref;
3805
+ // TODO: avoid iterating over change tree if no changes were made
3806
+ const numChanges = operations.operations.length;
3807
+ if (numChanges === 0) {
3808
+ continue;
3809
+ }
3810
+ const ctor = ref.constructor;
3811
+ const encoder = ctor[$encoder];
3812
+ const filter = ctor[$filter];
3813
+ const metadata = ctor[Symbol.metadata];
3824
3814
  // skip root `refId` if it's the first change tree
3825
3815
  // (unless it "hasView", which will need to revisit the root)
3826
3816
  if (hasView || it.offset > initialOffset || changeTree !== rootChangeTree) {
3827
3817
  buffer[it.offset++] = SWITCH_TO_STRUCTURE & 255;
3828
3818
  encode.number(buffer, changeTree.refId, it);
3829
3819
  }
3830
- for (let j = 0, numChanges = operations.operations.length; j < numChanges; j++) {
3820
+ for (let j = 0; j < numChanges; j++) {
3831
3821
  const fieldIndex = operations.operations[j];
3832
3822
  const operation = (fieldIndex < 0)
3833
3823
  ? Math.abs(fieldIndex) // "pure" operation without fieldIndex (e.g. CLEAR, REVERSE, etc.)
@@ -3924,14 +3914,16 @@ class Encoder {
3924
3914
  encodeView(view, sharedOffset, it, bytes = this.sharedBuffer) {
3925
3915
  const viewOffset = it.offset;
3926
3916
  // 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];
3917
+ for (const [refId, changes] of view.changes) {
3931
3918
  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
- ) {
3919
+ if (changeTree === undefined) {
3920
+ // detached instance, remove from view and skip.
3921
+ view.changes.delete(refId);
3922
+ continue;
3923
+ }
3924
+ const keys = Object.keys(changes);
3925
+ if (keys.length === 0) {
3926
+ // FIXME: avoid having empty changes if no changes were made
3935
3927
  // console.log("changes.size === 0, skip", changeTree.ref.constructor.name);
3936
3928
  continue;
3937
3929
  }
@@ -3941,13 +3933,14 @@ class Encoder {
3941
3933
  const metadata = ctor[Symbol.metadata];
3942
3934
  bytes[it.offset++] = SWITCH_TO_STRUCTURE & 255;
3943
3935
  encode.number(bytes, changeTree.refId, it);
3944
- const keys = Object.keys(changes);
3945
3936
  for (let i = 0, numChanges = keys.length; i < numChanges; i++) {
3946
- const key = keys[i];
3947
- const operation = changes[key];
3937
+ const index = Number(keys[i]);
3938
+ // 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")
3939
+ const value = changeTree.ref[$getByIndex](index);
3940
+ const operation = (value !== undefined && changes[index]) || OPERATION.DELETE;
3948
3941
  // isEncodeAll = false
3949
3942
  // hasView = true
3950
- encoder(this, bytes, changeTree, Number(key), operation, it, false, true, metadata);
3943
+ encoder(this, bytes, changeTree, index, operation, it, false, true, metadata);
3951
3944
  }
3952
3945
  }
3953
3946
  //
@@ -3955,7 +3948,7 @@ class Encoder {
3955
3948
  // (to allow re-using StateView's for multiple clients)
3956
3949
  //
3957
3950
  // clear "view" changes after encoding
3958
- view.changes = {};
3951
+ view.changes.clear();
3959
3952
  // try to encode "filtered" changes
3960
3953
  this.encode(it, view, bytes, "filteredChanges", false, viewOffset);
3961
3954
  return Buffer.concat([
@@ -3963,20 +3956,6 @@ class Encoder {
3963
3956
  bytes.subarray(viewOffset, it.offset)
3964
3957
  ]);
3965
3958
  }
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
3959
  discardChanges() {
3981
3960
  // discard shared changes
3982
3961
  let length = this.root.changes.length;
@@ -4708,7 +4687,8 @@ class StateView {
4708
4687
  * Manual "ADD" operations for changes per ChangeTree, specific to this view.
4709
4688
  * (This is used to force encoding a property, even if it was not changed)
4710
4689
  */
4711
- this.changes = {};
4690
+ // TODO: use map here!? may fix encode ordering issue
4691
+ this.changes = new Map();
4712
4692
  }
4713
4693
  // TODO: allow to set multiple tags at once
4714
4694
  add(obj, tag = DEFAULT_VIEW_TAG, checkIncludeParent = true) {
@@ -4724,16 +4704,16 @@ class StateView {
4724
4704
  // - if it was invisible to this view
4725
4705
  // - if it were previously filtered out
4726
4706
  if (checkIncludeParent && changeTree.parent) {
4727
- this.addParent(changeTree.parent[$changes], changeTree.parentIndex, tag);
4707
+ this.addParentOf(changeTree, tag);
4728
4708
  }
4729
4709
  //
4730
4710
  // TODO: when adding an item of a MapSchema, the changes may not
4731
4711
  // be set (only the parent's changes are set)
4732
4712
  //
4733
- let changes = this.changes[changeTree.refId];
4713
+ let changes = this.changes.get(changeTree.refId);
4734
4714
  if (changes === undefined) {
4735
4715
  changes = {};
4736
- this.changes[changeTree.refId] = changes;
4716
+ this.changes.set(changeTree.refId, changes);
4737
4717
  }
4738
4718
  // set tag
4739
4719
  if (tag !== DEFAULT_VIEW_TAG) {
@@ -4789,24 +4769,28 @@ class StateView {
4789
4769
  });
4790
4770
  return this;
4791
4771
  }
4792
- addParent(changeTree, parentIndex, tag) {
4793
- // view must have all "changeTree" parent tree
4794
- this.items.add(changeTree);
4795
- // add parent's parent
4796
- const parentChangeTree = changeTree.parent?.[$changes];
4797
- if (parentChangeTree && (parentChangeTree.filteredChanges !== undefined)) {
4798
- this.addParent(parentChangeTree, changeTree.parentIndex, tag);
4799
- }
4800
- // parent is already available, no need to add it!
4801
- if (!this.invisible.has(changeTree)) {
4802
- return;
4772
+ addParentOf(childChangeTree, tag) {
4773
+ const changeTree = childChangeTree.parent[$changes];
4774
+ const parentIndex = childChangeTree.parentIndex;
4775
+ if (!this.items.has(changeTree)) {
4776
+ // view must have all "changeTree" parent tree
4777
+ this.items.add(changeTree);
4778
+ // add parent's parent
4779
+ const parentChangeTree = changeTree.parent?.[$changes];
4780
+ if (parentChangeTree && (parentChangeTree.filteredChanges !== undefined)) {
4781
+ this.addParentOf(changeTree, tag);
4782
+ }
4783
+ // parent is already available, no need to add it!
4784
+ if (!this.invisible.has(changeTree)) {
4785
+ return;
4786
+ }
4803
4787
  }
4804
4788
  // add parent's tag properties
4805
4789
  if (changeTree.getChange(parentIndex) !== OPERATION.DELETE) {
4806
- let changes = this.changes[changeTree.refId];
4790
+ let changes = this.changes.get(changeTree.refId);
4807
4791
  if (changes === undefined) {
4808
4792
  changes = {};
4809
- this.changes[changeTree.refId] = changes;
4793
+ this.changes.set(changeTree.refId, changes);
4810
4794
  }
4811
4795
  if (!this.tags) {
4812
4796
  this.tags = new WeakMap();
@@ -4832,20 +4816,20 @@ class StateView {
4832
4816
  this.items.delete(changeTree);
4833
4817
  const ref = changeTree.ref;
4834
4818
  const metadata = ref.constructor[Symbol.metadata];
4835
- let changes = this.changes[changeTree.refId];
4819
+ let changes = this.changes.get(changeTree.refId);
4836
4820
  if (changes === undefined) {
4837
4821
  changes = {};
4838
- this.changes[changeTree.refId] = changes;
4822
+ this.changes.set(changeTree.refId, changes);
4839
4823
  }
4840
4824
  if (tag === DEFAULT_VIEW_TAG) {
4841
4825
  // parent is collection (Map/Array)
4842
4826
  const parent = changeTree.parent;
4843
4827
  if (!Metadata.isValidInstance(parent)) {
4844
4828
  const parentChangeTree = parent[$changes];
4845
- let changes = this.changes[parentChangeTree.refId];
4829
+ let changes = this.changes.get(parentChangeTree.refId);
4846
4830
  if (changes === undefined) {
4847
4831
  changes = {};
4848
- this.changes[parentChangeTree.refId] = changes;
4832
+ this.changes.set(parentChangeTree.refId, changes);
4849
4833
  }
4850
4834
  // DELETE / DELETE BY REF ID
4851
4835
  changes[changeTree.parentIndex] = OPERATION.DELETE;
@@ -4877,6 +4861,13 @@ class StateView {
4877
4861
  }
4878
4862
  return this;
4879
4863
  }
4864
+ has(obj) {
4865
+ return this.items.has(obj[$changes]);
4866
+ }
4867
+ hasTag(ob, tag = DEFAULT_VIEW_TAG) {
4868
+ const tags = this.tags?.get(ob[$changes]);
4869
+ return tags?.has(tag) ?? false;
4870
+ }
4880
4871
  }
4881
4872
 
4882
4873
  registerType("map", { constructor: MapSchema });