@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
@@ -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
  }
@@ -1249,6 +1248,7 @@ class ChangeTree {
1249
1248
  else {
1250
1249
  enqueueChangeTree(this.root, this, 'changes');
1251
1250
  }
1251
+ return previousValue;
1252
1252
  }
1253
1253
  endEncode() {
1254
1254
  this.indexedOperations = {};
@@ -1533,19 +1533,6 @@ function decodeValue(decoder, operation, ref, index, type, bytes, it, allChanges
1533
1533
  //
1534
1534
  if (operation !== exports.OPERATION.DELETE_AND_ADD) {
1535
1535
  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
1536
  }
1550
1537
  value = undefined;
1551
1538
  }
@@ -1893,7 +1880,8 @@ class ArraySchema {
1893
1880
  else {
1894
1881
  if (setValue[$changes]) {
1895
1882
  assertInstanceType(setValue, obj[$childType], obj, key);
1896
- if (obj.items[key] !== undefined) {
1883
+ const previousValue = obj.items[key];
1884
+ if (previousValue !== undefined) {
1897
1885
  if (setValue[$changes].isNew) {
1898
1886
  this[$changes].indexedOperation(Number(key), exports.OPERATION.MOVE_AND_ADD);
1899
1887
  }
@@ -1905,10 +1893,13 @@ class ArraySchema {
1905
1893
  this[$changes].indexedOperation(Number(key), exports.OPERATION.MOVE);
1906
1894
  }
1907
1895
  }
1896
+ // remove root reference from previous value
1897
+ previousValue[$changes].root?.remove(previousValue[$changes]);
1908
1898
  }
1909
1899
  else if (setValue[$changes].isNew) {
1910
1900
  this[$changes].indexedOperation(Number(key), exports.OPERATION.ADD);
1911
1901
  }
1902
+ setValue[$changes].setParent(this, obj[$changes].root, key);
1912
1903
  }
1913
1904
  else {
1914
1905
  obj.$changeAt(Number(key), setValue);
@@ -2503,7 +2494,7 @@ class MapSchema {
2503
2494
  static [(_a$3 = $encoder, _b$3 = $decoder, $filter)](ref, index, view) {
2504
2495
  return (!view ||
2505
2496
  typeof (ref[$childType]) === "string" ||
2506
- view.items.has(ref[$getByIndex](index)[$changes]));
2497
+ view.items.has((ref[$getByIndex](index) ?? ref.deletedItems[index])[$changes]));
2507
2498
  }
2508
2499
  static is(type) {
2509
2500
  return type['map'] !== undefined;
@@ -2511,6 +2502,7 @@ class MapSchema {
2511
2502
  constructor(initialValues) {
2512
2503
  this.$items = new Map();
2513
2504
  this.$indexes = new Map();
2505
+ this.deletedItems = {};
2514
2506
  this[$changes] = new ChangeTree(this);
2515
2507
  this[$changes].indexes = {};
2516
2508
  if (initialValues) {
@@ -2546,33 +2538,34 @@ class MapSchema {
2546
2538
  // See: https://github.com/colyseus/colyseus/issues/561#issuecomment-1646733468
2547
2539
  key = key.toString();
2548
2540
  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
2541
  const isRef = (value[$changes]) !== undefined;
2558
- //
2559
- // (encoding)
2560
- // set a unique id to relate directly with this key/value.
2561
- //
2562
- if (!isReplace) {
2542
+ let index;
2543
+ let operation;
2544
+ // IS REPLACE?
2545
+ if (typeof (changeTree.indexes[key]) !== "undefined") {
2546
+ index = changeTree.indexes[key];
2547
+ operation = exports.OPERATION.REPLACE;
2548
+ const previousValue = this.$items.get(key);
2549
+ if (previousValue === value) {
2550
+ // if value is the same, avoid re-encoding it.
2551
+ return;
2552
+ }
2553
+ else if (isRef) {
2554
+ // if is schema, force ADD operation if value differ from previous one.
2555
+ operation = exports.OPERATION.DELETE_AND_ADD;
2556
+ // remove reference from previous value
2557
+ if (previousValue !== undefined) {
2558
+ previousValue[$changes].root?.remove(previousValue[$changes]);
2559
+ }
2560
+ }
2561
+ }
2562
+ else {
2563
+ index = changeTree.indexes[$numFields] ?? 0;
2564
+ operation = exports.OPERATION.ADD;
2563
2565
  this.$indexes.set(index, key);
2564
2566
  changeTree.indexes[key] = index;
2565
2567
  changeTree.indexes[$numFields] = index + 1;
2566
2568
  }
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
2569
  this.$items.set(key, value);
2577
2570
  changeTree.change(index, operation);
2578
2571
  //
@@ -2589,7 +2582,7 @@ class MapSchema {
2589
2582
  }
2590
2583
  delete(key) {
2591
2584
  const index = this[$changes].indexes[key];
2592
- this[$changes].delete(index);
2585
+ this.deletedItems[index] = this[$changes].delete(index);
2593
2586
  return this.$items.delete(key);
2594
2587
  }
2595
2588
  clear() {
@@ -2636,17 +2629,7 @@ class MapSchema {
2636
2629
  this.$indexes.delete(index);
2637
2630
  }
2638
2631
  [$onEncodeEnd]() {
2639
- const changeTree = this[$changes];
2640
- const keys = Object.keys(changeTree.indexedOperations);
2641
- for (let i = 0, len = keys.length; i < len; i++) {
2642
- const key = keys[i];
2643
- const fieldIndex = Number(key);
2644
- const operation = changeTree.indexedOperations[key];
2645
- if (operation === exports.OPERATION.DELETE) {
2646
- const index = this[$getByIndex](fieldIndex);
2647
- delete changeTree.indexes[index];
2648
- }
2649
- }
2632
+ this.deletedItems = {};
2650
2633
  }
2651
2634
  toJSON() {
2652
2635
  const map = {};
@@ -2944,13 +2927,14 @@ function getPropertyDescriptor(fieldCached, fieldIndex, type, complexTypeKlass)
2944
2927
  const changeTree = this[$changes];
2945
2928
  //
2946
2929
  // 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
2930
  //
2949
2931
  if (previousValue !== undefined && previousValue[$changes]) {
2950
2932
  changeTree.root?.remove(previousValue[$changes]);
2933
+ this.constructor[$track](changeTree, fieldIndex, exports.OPERATION.DELETE_AND_ADD);
2934
+ }
2935
+ else {
2936
+ this.constructor[$track](changeTree, fieldIndex, exports.OPERATION.ADD);
2951
2937
  }
2952
- // flag the change for encoding.
2953
- this.constructor[$track](changeTree, fieldIndex, exports.OPERATION.ADD);
2954
2938
  //
2955
2939
  // call setParent() recursively for this and its child
2956
2940
  // structures.
@@ -3773,6 +3757,7 @@ class Root {
3773
3757
  if (spliceOne(changeSet, changeSet.indexOf(changeTree))) {
3774
3758
  changeTree[changeSetName].queueRootIndex = -1;
3775
3759
  // changeSet[index] = undefined;
3760
+ return true;
3776
3761
  }
3777
3762
  }
3778
3763
  clear() {
@@ -3808,12 +3793,6 @@ class Encoder {
3808
3793
  const changeTrees = this.root[changeSetName];
3809
3794
  for (let i = 0, numChangeTrees = changeTrees.length; i < numChangeTrees; i++) {
3810
3795
  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
3796
  if (hasView) {
3818
3797
  if (!view.items.has(changeTree)) {
3819
3798
  view.invisible.add(changeTree);
@@ -3823,13 +3802,24 @@ class Encoder {
3823
3802
  view.invisible.delete(changeTree); // remove from invisible list
3824
3803
  }
3825
3804
  }
3805
+ const operations = changeTree[changeSetName];
3806
+ const ref = changeTree.ref;
3807
+ // TODO: avoid iterating over change tree if no changes were made
3808
+ const numChanges = operations.operations.length;
3809
+ if (numChanges === 0) {
3810
+ continue;
3811
+ }
3812
+ const ctor = ref.constructor;
3813
+ const encoder = ctor[$encoder];
3814
+ const filter = ctor[$filter];
3815
+ const metadata = ctor[Symbol.metadata];
3826
3816
  // skip root `refId` if it's the first change tree
3827
3817
  // (unless it "hasView", which will need to revisit the root)
3828
3818
  if (hasView || it.offset > initialOffset || changeTree !== rootChangeTree) {
3829
3819
  buffer[it.offset++] = SWITCH_TO_STRUCTURE & 255;
3830
3820
  encode.number(buffer, changeTree.refId, it);
3831
3821
  }
3832
- for (let j = 0, numChanges = operations.operations.length; j < numChanges; j++) {
3822
+ for (let j = 0; j < numChanges; j++) {
3833
3823
  const fieldIndex = operations.operations[j];
3834
3824
  const operation = (fieldIndex < 0)
3835
3825
  ? Math.abs(fieldIndex) // "pure" operation without fieldIndex (e.g. CLEAR, REVERSE, etc.)
@@ -3926,14 +3916,16 @@ class Encoder {
3926
3916
  encodeView(view, sharedOffset, it, bytes = this.sharedBuffer) {
3927
3917
  const viewOffset = it.offset;
3928
3918
  // 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];
3919
+ for (const [refId, changes] of view.changes) {
3933
3920
  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
- ) {
3921
+ if (changeTree === undefined) {
3922
+ // detached instance, remove from view and skip.
3923
+ view.changes.delete(refId);
3924
+ continue;
3925
+ }
3926
+ const keys = Object.keys(changes);
3927
+ if (keys.length === 0) {
3928
+ // FIXME: avoid having empty changes if no changes were made
3937
3929
  // console.log("changes.size === 0, skip", changeTree.ref.constructor.name);
3938
3930
  continue;
3939
3931
  }
@@ -3943,13 +3935,14 @@ class Encoder {
3943
3935
  const metadata = ctor[Symbol.metadata];
3944
3936
  bytes[it.offset++] = SWITCH_TO_STRUCTURE & 255;
3945
3937
  encode.number(bytes, changeTree.refId, it);
3946
- const keys = Object.keys(changes);
3947
3938
  for (let i = 0, numChanges = keys.length; i < numChanges; i++) {
3948
- const key = keys[i];
3949
- const operation = changes[key];
3939
+ const index = Number(keys[i]);
3940
+ // 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")
3941
+ const value = changeTree.ref[$getByIndex](index);
3942
+ const operation = (value !== undefined && changes[index]) || exports.OPERATION.DELETE;
3950
3943
  // isEncodeAll = false
3951
3944
  // hasView = true
3952
- encoder(this, bytes, changeTree, Number(key), operation, it, false, true, metadata);
3945
+ encoder(this, bytes, changeTree, index, operation, it, false, true, metadata);
3953
3946
  }
3954
3947
  }
3955
3948
  //
@@ -3957,7 +3950,7 @@ class Encoder {
3957
3950
  // (to allow re-using StateView's for multiple clients)
3958
3951
  //
3959
3952
  // clear "view" changes after encoding
3960
- view.changes = {};
3953
+ view.changes.clear();
3961
3954
  // try to encode "filtered" changes
3962
3955
  this.encode(it, view, bytes, "filteredChanges", false, viewOffset);
3963
3956
  return Buffer.concat([
@@ -3965,20 +3958,6 @@ class Encoder {
3965
3958
  bytes.subarray(viewOffset, it.offset)
3966
3959
  ]);
3967
3960
  }
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
3961
  discardChanges() {
3983
3962
  // discard shared changes
3984
3963
  let length = this.root.changes.length;
@@ -4710,7 +4689,8 @@ class StateView {
4710
4689
  * Manual "ADD" operations for changes per ChangeTree, specific to this view.
4711
4690
  * (This is used to force encoding a property, even if it was not changed)
4712
4691
  */
4713
- this.changes = {};
4692
+ // TODO: use map here!? may fix encode ordering issue
4693
+ this.changes = new Map();
4714
4694
  }
4715
4695
  // TODO: allow to set multiple tags at once
4716
4696
  add(obj, tag = DEFAULT_VIEW_TAG, checkIncludeParent = true) {
@@ -4726,16 +4706,16 @@ class StateView {
4726
4706
  // - if it was invisible to this view
4727
4707
  // - if it were previously filtered out
4728
4708
  if (checkIncludeParent && changeTree.parent) {
4729
- this.addParent(changeTree.parent[$changes], changeTree.parentIndex, tag);
4709
+ this.addParentOf(changeTree, tag);
4730
4710
  }
4731
4711
  //
4732
4712
  // TODO: when adding an item of a MapSchema, the changes may not
4733
4713
  // be set (only the parent's changes are set)
4734
4714
  //
4735
- let changes = this.changes[changeTree.refId];
4715
+ let changes = this.changes.get(changeTree.refId);
4736
4716
  if (changes === undefined) {
4737
4717
  changes = {};
4738
- this.changes[changeTree.refId] = changes;
4718
+ this.changes.set(changeTree.refId, changes);
4739
4719
  }
4740
4720
  // set tag
4741
4721
  if (tag !== DEFAULT_VIEW_TAG) {
@@ -4791,24 +4771,28 @@ class StateView {
4791
4771
  });
4792
4772
  return this;
4793
4773
  }
4794
- addParent(changeTree, parentIndex, tag) {
4795
- // view must have all "changeTree" parent tree
4796
- this.items.add(changeTree);
4797
- // add parent's parent
4798
- const parentChangeTree = changeTree.parent?.[$changes];
4799
- if (parentChangeTree && (parentChangeTree.filteredChanges !== undefined)) {
4800
- this.addParent(parentChangeTree, changeTree.parentIndex, tag);
4801
- }
4802
- // parent is already available, no need to add it!
4803
- if (!this.invisible.has(changeTree)) {
4804
- return;
4774
+ addParentOf(childChangeTree, tag) {
4775
+ const changeTree = childChangeTree.parent[$changes];
4776
+ const parentIndex = childChangeTree.parentIndex;
4777
+ if (!this.items.has(changeTree)) {
4778
+ // view must have all "changeTree" parent tree
4779
+ this.items.add(changeTree);
4780
+ // add parent's parent
4781
+ const parentChangeTree = changeTree.parent?.[$changes];
4782
+ if (parentChangeTree && (parentChangeTree.filteredChanges !== undefined)) {
4783
+ this.addParentOf(changeTree, tag);
4784
+ }
4785
+ // parent is already available, no need to add it!
4786
+ if (!this.invisible.has(changeTree)) {
4787
+ return;
4788
+ }
4805
4789
  }
4806
4790
  // add parent's tag properties
4807
4791
  if (changeTree.getChange(parentIndex) !== exports.OPERATION.DELETE) {
4808
- let changes = this.changes[changeTree.refId];
4792
+ let changes = this.changes.get(changeTree.refId);
4809
4793
  if (changes === undefined) {
4810
4794
  changes = {};
4811
- this.changes[changeTree.refId] = changes;
4795
+ this.changes.set(changeTree.refId, changes);
4812
4796
  }
4813
4797
  if (!this.tags) {
4814
4798
  this.tags = new WeakMap();
@@ -4834,20 +4818,20 @@ class StateView {
4834
4818
  this.items.delete(changeTree);
4835
4819
  const ref = changeTree.ref;
4836
4820
  const metadata = ref.constructor[Symbol.metadata];
4837
- let changes = this.changes[changeTree.refId];
4821
+ let changes = this.changes.get(changeTree.refId);
4838
4822
  if (changes === undefined) {
4839
4823
  changes = {};
4840
- this.changes[changeTree.refId] = changes;
4824
+ this.changes.set(changeTree.refId, changes);
4841
4825
  }
4842
4826
  if (tag === DEFAULT_VIEW_TAG) {
4843
4827
  // parent is collection (Map/Array)
4844
4828
  const parent = changeTree.parent;
4845
4829
  if (!Metadata.isValidInstance(parent)) {
4846
4830
  const parentChangeTree = parent[$changes];
4847
- let changes = this.changes[parentChangeTree.refId];
4831
+ let changes = this.changes.get(parentChangeTree.refId);
4848
4832
  if (changes === undefined) {
4849
4833
  changes = {};
4850
- this.changes[parentChangeTree.refId] = changes;
4834
+ this.changes.set(parentChangeTree.refId, changes);
4851
4835
  }
4852
4836
  // DELETE / DELETE BY REF ID
4853
4837
  changes[changeTree.parentIndex] = exports.OPERATION.DELETE;
@@ -4879,6 +4863,13 @@ class StateView {
4879
4863
  }
4880
4864
  return this;
4881
4865
  }
4866
+ has(obj) {
4867
+ return this.items.has(obj[$changes]);
4868
+ }
4869
+ hasTag(ob, tag = DEFAULT_VIEW_TAG) {
4870
+ const tags = this.tags?.get(ob[$changes]);
4871
+ return tags?.has(tag) ?? false;
4872
+ }
4882
4873
  }
4883
4874
 
4884
4875
  registerType("map", { constructor: MapSchema });