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