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