@colyseus/schema 4.0.25 → 4.0.26

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.
@@ -15,7 +15,7 @@ export declare class StateView {
15
15
  * List of ChangeTree's that are invisible to this view
16
16
  */
17
17
  invisible: WeakSet<ChangeTree>;
18
- tags?: WeakMap<ChangeTree, Set<number>>;
18
+ tags?: WeakMap<ChangeTree, number>;
19
19
  /**
20
20
  * Manual "ADD" operations for changes per ChangeTree, specific to this view.
21
21
  * (This is used to force encoding a property, even if it was not changed)
package/build/index.cjs CHANGED
@@ -817,10 +817,25 @@ const Metadata = {
817
817
  });
818
818
  }
819
819
  metadata[$viewFieldIndexes].push(index);
820
- if (!metadata[$fieldIndexesByViewTag][tag]) {
821
- metadata[$fieldIndexesByViewTag][tag] = [];
820
+ // Populate $fieldIndexesByViewTag: for a bitmask tag, register the field
821
+ // index under each individual set bit so that view.add(obj, Tag.ONE) finds
822
+ // fields tagged @view(Tag.ONE|Tag.TWO).
823
+ // Negative tags (i.e. DEFAULT_VIEW_TAG = -1) are stored as-is.
824
+ if (tag < 0) {
825
+ if (!metadata[$fieldIndexesByViewTag][tag]) {
826
+ metadata[$fieldIndexesByViewTag][tag] = [];
827
+ }
828
+ metadata[$fieldIndexesByViewTag][tag].push(index);
829
+ }
830
+ else {
831
+ for (let bits = tag; bits > 0; bits &= bits - 1) {
832
+ const bit = bits & (-bits); // isolate lowest set bit
833
+ if (!metadata[$fieldIndexesByViewTag][bit]) {
834
+ metadata[$fieldIndexesByViewTag][bit] = [];
835
+ }
836
+ metadata[$fieldIndexesByViewTag][bit].push(index);
837
+ }
822
838
  }
823
- metadata[$fieldIndexesByViewTag][tag].push(index);
824
839
  },
825
840
  setFields(target, fields) {
826
841
  // for inheritance support
@@ -1676,17 +1691,25 @@ function decodeValue(decoder, operation, ref, index, type, bytes, it, allChanges
1676
1691
  if (previousValue) {
1677
1692
  let previousRefId = previousValue[$refId];
1678
1693
  if (previousRefId !== undefined && refId !== previousRefId) {
1694
+ // Collection field replaced by a different instance.
1679
1695
  //
1680
- // enqueue onRemove if structure has been replaced.
1681
- //
1696
+ // Don't decrement children here: GC (`garbageCollectDeletedRefs`)
1697
+ // removes them once the previous collection's refId hits zero.
1698
+ // Doing it here too would double-decrement a *shared* child and
1699
+ // drop it while still referenced ("refId not found").
1700
+ if ((operation & exports.OPERATION.DELETE) !== exports.OPERATION.DELETE) {
1701
+ // Replacement not tagged DELETE (e.g. pending ADD not upgraded
1702
+ // to DELETE_AND_ADD), so the previous refId wasn't decremented
1703
+ // above. Release it here, else it never gets GC'd (leak).
1704
+ $root.removeRef(previousRefId);
1705
+ }
1706
+ // enqueue onRemove callbacks for the previous collection's children.
1682
1707
  const entries = previousValue.entries();
1683
1708
  let iter;
1684
1709
  while ((iter = entries.next()) && !iter.done) {
1685
1710
  const [key, value] = iter.value;
1686
- // if value is a schema, remove its reference
1687
1711
  if (typeof (value) === "object") {
1688
1712
  previousRefId = value[$refId];
1689
- $root.removeRef(previousRefId);
1690
1713
  }
1691
1714
  allChanges.push({
1692
1715
  ref: previousValue,
@@ -3764,9 +3787,10 @@ class Schema {
3764
3787
  return view.isChangeTreeVisible(ref[$changes]);
3765
3788
  }
3766
3789
  else {
3767
- // view pass: custom tag
3790
+ // view pass: custom tag (bitmask)
3791
+ // tag is the field's stored bitmask; view.tags stores the accumulated bitmask of tags used in view.add().
3768
3792
  const tags = view.tags?.get(ref[$changes]);
3769
- return tags && tags.has(tag);
3793
+ return tags != null && (tag & tags) !== 0;
3770
3794
  }
3771
3795
  }
3772
3796
  // allow inherited classes to have a constructor
@@ -5580,7 +5604,7 @@ class StateView {
5580
5604
  * List of ChangeTree's that are invisible to this view
5581
5605
  */
5582
5606
  invisible = new WeakSet();
5583
- tags; // TODO: use bit manipulation instead of Set<number> ()
5607
+ tags; // bitmask of tags used to add each ChangeTree
5584
5608
  /**
5585
5609
  * Manual "ADD" operations for changes per ChangeTree, specific to this view.
5586
5610
  * (This is used to force encoding a property, even if it was not changed)
@@ -5668,9 +5692,16 @@ class StateView {
5668
5692
  changeTree.forEachChild((change, index) => {
5669
5693
  // Do not ADD children that don't have the same tag
5670
5694
  if (metadata &&
5671
- metadata[index].tag !== undefined &&
5672
- metadata[index].tag !== tag) {
5673
- return;
5695
+ metadata[index].tag !== undefined) {
5696
+ const fieldTag = metadata[index].tag;
5697
+ // DEFAULT_VIEW_TAG fields are visible to all clients.
5698
+ // Custom-tagged fields are only visible when bits overlap,
5699
+ // and never to default-tag clients.
5700
+ const tagMatch = fieldTag === DEFAULT_VIEW_TAG ||
5701
+ (tag !== DEFAULT_VIEW_TAG && (fieldTag & tag) !== 0);
5702
+ if (!tagMatch) {
5703
+ return;
5704
+ }
5674
5705
  }
5675
5706
  if (this.add(change.ref, tag, false)) {
5676
5707
  isChildAdded = true;
@@ -5681,15 +5712,9 @@ class StateView {
5681
5712
  if (!this.tags) {
5682
5713
  this.tags = new WeakMap();
5683
5714
  }
5684
- let tags;
5685
- if (!this.tags.has(changeTree)) {
5686
- tags = new Set();
5687
- this.tags.set(changeTree, tags);
5688
- }
5689
- else {
5690
- tags = this.tags.get(changeTree);
5691
- }
5692
- tags.add(tag);
5715
+ // Add tag bits into the bitmask stored for this ChangeTree.
5716
+ const currentMask = this.tags.get(changeTree) ?? 0;
5717
+ this.tags.set(changeTree, currentMask | tag);
5693
5718
  // Ref: add tagged properties
5694
5719
  metadata?.[$fieldIndexesByViewTag]?.[tag]?.forEach((index) => {
5695
5720
  if (changeTree.getChange(index) !== exports.OPERATION.DELETE) {
@@ -5713,7 +5738,7 @@ class StateView {
5713
5738
  if (op !== exports.OPERATION.DELETE &&
5714
5739
  (isInvisible || // if "invisible", include all
5715
5740
  tagAtIndex === undefined || // "all change" with no tag
5716
- tagAtIndex === tag // tagged property
5741
+ (tagAtIndex === DEFAULT_VIEW_TAG || (tag !== DEFAULT_VIEW_TAG && (tagAtIndex & tag) !== 0)) // tagged property
5717
5742
  )) {
5718
5743
  changes[index] = op;
5719
5744
  isChildAdded = true; // FIXME: assign only once
@@ -5739,18 +5764,16 @@ class StateView {
5739
5764
  // add parent's tag properties
5740
5765
  if (changeTree.getChange(parentIndex) !== exports.OPERATION.DELETE) {
5741
5766
  const changes = this.touchChanges(changeTree.ref[$refId]);
5742
- if (!this.tags) {
5743
- this.tags = new WeakMap();
5744
- }
5745
- let tags;
5746
- if (!this.tags.has(changeTree)) {
5747
- tags = new Set();
5748
- this.tags.set(changeTree, tags);
5749
- }
5750
- else {
5751
- tags = this.tags.get(changeTree);
5767
+ // Only accumulate positive (custom) tags in the bitmask.
5768
+ // DEFAULT_VIEW_TAG = -1 has all bits set and must not be OR'd in,
5769
+ // as it would make every custom-tagged field appear visible.
5770
+ if (tag !== DEFAULT_VIEW_TAG) {
5771
+ if (!this.tags) {
5772
+ this.tags = new WeakMap();
5773
+ }
5774
+ const currentMask = this.tags.has(changeTree) ? this.tags.get(changeTree) : 0;
5775
+ this.tags.set(changeTree, currentMask | tag);
5752
5776
  }
5753
- tags.add(tag);
5754
5777
  changes[parentIndex] = exports.OPERATION.ADD;
5755
5778
  }
5756
5779
  }
@@ -5820,18 +5843,19 @@ class StateView {
5820
5843
  }
5821
5844
  // remove tag
5822
5845
  if (this.tags && this.tags.has(changeTree)) {
5823
- const tags = this.tags.get(changeTree);
5824
5846
  if (tag === undefined) {
5825
5847
  // delete all tags
5826
5848
  this.tags.delete(changeTree);
5827
5849
  }
5828
5850
  else {
5829
- // delete specific tag
5830
- tags.delete(tag);
5831
- // if tag set is empty, delete it entirely
5832
- if (tags.size === 0) {
5851
+ // clear the tag's bits from the bitmask
5852
+ const newMask = this.tags.get(changeTree) & ~tag;
5853
+ if (newMask === 0) {
5833
5854
  this.tags.delete(changeTree);
5834
5855
  }
5856
+ else {
5857
+ this.tags.set(changeTree, newMask);
5858
+ }
5835
5859
  }
5836
5860
  }
5837
5861
  return this;
@@ -5841,7 +5865,7 @@ class StateView {
5841
5865
  }
5842
5866
  hasTag(ob, tag = DEFAULT_VIEW_TAG) {
5843
5867
  const tags = this.tags?.get(ob[$changes]);
5844
- return tags?.has(tag) ?? false;
5868
+ return tags != null && (tags & tag) !== 0;
5845
5869
  }
5846
5870
  clear() {
5847
5871
  if (!this.iterable) {