@colyseus/schema 4.0.25 → 4.0.27
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/Encoder.d.ts +2 -0
- package/build/encoder/StateView.d.ts +1 -1
- package/build/index.cjs +97 -66
- package/build/index.cjs.map +1 -1
- package/build/index.js +97 -66
- package/build/index.mjs +97 -66
- 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/Encoder.ts +32 -29
- package/src/encoder/StateView.ts +32 -32
package/build/index.js
CHANGED
|
@@ -821,10 +821,25 @@
|
|
|
821
821
|
});
|
|
822
822
|
}
|
|
823
823
|
metadata[$viewFieldIndexes].push(index);
|
|
824
|
-
|
|
825
|
-
|
|
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
|
-
//
|
|
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 &&
|
|
3797
|
+
return tags != null && (tag & tags) !== 0;
|
|
3774
3798
|
}
|
|
3775
3799
|
}
|
|
3776
3800
|
// allow inherited classes to have a constructor
|
|
@@ -4380,41 +4404,43 @@
|
|
|
4380
4404
|
encoder(this, buffer, changeTree, fieldIndex, operation, it, isEncodeAll, hasView, metadata);
|
|
4381
4405
|
}
|
|
4382
4406
|
}
|
|
4383
|
-
if (it.offset
|
|
4384
|
-
|
|
4385
|
-
|
|
4386
|
-
|
|
4387
|
-
|
|
4407
|
+
if (it.offset <= buffer.byteLength) {
|
|
4408
|
+
return buffer.subarray(0, it.offset);
|
|
4409
|
+
}
|
|
4410
|
+
// Overflowed: grow and re-encode. Reuse the same iterator so `it.offset`
|
|
4411
|
+
// ends accurate — a fresh one strands it at the overflow value and
|
|
4412
|
+
// corrupts the next view's `viewOffset` in a multi-view encode.
|
|
4413
|
+
buffer = this.ensureCapacity(buffer, it.offset);
|
|
4414
|
+
console.warn(`@colyseus/schema buffer overflow. Encoded state is higher than default BUFFER_SIZE. Use the following to increase default BUFFER_SIZE:
|
|
4388
4415
|
|
|
4389
4416
|
import { Encoder } from "@colyseus/schema";
|
|
4390
|
-
Encoder.BUFFER_SIZE = ${Math.round(
|
|
4417
|
+
Encoder.BUFFER_SIZE = ${Math.round(buffer.byteLength / 1024)} * 1024; // ${Math.round(buffer.byteLength / 1024)} KB
|
|
4391
4418
|
`);
|
|
4392
|
-
|
|
4393
|
-
|
|
4394
|
-
// -> No we probably can't unless we catch the need for resize before encoding which is likely more computationally expensive than resizing on demand
|
|
4395
|
-
//
|
|
4396
|
-
const newBuffer = new Uint8Array(newSize);
|
|
4397
|
-
newBuffer.set(buffer); // copy previous encoding steps beyond the initialOffset
|
|
4398
|
-
buffer = newBuffer;
|
|
4399
|
-
// assign resized buffer to local sharedBuffer
|
|
4400
|
-
if (buffer === this.sharedBuffer) {
|
|
4401
|
-
this.sharedBuffer = buffer;
|
|
4402
|
-
}
|
|
4403
|
-
return this.encode({ offset: initialOffset }, view, buffer, changeSetName, isEncodeAll);
|
|
4404
|
-
}
|
|
4405
|
-
else {
|
|
4406
|
-
return buffer.subarray(0, it.offset);
|
|
4407
|
-
}
|
|
4419
|
+
it.offset = initialOffset;
|
|
4420
|
+
return this.encode(it, view, buffer, changeSetName, isEncodeAll);
|
|
4408
4421
|
}
|
|
4409
4422
|
encodeAll(it = { offset: 0 }, buffer = this.sharedBuffer) {
|
|
4410
4423
|
return this.encode(it, undefined, buffer, "allChanges", true);
|
|
4411
4424
|
}
|
|
4412
4425
|
encodeAllView(view, sharedOffset, it, bytes = this.sharedBuffer) {
|
|
4413
4426
|
const viewOffset = it.offset;
|
|
4414
|
-
//
|
|
4415
|
-
this.encode(it, view, bytes, "allFilteredChanges", true, viewOffset);
|
|
4427
|
+
// encode() may reallocate the buffer — keep its return, not the stale `bytes`.
|
|
4428
|
+
bytes = this.encode(it, view, bytes, "allFilteredChanges", true, viewOffset);
|
|
4416
4429
|
return concatBytes(bytes.subarray(0, sharedOffset), bytes.subarray(viewOffset, it.offset));
|
|
4417
4430
|
}
|
|
4431
|
+
/** Grow `buffer` to keep BUFFER_SIZE free bytes past `offset`, preserving `[0, offset)`. */
|
|
4432
|
+
ensureCapacity(buffer, offset) {
|
|
4433
|
+
if (offset + Encoder.BUFFER_SIZE <= buffer.byteLength) {
|
|
4434
|
+
return buffer;
|
|
4435
|
+
}
|
|
4436
|
+
const size = Math.ceil((offset + Encoder.BUFFER_SIZE) / Encoder.BUFFER_SIZE) * Encoder.BUFFER_SIZE;
|
|
4437
|
+
const grown = new Uint8Array(size);
|
|
4438
|
+
grown.set(buffer.subarray(0, offset));
|
|
4439
|
+
if (buffer === this.sharedBuffer) {
|
|
4440
|
+
this.sharedBuffer = grown;
|
|
4441
|
+
}
|
|
4442
|
+
return grown;
|
|
4443
|
+
}
|
|
4418
4444
|
encodeView(view, sharedOffset, it, bytes = this.sharedBuffer) {
|
|
4419
4445
|
const viewOffset = it.offset;
|
|
4420
4446
|
//
|
|
@@ -4453,6 +4479,9 @@
|
|
|
4453
4479
|
const ctor = ref.constructor;
|
|
4454
4480
|
const encoder = ctor[$encoder];
|
|
4455
4481
|
const metadata = ctor[Symbol.metadata];
|
|
4482
|
+
// These writes are unguarded and unrecoverable (view.changes is cleared
|
|
4483
|
+
// below), so unlike encode() they can't re-encode on overflow — grow ahead.
|
|
4484
|
+
bytes = this.ensureCapacity(bytes, it.offset);
|
|
4456
4485
|
bytes[it.offset++] = SWITCH_TO_STRUCTURE & 255;
|
|
4457
4486
|
encode.number(bytes, ref[$refId], it);
|
|
4458
4487
|
for (let i = 0, numChanges = keys.length; i < numChanges; i++) {
|
|
@@ -4472,8 +4501,10 @@
|
|
|
4472
4501
|
// clear "view" changes after encoding
|
|
4473
4502
|
view.changes.clear();
|
|
4474
4503
|
view.changesOutOfOrder = false;
|
|
4475
|
-
//
|
|
4476
|
-
|
|
4504
|
+
// encode() may reallocate the buffer — keep its return, not the stale `bytes`.
|
|
4505
|
+
// Anchor the re-encode at the current offset (default), not `viewOffset`: a
|
|
4506
|
+
// resize must not clobber the view.changes already written at [viewOffset, ).
|
|
4507
|
+
bytes = this.encode(it, view, bytes, "filteredChanges", false);
|
|
4477
4508
|
return concatBytes(bytes.subarray(0, sharedOffset), bytes.subarray(viewOffset, it.offset));
|
|
4478
4509
|
}
|
|
4479
4510
|
/**
|
|
@@ -5584,7 +5615,7 @@
|
|
|
5584
5615
|
* List of ChangeTree's that are invisible to this view
|
|
5585
5616
|
*/
|
|
5586
5617
|
invisible = new WeakSet();
|
|
5587
|
-
tags; //
|
|
5618
|
+
tags; // bitmask of tags used to add each ChangeTree
|
|
5588
5619
|
/**
|
|
5589
5620
|
* Manual "ADD" operations for changes per ChangeTree, specific to this view.
|
|
5590
5621
|
* (This is used to force encoding a property, even if it was not changed)
|
|
@@ -5672,9 +5703,16 @@
|
|
|
5672
5703
|
changeTree.forEachChild((change, index) => {
|
|
5673
5704
|
// Do not ADD children that don't have the same tag
|
|
5674
5705
|
if (metadata &&
|
|
5675
|
-
metadata[index].tag !== undefined
|
|
5676
|
-
metadata[index].tag
|
|
5677
|
-
|
|
5706
|
+
metadata[index].tag !== undefined) {
|
|
5707
|
+
const fieldTag = metadata[index].tag;
|
|
5708
|
+
// DEFAULT_VIEW_TAG fields are visible to all clients.
|
|
5709
|
+
// Custom-tagged fields are only visible when bits overlap,
|
|
5710
|
+
// and never to default-tag clients.
|
|
5711
|
+
const tagMatch = fieldTag === DEFAULT_VIEW_TAG ||
|
|
5712
|
+
(tag !== DEFAULT_VIEW_TAG && (fieldTag & tag) !== 0);
|
|
5713
|
+
if (!tagMatch) {
|
|
5714
|
+
return;
|
|
5715
|
+
}
|
|
5678
5716
|
}
|
|
5679
5717
|
if (this.add(change.ref, tag, false)) {
|
|
5680
5718
|
isChildAdded = true;
|
|
@@ -5685,15 +5723,9 @@
|
|
|
5685
5723
|
if (!this.tags) {
|
|
5686
5724
|
this.tags = new WeakMap();
|
|
5687
5725
|
}
|
|
5688
|
-
|
|
5689
|
-
|
|
5690
|
-
|
|
5691
|
-
this.tags.set(changeTree, tags);
|
|
5692
|
-
}
|
|
5693
|
-
else {
|
|
5694
|
-
tags = this.tags.get(changeTree);
|
|
5695
|
-
}
|
|
5696
|
-
tags.add(tag);
|
|
5726
|
+
// Add tag bits into the bitmask stored for this ChangeTree.
|
|
5727
|
+
const currentMask = this.tags.get(changeTree) ?? 0;
|
|
5728
|
+
this.tags.set(changeTree, currentMask | tag);
|
|
5697
5729
|
// Ref: add tagged properties
|
|
5698
5730
|
metadata?.[$fieldIndexesByViewTag]?.[tag]?.forEach((index) => {
|
|
5699
5731
|
if (changeTree.getChange(index) !== exports.OPERATION.DELETE) {
|
|
@@ -5717,7 +5749,7 @@
|
|
|
5717
5749
|
if (op !== exports.OPERATION.DELETE &&
|
|
5718
5750
|
(isInvisible || // if "invisible", include all
|
|
5719
5751
|
tagAtIndex === undefined || // "all change" with no tag
|
|
5720
|
-
tagAtIndex === tag // tagged property
|
|
5752
|
+
(tagAtIndex === DEFAULT_VIEW_TAG || (tag !== DEFAULT_VIEW_TAG && (tagAtIndex & tag) !== 0)) // tagged property
|
|
5721
5753
|
)) {
|
|
5722
5754
|
changes[index] = op;
|
|
5723
5755
|
isChildAdded = true; // FIXME: assign only once
|
|
@@ -5743,18 +5775,16 @@
|
|
|
5743
5775
|
// add parent's tag properties
|
|
5744
5776
|
if (changeTree.getChange(parentIndex) !== exports.OPERATION.DELETE) {
|
|
5745
5777
|
const changes = this.touchChanges(changeTree.ref[$refId]);
|
|
5746
|
-
|
|
5747
|
-
|
|
5748
|
-
|
|
5749
|
-
|
|
5750
|
-
|
|
5751
|
-
|
|
5752
|
-
|
|
5753
|
-
|
|
5754
|
-
|
|
5755
|
-
tags = this.tags.get(changeTree);
|
|
5778
|
+
// Only accumulate positive (custom) tags in the bitmask.
|
|
5779
|
+
// DEFAULT_VIEW_TAG = -1 has all bits set and must not be OR'd in,
|
|
5780
|
+
// as it would make every custom-tagged field appear visible.
|
|
5781
|
+
if (tag !== DEFAULT_VIEW_TAG) {
|
|
5782
|
+
if (!this.tags) {
|
|
5783
|
+
this.tags = new WeakMap();
|
|
5784
|
+
}
|
|
5785
|
+
const currentMask = this.tags.has(changeTree) ? this.tags.get(changeTree) : 0;
|
|
5786
|
+
this.tags.set(changeTree, currentMask | tag);
|
|
5756
5787
|
}
|
|
5757
|
-
tags.add(tag);
|
|
5758
5788
|
changes[parentIndex] = exports.OPERATION.ADD;
|
|
5759
5789
|
}
|
|
5760
5790
|
}
|
|
@@ -5824,18 +5854,19 @@
|
|
|
5824
5854
|
}
|
|
5825
5855
|
// remove tag
|
|
5826
5856
|
if (this.tags && this.tags.has(changeTree)) {
|
|
5827
|
-
const tags = this.tags.get(changeTree);
|
|
5828
5857
|
if (tag === undefined) {
|
|
5829
5858
|
// delete all tags
|
|
5830
5859
|
this.tags.delete(changeTree);
|
|
5831
5860
|
}
|
|
5832
5861
|
else {
|
|
5833
|
-
//
|
|
5834
|
-
tags.
|
|
5835
|
-
|
|
5836
|
-
if (tags.size === 0) {
|
|
5862
|
+
// clear the tag's bits from the bitmask
|
|
5863
|
+
const newMask = this.tags.get(changeTree) & ~tag;
|
|
5864
|
+
if (newMask === 0) {
|
|
5837
5865
|
this.tags.delete(changeTree);
|
|
5838
5866
|
}
|
|
5867
|
+
else {
|
|
5868
|
+
this.tags.set(changeTree, newMask);
|
|
5869
|
+
}
|
|
5839
5870
|
}
|
|
5840
5871
|
}
|
|
5841
5872
|
return this;
|
|
@@ -5845,7 +5876,7 @@
|
|
|
5845
5876
|
}
|
|
5846
5877
|
hasTag(ob, tag = DEFAULT_VIEW_TAG) {
|
|
5847
5878
|
const tags = this.tags?.get(ob[$changes]);
|
|
5848
|
-
return tags
|
|
5879
|
+
return tags != null && (tags & tag) !== 0;
|
|
5849
5880
|
}
|
|
5850
5881
|
clear() {
|
|
5851
5882
|
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
|
-
|
|
819
|
-
|
|
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
|
-
//
|
|
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 &&
|
|
3791
|
+
return tags != null && (tag & tags) !== 0;
|
|
3768
3792
|
}
|
|
3769
3793
|
}
|
|
3770
3794
|
// allow inherited classes to have a constructor
|
|
@@ -4374,41 +4398,43 @@ class Encoder {
|
|
|
4374
4398
|
encoder(this, buffer, changeTree, fieldIndex, operation, it, isEncodeAll, hasView, metadata);
|
|
4375
4399
|
}
|
|
4376
4400
|
}
|
|
4377
|
-
if (it.offset
|
|
4378
|
-
|
|
4379
|
-
|
|
4380
|
-
|
|
4381
|
-
|
|
4401
|
+
if (it.offset <= buffer.byteLength) {
|
|
4402
|
+
return buffer.subarray(0, it.offset);
|
|
4403
|
+
}
|
|
4404
|
+
// Overflowed: grow and re-encode. Reuse the same iterator so `it.offset`
|
|
4405
|
+
// ends accurate — a fresh one strands it at the overflow value and
|
|
4406
|
+
// corrupts the next view's `viewOffset` in a multi-view encode.
|
|
4407
|
+
buffer = this.ensureCapacity(buffer, it.offset);
|
|
4408
|
+
console.warn(`@colyseus/schema buffer overflow. Encoded state is higher than default BUFFER_SIZE. Use the following to increase default BUFFER_SIZE:
|
|
4382
4409
|
|
|
4383
4410
|
import { Encoder } from "@colyseus/schema";
|
|
4384
|
-
Encoder.BUFFER_SIZE = ${Math.round(
|
|
4411
|
+
Encoder.BUFFER_SIZE = ${Math.round(buffer.byteLength / 1024)} * 1024; // ${Math.round(buffer.byteLength / 1024)} KB
|
|
4385
4412
|
`);
|
|
4386
|
-
|
|
4387
|
-
|
|
4388
|
-
// -> No we probably can't unless we catch the need for resize before encoding which is likely more computationally expensive than resizing on demand
|
|
4389
|
-
//
|
|
4390
|
-
const newBuffer = new Uint8Array(newSize);
|
|
4391
|
-
newBuffer.set(buffer); // copy previous encoding steps beyond the initialOffset
|
|
4392
|
-
buffer = newBuffer;
|
|
4393
|
-
// assign resized buffer to local sharedBuffer
|
|
4394
|
-
if (buffer === this.sharedBuffer) {
|
|
4395
|
-
this.sharedBuffer = buffer;
|
|
4396
|
-
}
|
|
4397
|
-
return this.encode({ offset: initialOffset }, view, buffer, changeSetName, isEncodeAll);
|
|
4398
|
-
}
|
|
4399
|
-
else {
|
|
4400
|
-
return buffer.subarray(0, it.offset);
|
|
4401
|
-
}
|
|
4413
|
+
it.offset = initialOffset;
|
|
4414
|
+
return this.encode(it, view, buffer, changeSetName, isEncodeAll);
|
|
4402
4415
|
}
|
|
4403
4416
|
encodeAll(it = { offset: 0 }, buffer = this.sharedBuffer) {
|
|
4404
4417
|
return this.encode(it, undefined, buffer, "allChanges", true);
|
|
4405
4418
|
}
|
|
4406
4419
|
encodeAllView(view, sharedOffset, it, bytes = this.sharedBuffer) {
|
|
4407
4420
|
const viewOffset = it.offset;
|
|
4408
|
-
//
|
|
4409
|
-
this.encode(it, view, bytes, "allFilteredChanges", true, viewOffset);
|
|
4421
|
+
// encode() may reallocate the buffer — keep its return, not the stale `bytes`.
|
|
4422
|
+
bytes = this.encode(it, view, bytes, "allFilteredChanges", true, viewOffset);
|
|
4410
4423
|
return concatBytes(bytes.subarray(0, sharedOffset), bytes.subarray(viewOffset, it.offset));
|
|
4411
4424
|
}
|
|
4425
|
+
/** Grow `buffer` to keep BUFFER_SIZE free bytes past `offset`, preserving `[0, offset)`. */
|
|
4426
|
+
ensureCapacity(buffer, offset) {
|
|
4427
|
+
if (offset + Encoder.BUFFER_SIZE <= buffer.byteLength) {
|
|
4428
|
+
return buffer;
|
|
4429
|
+
}
|
|
4430
|
+
const size = Math.ceil((offset + Encoder.BUFFER_SIZE) / Encoder.BUFFER_SIZE) * Encoder.BUFFER_SIZE;
|
|
4431
|
+
const grown = new Uint8Array(size);
|
|
4432
|
+
grown.set(buffer.subarray(0, offset));
|
|
4433
|
+
if (buffer === this.sharedBuffer) {
|
|
4434
|
+
this.sharedBuffer = grown;
|
|
4435
|
+
}
|
|
4436
|
+
return grown;
|
|
4437
|
+
}
|
|
4412
4438
|
encodeView(view, sharedOffset, it, bytes = this.sharedBuffer) {
|
|
4413
4439
|
const viewOffset = it.offset;
|
|
4414
4440
|
//
|
|
@@ -4447,6 +4473,9 @@ class Encoder {
|
|
|
4447
4473
|
const ctor = ref.constructor;
|
|
4448
4474
|
const encoder = ctor[$encoder];
|
|
4449
4475
|
const metadata = ctor[Symbol.metadata];
|
|
4476
|
+
// These writes are unguarded and unrecoverable (view.changes is cleared
|
|
4477
|
+
// below), so unlike encode() they can't re-encode on overflow — grow ahead.
|
|
4478
|
+
bytes = this.ensureCapacity(bytes, it.offset);
|
|
4450
4479
|
bytes[it.offset++] = SWITCH_TO_STRUCTURE & 255;
|
|
4451
4480
|
encode.number(bytes, ref[$refId], it);
|
|
4452
4481
|
for (let i = 0, numChanges = keys.length; i < numChanges; i++) {
|
|
@@ -4466,8 +4495,10 @@ class Encoder {
|
|
|
4466
4495
|
// clear "view" changes after encoding
|
|
4467
4496
|
view.changes.clear();
|
|
4468
4497
|
view.changesOutOfOrder = false;
|
|
4469
|
-
//
|
|
4470
|
-
|
|
4498
|
+
// encode() may reallocate the buffer — keep its return, not the stale `bytes`.
|
|
4499
|
+
// Anchor the re-encode at the current offset (default), not `viewOffset`: a
|
|
4500
|
+
// resize must not clobber the view.changes already written at [viewOffset, ).
|
|
4501
|
+
bytes = this.encode(it, view, bytes, "filteredChanges", false);
|
|
4471
4502
|
return concatBytes(bytes.subarray(0, sharedOffset), bytes.subarray(viewOffset, it.offset));
|
|
4472
4503
|
}
|
|
4473
4504
|
/**
|
|
@@ -5578,7 +5609,7 @@ class StateView {
|
|
|
5578
5609
|
* List of ChangeTree's that are invisible to this view
|
|
5579
5610
|
*/
|
|
5580
5611
|
invisible = new WeakSet();
|
|
5581
|
-
tags; //
|
|
5612
|
+
tags; // bitmask of tags used to add each ChangeTree
|
|
5582
5613
|
/**
|
|
5583
5614
|
* Manual "ADD" operations for changes per ChangeTree, specific to this view.
|
|
5584
5615
|
* (This is used to force encoding a property, even if it was not changed)
|
|
@@ -5666,9 +5697,16 @@ class StateView {
|
|
|
5666
5697
|
changeTree.forEachChild((change, index) => {
|
|
5667
5698
|
// Do not ADD children that don't have the same tag
|
|
5668
5699
|
if (metadata &&
|
|
5669
|
-
metadata[index].tag !== undefined
|
|
5670
|
-
metadata[index].tag
|
|
5671
|
-
|
|
5700
|
+
metadata[index].tag !== undefined) {
|
|
5701
|
+
const fieldTag = metadata[index].tag;
|
|
5702
|
+
// DEFAULT_VIEW_TAG fields are visible to all clients.
|
|
5703
|
+
// Custom-tagged fields are only visible when bits overlap,
|
|
5704
|
+
// and never to default-tag clients.
|
|
5705
|
+
const tagMatch = fieldTag === DEFAULT_VIEW_TAG ||
|
|
5706
|
+
(tag !== DEFAULT_VIEW_TAG && (fieldTag & tag) !== 0);
|
|
5707
|
+
if (!tagMatch) {
|
|
5708
|
+
return;
|
|
5709
|
+
}
|
|
5672
5710
|
}
|
|
5673
5711
|
if (this.add(change.ref, tag, false)) {
|
|
5674
5712
|
isChildAdded = true;
|
|
@@ -5679,15 +5717,9 @@ class StateView {
|
|
|
5679
5717
|
if (!this.tags) {
|
|
5680
5718
|
this.tags = new WeakMap();
|
|
5681
5719
|
}
|
|
5682
|
-
|
|
5683
|
-
|
|
5684
|
-
|
|
5685
|
-
this.tags.set(changeTree, tags);
|
|
5686
|
-
}
|
|
5687
|
-
else {
|
|
5688
|
-
tags = this.tags.get(changeTree);
|
|
5689
|
-
}
|
|
5690
|
-
tags.add(tag);
|
|
5720
|
+
// Add tag bits into the bitmask stored for this ChangeTree.
|
|
5721
|
+
const currentMask = this.tags.get(changeTree) ?? 0;
|
|
5722
|
+
this.tags.set(changeTree, currentMask | tag);
|
|
5691
5723
|
// Ref: add tagged properties
|
|
5692
5724
|
metadata?.[$fieldIndexesByViewTag]?.[tag]?.forEach((index) => {
|
|
5693
5725
|
if (changeTree.getChange(index) !== OPERATION.DELETE) {
|
|
@@ -5711,7 +5743,7 @@ class StateView {
|
|
|
5711
5743
|
if (op !== OPERATION.DELETE &&
|
|
5712
5744
|
(isInvisible || // if "invisible", include all
|
|
5713
5745
|
tagAtIndex === undefined || // "all change" with no tag
|
|
5714
|
-
tagAtIndex === tag // tagged property
|
|
5746
|
+
(tagAtIndex === DEFAULT_VIEW_TAG || (tag !== DEFAULT_VIEW_TAG && (tagAtIndex & tag) !== 0)) // tagged property
|
|
5715
5747
|
)) {
|
|
5716
5748
|
changes[index] = op;
|
|
5717
5749
|
isChildAdded = true; // FIXME: assign only once
|
|
@@ -5737,18 +5769,16 @@ class StateView {
|
|
|
5737
5769
|
// add parent's tag properties
|
|
5738
5770
|
if (changeTree.getChange(parentIndex) !== OPERATION.DELETE) {
|
|
5739
5771
|
const changes = this.touchChanges(changeTree.ref[$refId]);
|
|
5740
|
-
|
|
5741
|
-
|
|
5742
|
-
|
|
5743
|
-
|
|
5744
|
-
|
|
5745
|
-
|
|
5746
|
-
|
|
5747
|
-
|
|
5748
|
-
|
|
5749
|
-
tags = this.tags.get(changeTree);
|
|
5772
|
+
// Only accumulate positive (custom) tags in the bitmask.
|
|
5773
|
+
// DEFAULT_VIEW_TAG = -1 has all bits set and must not be OR'd in,
|
|
5774
|
+
// as it would make every custom-tagged field appear visible.
|
|
5775
|
+
if (tag !== DEFAULT_VIEW_TAG) {
|
|
5776
|
+
if (!this.tags) {
|
|
5777
|
+
this.tags = new WeakMap();
|
|
5778
|
+
}
|
|
5779
|
+
const currentMask = this.tags.has(changeTree) ? this.tags.get(changeTree) : 0;
|
|
5780
|
+
this.tags.set(changeTree, currentMask | tag);
|
|
5750
5781
|
}
|
|
5751
|
-
tags.add(tag);
|
|
5752
5782
|
changes[parentIndex] = OPERATION.ADD;
|
|
5753
5783
|
}
|
|
5754
5784
|
}
|
|
@@ -5818,18 +5848,19 @@ class StateView {
|
|
|
5818
5848
|
}
|
|
5819
5849
|
// remove tag
|
|
5820
5850
|
if (this.tags && this.tags.has(changeTree)) {
|
|
5821
|
-
const tags = this.tags.get(changeTree);
|
|
5822
5851
|
if (tag === undefined) {
|
|
5823
5852
|
// delete all tags
|
|
5824
5853
|
this.tags.delete(changeTree);
|
|
5825
5854
|
}
|
|
5826
5855
|
else {
|
|
5827
|
-
//
|
|
5828
|
-
tags.
|
|
5829
|
-
|
|
5830
|
-
if (tags.size === 0) {
|
|
5856
|
+
// clear the tag's bits from the bitmask
|
|
5857
|
+
const newMask = this.tags.get(changeTree) & ~tag;
|
|
5858
|
+
if (newMask === 0) {
|
|
5831
5859
|
this.tags.delete(changeTree);
|
|
5832
5860
|
}
|
|
5861
|
+
else {
|
|
5862
|
+
this.tags.set(changeTree, newMask);
|
|
5863
|
+
}
|
|
5833
5864
|
}
|
|
5834
5865
|
}
|
|
5835
5866
|
return this;
|
|
@@ -5839,7 +5870,7 @@ class StateView {
|
|
|
5839
5870
|
}
|
|
5840
5871
|
hasTag(ob, tag = DEFAULT_VIEW_TAG) {
|
|
5841
5872
|
const tags = this.tags?.get(ob[$changes]);
|
|
5842
|
-
return tags
|
|
5873
|
+
return tags != null && (tags & tag) !== 0;
|
|
5843
5874
|
}
|
|
5844
5875
|
clear() {
|
|
5845
5876
|
if (!this.iterable) {
|