@colyseus/schema 4.0.24 → 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
@@ -1371,7 +1386,8 @@ class ChangeTree {
1371
1386
  key += `-${this.root.types.schemas.get(parentConstructor)}`;
1372
1387
  }
1373
1388
  key += `-${parentIndex}`;
1374
- const fieldHasViewTag = Metadata.hasViewTagAtIndex(parentConstructor?.[Symbol.metadata], parentIndex);
1389
+ const parentMetadata = parentConstructor?.[Symbol.metadata];
1390
+ const fieldHasViewTag = Metadata.hasViewTagAtIndex(parentMetadata, parentIndex);
1375
1391
  this.isFiltered = parent[$changes].isFiltered // in case parent is already filtered
1376
1392
  || this.root.types.parentFiltered[key]
1377
1393
  || fieldHasViewTag;
@@ -1380,9 +1396,22 @@ class ChangeTree {
1380
1396
  // when it's available, we need to enqueue the "changes" changeset into the "filteredChanges" changeset.
1381
1397
  //
1382
1398
  if (this.isFiltered) {
1399
+ //
1400
+ // Children of a `@view(N)` collection (non-default tag) inherit
1401
+ // visibility from their parent, so items pushed/set after the
1402
+ // initial `view.add(state, N)` show up automatically.
1403
+ //
1404
+ // Default-tag `@view()` collections deliberately keep per-item
1405
+ // gating — `view.add(item)` is required to opt each one in.
1406
+ //
1407
+ // The `parentMetadata[parentIndex].tag` access is safe inside
1408
+ // this branch: the OR's short-circuit means we only reach it
1409
+ // when `fieldHasViewTag` is true, which guarantees the metadata
1410
+ // entry and its `tag` property exist.
1411
+ //
1383
1412
  this.isVisibilitySharedWithParent = (parentChangeTree.isFiltered &&
1384
1413
  typeof (refType) !== "string" &&
1385
- !fieldHasViewTag);
1414
+ (!fieldHasViewTag || (parentIsCollection && parentMetadata[parentIndex].tag !== DEFAULT_VIEW_TAG)));
1386
1415
  if (!this.filteredChanges) {
1387
1416
  this.filteredChanges = createChangeSet();
1388
1417
  this.allFilteredChanges = createChangeSet();
@@ -1662,17 +1691,25 @@ function decodeValue(decoder, operation, ref, index, type, bytes, it, allChanges
1662
1691
  if (previousValue) {
1663
1692
  let previousRefId = previousValue[$refId];
1664
1693
  if (previousRefId !== undefined && refId !== previousRefId) {
1694
+ // Collection field replaced by a different instance.
1665
1695
  //
1666
- // enqueue onRemove if structure has been replaced.
1667
- //
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.
1668
1707
  const entries = previousValue.entries();
1669
1708
  let iter;
1670
1709
  while ((iter = entries.next()) && !iter.done) {
1671
1710
  const [key, value] = iter.value;
1672
- // if value is a schema, remove its reference
1673
1711
  if (typeof (value) === "object") {
1674
1712
  previousRefId = value[$refId];
1675
- $root.removeRef(previousRefId);
1676
1713
  }
1677
1714
  allChanges.push({
1678
1715
  ref: previousValue,
@@ -3750,9 +3787,10 @@ class Schema {
3750
3787
  return view.isChangeTreeVisible(ref[$changes]);
3751
3788
  }
3752
3789
  else {
3753
- // 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().
3754
3792
  const tags = view.tags?.get(ref[$changes]);
3755
- return tags && tags.has(tag);
3793
+ return tags != null && (tag & tags) !== 0;
3756
3794
  }
3757
3795
  }
3758
3796
  // allow inherited classes to have a constructor
@@ -5566,7 +5604,7 @@ class StateView {
5566
5604
  * List of ChangeTree's that are invisible to this view
5567
5605
  */
5568
5606
  invisible = new WeakSet();
5569
- tags; // TODO: use bit manipulation instead of Set<number> ()
5607
+ tags; // bitmask of tags used to add each ChangeTree
5570
5608
  /**
5571
5609
  * Manual "ADD" operations for changes per ChangeTree, specific to this view.
5572
5610
  * (This is used to force encoding a property, even if it was not changed)
@@ -5654,9 +5692,16 @@ class StateView {
5654
5692
  changeTree.forEachChild((change, index) => {
5655
5693
  // Do not ADD children that don't have the same tag
5656
5694
  if (metadata &&
5657
- metadata[index].tag !== undefined &&
5658
- metadata[index].tag !== tag) {
5659
- 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
+ }
5660
5705
  }
5661
5706
  if (this.add(change.ref, tag, false)) {
5662
5707
  isChildAdded = true;
@@ -5667,15 +5712,9 @@ class StateView {
5667
5712
  if (!this.tags) {
5668
5713
  this.tags = new WeakMap();
5669
5714
  }
5670
- let tags;
5671
- if (!this.tags.has(changeTree)) {
5672
- tags = new Set();
5673
- this.tags.set(changeTree, tags);
5674
- }
5675
- else {
5676
- tags = this.tags.get(changeTree);
5677
- }
5678
- 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);
5679
5718
  // Ref: add tagged properties
5680
5719
  metadata?.[$fieldIndexesByViewTag]?.[tag]?.forEach((index) => {
5681
5720
  if (changeTree.getChange(index) !== exports.OPERATION.DELETE) {
@@ -5699,7 +5738,7 @@ class StateView {
5699
5738
  if (op !== exports.OPERATION.DELETE &&
5700
5739
  (isInvisible || // if "invisible", include all
5701
5740
  tagAtIndex === undefined || // "all change" with no tag
5702
- tagAtIndex === tag // tagged property
5741
+ (tagAtIndex === DEFAULT_VIEW_TAG || (tag !== DEFAULT_VIEW_TAG && (tagAtIndex & tag) !== 0)) // tagged property
5703
5742
  )) {
5704
5743
  changes[index] = op;
5705
5744
  isChildAdded = true; // FIXME: assign only once
@@ -5725,18 +5764,16 @@ class StateView {
5725
5764
  // add parent's tag properties
5726
5765
  if (changeTree.getChange(parentIndex) !== exports.OPERATION.DELETE) {
5727
5766
  const changes = this.touchChanges(changeTree.ref[$refId]);
5728
- if (!this.tags) {
5729
- this.tags = new WeakMap();
5730
- }
5731
- let tags;
5732
- if (!this.tags.has(changeTree)) {
5733
- tags = new Set();
5734
- this.tags.set(changeTree, tags);
5735
- }
5736
- else {
5737
- 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);
5738
5776
  }
5739
- tags.add(tag);
5740
5777
  changes[parentIndex] = exports.OPERATION.ADD;
5741
5778
  }
5742
5779
  }
@@ -5806,18 +5843,19 @@ class StateView {
5806
5843
  }
5807
5844
  // remove tag
5808
5845
  if (this.tags && this.tags.has(changeTree)) {
5809
- const tags = this.tags.get(changeTree);
5810
5846
  if (tag === undefined) {
5811
5847
  // delete all tags
5812
5848
  this.tags.delete(changeTree);
5813
5849
  }
5814
5850
  else {
5815
- // delete specific tag
5816
- tags.delete(tag);
5817
- // if tag set is empty, delete it entirely
5818
- 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) {
5819
5854
  this.tags.delete(changeTree);
5820
5855
  }
5856
+ else {
5857
+ this.tags.set(changeTree, newMask);
5858
+ }
5821
5859
  }
5822
5860
  }
5823
5861
  return this;
@@ -5827,7 +5865,7 @@ class StateView {
5827
5865
  }
5828
5866
  hasTag(ob, tag = DEFAULT_VIEW_TAG) {
5829
5867
  const tags = this.tags?.get(ob[$changes]);
5830
- return tags?.has(tag) ?? false;
5868
+ return tags != null && (tags & tag) !== 0;
5831
5869
  }
5832
5870
  clear() {
5833
5871
  if (!this.iterable) {