@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.
- package/build/encoder/StateView.d.ts +1 -1
- package/build/index.cjs +80 -42
- package/build/index.cjs.map +1 -1
- package/build/index.js +80 -42
- package/build/index.mjs +80 -42
- package/build/index.mjs.map +1 -1
- package/package.json +1 -1
- package/src/Metadata.ts +17 -4
- package/src/Schema.ts +3 -2
- package/src/decoder/DecodeOperation.ts +13 -4
- package/src/encoder/ChangeTree.ts +17 -3
- package/src/encoder/StateView.ts +32 -32
|
@@ -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,
|
|
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
|
-
|
|
821
|
-
|
|
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
|
|
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
|
-
//
|
|
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 &&
|
|
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; //
|
|
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
|
|
5659
|
-
|
|
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
|
-
|
|
5671
|
-
|
|
5672
|
-
|
|
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
|
-
|
|
5729
|
-
|
|
5730
|
-
|
|
5731
|
-
|
|
5732
|
-
|
|
5733
|
-
|
|
5734
|
-
|
|
5735
|
-
|
|
5736
|
-
|
|
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
|
-
//
|
|
5816
|
-
tags.
|
|
5817
|
-
|
|
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
|
|
5868
|
+
return tags != null && (tags & tag) !== 0;
|
|
5831
5869
|
}
|
|
5832
5870
|
clear() {
|
|
5833
5871
|
if (!this.iterable) {
|