@colyseus/schema 4.0.21 → 4.0.23
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/decoder/strategy/Callbacks.d.ts +5 -5
- package/build/encoder/Encoder.d.ts +17 -0
- package/build/encoder/StateView.d.ts +26 -0
- package/build/index.cjs +107 -26
- package/build/index.cjs.map +1 -1
- package/build/index.js +107 -26
- package/build/index.mjs +107 -26
- package/build/index.mjs.map +1 -1
- package/package.json +1 -1
- package/src/decoder/strategy/Callbacks.ts +15 -13
- package/src/encoder/Encoder.ts +67 -2
- package/src/encoder/StateView.ts +47 -24
package/build/index.js
CHANGED
|
@@ -4393,8 +4393,25 @@
|
|
|
4393
4393
|
}
|
|
4394
4394
|
encodeView(view, sharedOffset, it, bytes = this.sharedBuffer) {
|
|
4395
4395
|
const viewOffset = it.offset;
|
|
4396
|
-
//
|
|
4397
|
-
|
|
4396
|
+
//
|
|
4397
|
+
// Iterate `view.changes` in topological order so a refId is never
|
|
4398
|
+
// SWITCH_TO_STRUCTURE'd before an earlier op has introduced it on
|
|
4399
|
+
// the decoder. Map insertion order alone isn't sufficient: a
|
|
4400
|
+
// sequence like view.remove(child) → view.add(child) on a child
|
|
4401
|
+
// whose ancestor wasn't yet visible can put the child entry into
|
|
4402
|
+
// the Map before its newly-visible ancestor.
|
|
4403
|
+
//
|
|
4404
|
+
// Hot-path optimization: `view.add` preserves topo order by
|
|
4405
|
+
// construction (addParentOf walks deepest-ancestor-first before
|
|
4406
|
+
// touching the obj's own entry). Only `view.remove` can leave the
|
|
4407
|
+
// Map dirty. `StateView.changesOutOfOrder` tracks this so most
|
|
4408
|
+
// encodes can iterate `view.changes` directly, paying nothing.
|
|
4409
|
+
//
|
|
4410
|
+
const orderedRefIds = view.changesOutOfOrder
|
|
4411
|
+
? this.topoOrderViewChanges(view)
|
|
4412
|
+
: view.changes.keys();
|
|
4413
|
+
for (const refId of orderedRefIds) {
|
|
4414
|
+
const changes = view.changes.get(refId);
|
|
4398
4415
|
const changeTree = this.root.changeTrees[refId];
|
|
4399
4416
|
if (changeTree === undefined) {
|
|
4400
4417
|
// detached instance, remove from view and skip.
|
|
@@ -4430,10 +4447,53 @@
|
|
|
4430
4447
|
//
|
|
4431
4448
|
// clear "view" changes after encoding
|
|
4432
4449
|
view.changes.clear();
|
|
4450
|
+
view.changesOutOfOrder = false;
|
|
4433
4451
|
// try to encode "filtered" changes
|
|
4434
4452
|
this.encode(it, view, bytes, "filteredChanges", false, viewOffset);
|
|
4435
4453
|
return concatBytes(bytes.subarray(0, sharedOffset), bytes.subarray(viewOffset, it.offset));
|
|
4436
4454
|
}
|
|
4455
|
+
/**
|
|
4456
|
+
* Produce a topological ordering of `view.changes` keys so each refId
|
|
4457
|
+
* is preceded by any ancestor that's also in the same view's changeset.
|
|
4458
|
+
*
|
|
4459
|
+
* The wire stream uses SWITCH_TO_STRUCTURE pointers; if a child is
|
|
4460
|
+
* encoded before any earlier op has introduced its refId on the
|
|
4461
|
+
* decoder, decode fails with "refId not found". An entry's refId can
|
|
4462
|
+
* only be introduced by an ADD on one of its ancestors — so any
|
|
4463
|
+
* ancestor that itself appears in this view's pending changes must
|
|
4464
|
+
* be encoded first.
|
|
4465
|
+
*
|
|
4466
|
+
* Implementation: DFS post-order over the parent chain. The `visited`
|
|
4467
|
+
* Set guards against duplicates; cycles are not expected in a
|
|
4468
|
+
* well-formed parent chain but the visited check is a cheap safety
|
|
4469
|
+
* net. Cost is O(n × d) for n entries with parent-chain depth d.
|
|
4470
|
+
*/
|
|
4471
|
+
topoOrderViewChanges(view) {
|
|
4472
|
+
const result = [];
|
|
4473
|
+
const visited = new Set();
|
|
4474
|
+
const visit = (refId) => {
|
|
4475
|
+
if (visited.has(refId)) {
|
|
4476
|
+
return;
|
|
4477
|
+
}
|
|
4478
|
+
visited.add(refId);
|
|
4479
|
+
const changeTree = this.root.changeTrees[refId];
|
|
4480
|
+
if (changeTree !== undefined) {
|
|
4481
|
+
let chain = changeTree.parentChain;
|
|
4482
|
+
while (chain) {
|
|
4483
|
+
const parentRefId = chain.ref[$refId];
|
|
4484
|
+
if (parentRefId !== undefined && view.changes.has(parentRefId)) {
|
|
4485
|
+
visit(parentRefId);
|
|
4486
|
+
}
|
|
4487
|
+
chain = chain.next;
|
|
4488
|
+
}
|
|
4489
|
+
}
|
|
4490
|
+
result.push(refId);
|
|
4491
|
+
};
|
|
4492
|
+
for (const refId of view.changes.keys()) {
|
|
4493
|
+
visit(refId);
|
|
4494
|
+
}
|
|
4495
|
+
return result;
|
|
4496
|
+
}
|
|
4437
4497
|
discardChanges() {
|
|
4438
4498
|
// discard shared changes
|
|
4439
4499
|
let current = this.root.changes.next;
|
|
@@ -5478,6 +5538,7 @@
|
|
|
5478
5538
|
else if ('decoder' in roomOrDecoder.serializer) {
|
|
5479
5539
|
return getDecoderStateCallbacks(roomOrDecoder.serializer.decoder);
|
|
5480
5540
|
}
|
|
5541
|
+
throw new Error('Invalid room or decoder');
|
|
5481
5542
|
},
|
|
5482
5543
|
getRawChanges(decoder, callback) {
|
|
5483
5544
|
return getRawChangesCallback(decoder, callback);
|
|
@@ -5505,12 +5566,45 @@
|
|
|
5505
5566
|
* (This is used to force encoding a property, even if it was not changed)
|
|
5506
5567
|
*/
|
|
5507
5568
|
changes = new Map();
|
|
5569
|
+
/**
|
|
5570
|
+
* Set when an operation may have left `changes` out of topological
|
|
5571
|
+
* order (a parent that needs to be encoded before its descendants is
|
|
5572
|
+
* positioned after them in the Map). `Encoder.encodeView` consults
|
|
5573
|
+
* this flag and only runs the topo-ordering pass when it's true,
|
|
5574
|
+
* skipping the work in the common case where insertion order already
|
|
5575
|
+
* coincides with topo order.
|
|
5576
|
+
*
|
|
5577
|
+
* Only `remove()` can break the invariant: it writes entries that
|
|
5578
|
+
* bypass `addParentOf`'s deepest-ancestor-first ordering. Everything
|
|
5579
|
+
* else (including multi-parent re-adds) preserves order by
|
|
5580
|
+
* construction. Reset to false at the end of each encodeView pass
|
|
5581
|
+
* (when `changes` is cleared).
|
|
5582
|
+
*/
|
|
5583
|
+
changesOutOfOrder = false;
|
|
5508
5584
|
constructor(iterable = false) {
|
|
5509
5585
|
this.iterable = iterable;
|
|
5510
5586
|
if (iterable) {
|
|
5511
5587
|
this.items = [];
|
|
5512
5588
|
}
|
|
5513
5589
|
}
|
|
5590
|
+
/**
|
|
5591
|
+
* Get the IndexedOperations entry for `refId`, creating one if missing.
|
|
5592
|
+
*
|
|
5593
|
+
* Map insertion order alone doesn't guarantee parent-before-child
|
|
5594
|
+
* iteration in all cases (a `view.remove()` followed by `view.add()`
|
|
5595
|
+
* can put a child entry into the Map before its newly-visible
|
|
5596
|
+
* ancestor). The wire-order invariant (parent SWITCH_TO_STRUCTURE
|
|
5597
|
+
* before any of its children's) is enforced at encode time by
|
|
5598
|
+
* `Encoder.encodeView` via a topological pass over `view.changes`.
|
|
5599
|
+
*/
|
|
5600
|
+
touchChanges(refId) {
|
|
5601
|
+
let entry = this.changes.get(refId);
|
|
5602
|
+
if (entry === undefined) {
|
|
5603
|
+
entry = {};
|
|
5604
|
+
this.changes.set(refId, entry);
|
|
5605
|
+
}
|
|
5606
|
+
return entry;
|
|
5607
|
+
}
|
|
5514
5608
|
// TODO: allow to set multiple tags at once
|
|
5515
5609
|
add(obj, tag = DEFAULT_VIEW_TAG, checkIncludeParent = true) {
|
|
5516
5610
|
const changeTree = obj?.[$changes];
|
|
@@ -5544,12 +5638,8 @@
|
|
|
5544
5638
|
if (checkIncludeParent && parentChangeTree) {
|
|
5545
5639
|
this.addParentOf(changeTree, tag);
|
|
5546
5640
|
}
|
|
5547
|
-
|
|
5548
|
-
|
|
5549
|
-
changes = {};
|
|
5550
|
-
// FIXME / OPTIMIZE: do not add if no changes are needed
|
|
5551
|
-
this.changes.set(obj[$refId], changes);
|
|
5552
|
-
}
|
|
5641
|
+
// FIXME / OPTIMIZE: do not add if no changes are needed
|
|
5642
|
+
const changes = this.touchChanges(obj[$refId]);
|
|
5553
5643
|
let isChildAdded = false;
|
|
5554
5644
|
//
|
|
5555
5645
|
// Add children of this ChangeTree first.
|
|
@@ -5628,11 +5718,7 @@
|
|
|
5628
5718
|
}
|
|
5629
5719
|
// add parent's tag properties
|
|
5630
5720
|
if (changeTree.getChange(parentIndex) !== exports.OPERATION.DELETE) {
|
|
5631
|
-
|
|
5632
|
-
if (changes === undefined) {
|
|
5633
|
-
changes = {};
|
|
5634
|
-
this.changes.set(changeTree.ref[$refId], changes);
|
|
5635
|
-
}
|
|
5721
|
+
const changes = this.touchChanges(changeTree.ref[$refId]);
|
|
5636
5722
|
if (!this.tags) {
|
|
5637
5723
|
this.tags = new WeakMap();
|
|
5638
5724
|
}
|
|
@@ -5654,6 +5740,9 @@
|
|
|
5654
5740
|
console.warn("StateView#remove(), invalid object:", obj);
|
|
5655
5741
|
return this;
|
|
5656
5742
|
}
|
|
5743
|
+
// remove() bypasses addParentOf's ordering guarantee — flag the
|
|
5744
|
+
// changeset as potentially out of topological order.
|
|
5745
|
+
this.changesOutOfOrder = true;
|
|
5657
5746
|
this.visible.delete(changeTree);
|
|
5658
5747
|
// remove from iterable list
|
|
5659
5748
|
if (this.iterable &&
|
|
@@ -5664,22 +5753,12 @@
|
|
|
5664
5753
|
const ref = changeTree.ref;
|
|
5665
5754
|
const metadata = ref.constructor[Symbol.metadata]; // ArraySchema/MapSchema do not have metadata
|
|
5666
5755
|
const refId = ref[$refId];
|
|
5667
|
-
let changes = this.changes.get(refId);
|
|
5668
|
-
if (changes === undefined) {
|
|
5669
|
-
changes = {};
|
|
5670
|
-
this.changes.set(refId, changes);
|
|
5671
|
-
}
|
|
5672
5756
|
if (tag === DEFAULT_VIEW_TAG) {
|
|
5673
5757
|
// parent is collection (Map/Array)
|
|
5674
5758
|
const parent = changeTree.parent;
|
|
5675
5759
|
if (parent && !Metadata.isValidInstance(parent) && changeTree.isFiltered) {
|
|
5676
|
-
const
|
|
5677
|
-
|
|
5678
|
-
if (changes === undefined) {
|
|
5679
|
-
changes = {};
|
|
5680
|
-
this.changes.set(parentRefId, changes);
|
|
5681
|
-
}
|
|
5682
|
-
else if (changes[changeTree.parentIndex] === exports.OPERATION.ADD) {
|
|
5760
|
+
const parentChanges = this.touchChanges(parent[$refId]);
|
|
5761
|
+
if (parentChanges[changeTree.parentIndex] === exports.OPERATION.ADD) {
|
|
5683
5762
|
//
|
|
5684
5763
|
// SAME PATCH ADD + REMOVE:
|
|
5685
5764
|
// The 'changes' of deleted structure should be ignored.
|
|
@@ -5687,12 +5766,13 @@
|
|
|
5687
5766
|
this.changes.delete(refId);
|
|
5688
5767
|
}
|
|
5689
5768
|
// DELETE / DELETE BY REF ID
|
|
5690
|
-
|
|
5769
|
+
parentChanges[changeTree.parentIndex] = exports.OPERATION.DELETE;
|
|
5691
5770
|
// Remove child schema from visible set
|
|
5692
5771
|
this._recursiveDeleteVisibleChangeTree(changeTree);
|
|
5693
5772
|
}
|
|
5694
5773
|
else {
|
|
5695
5774
|
// delete all "tagged" properties.
|
|
5775
|
+
const changes = this.touchChanges(refId);
|
|
5696
5776
|
metadata?.[$viewFieldIndexes]?.forEach((index) => {
|
|
5697
5777
|
changes[index] = exports.OPERATION.DELETE;
|
|
5698
5778
|
// Remove child structures of @view() fields from visible set.
|
|
@@ -5707,6 +5787,7 @@
|
|
|
5707
5787
|
}
|
|
5708
5788
|
else {
|
|
5709
5789
|
// delete only tagged properties
|
|
5790
|
+
const changes = this.touchChanges(refId);
|
|
5710
5791
|
metadata?.[$fieldIndexesByViewTag][tag].forEach((index) => {
|
|
5711
5792
|
changes[index] = exports.OPERATION.DELETE;
|
|
5712
5793
|
// Remove child structures from visible set
|
package/build/index.mjs
CHANGED
|
@@ -4387,8 +4387,25 @@ class Encoder {
|
|
|
4387
4387
|
}
|
|
4388
4388
|
encodeView(view, sharedOffset, it, bytes = this.sharedBuffer) {
|
|
4389
4389
|
const viewOffset = it.offset;
|
|
4390
|
-
//
|
|
4391
|
-
|
|
4390
|
+
//
|
|
4391
|
+
// Iterate `view.changes` in topological order so a refId is never
|
|
4392
|
+
// SWITCH_TO_STRUCTURE'd before an earlier op has introduced it on
|
|
4393
|
+
// the decoder. Map insertion order alone isn't sufficient: a
|
|
4394
|
+
// sequence like view.remove(child) → view.add(child) on a child
|
|
4395
|
+
// whose ancestor wasn't yet visible can put the child entry into
|
|
4396
|
+
// the Map before its newly-visible ancestor.
|
|
4397
|
+
//
|
|
4398
|
+
// Hot-path optimization: `view.add` preserves topo order by
|
|
4399
|
+
// construction (addParentOf walks deepest-ancestor-first before
|
|
4400
|
+
// touching the obj's own entry). Only `view.remove` can leave the
|
|
4401
|
+
// Map dirty. `StateView.changesOutOfOrder` tracks this so most
|
|
4402
|
+
// encodes can iterate `view.changes` directly, paying nothing.
|
|
4403
|
+
//
|
|
4404
|
+
const orderedRefIds = view.changesOutOfOrder
|
|
4405
|
+
? this.topoOrderViewChanges(view)
|
|
4406
|
+
: view.changes.keys();
|
|
4407
|
+
for (const refId of orderedRefIds) {
|
|
4408
|
+
const changes = view.changes.get(refId);
|
|
4392
4409
|
const changeTree = this.root.changeTrees[refId];
|
|
4393
4410
|
if (changeTree === undefined) {
|
|
4394
4411
|
// detached instance, remove from view and skip.
|
|
@@ -4424,10 +4441,53 @@ class Encoder {
|
|
|
4424
4441
|
//
|
|
4425
4442
|
// clear "view" changes after encoding
|
|
4426
4443
|
view.changes.clear();
|
|
4444
|
+
view.changesOutOfOrder = false;
|
|
4427
4445
|
// try to encode "filtered" changes
|
|
4428
4446
|
this.encode(it, view, bytes, "filteredChanges", false, viewOffset);
|
|
4429
4447
|
return concatBytes(bytes.subarray(0, sharedOffset), bytes.subarray(viewOffset, it.offset));
|
|
4430
4448
|
}
|
|
4449
|
+
/**
|
|
4450
|
+
* Produce a topological ordering of `view.changes` keys so each refId
|
|
4451
|
+
* is preceded by any ancestor that's also in the same view's changeset.
|
|
4452
|
+
*
|
|
4453
|
+
* The wire stream uses SWITCH_TO_STRUCTURE pointers; if a child is
|
|
4454
|
+
* encoded before any earlier op has introduced its refId on the
|
|
4455
|
+
* decoder, decode fails with "refId not found". An entry's refId can
|
|
4456
|
+
* only be introduced by an ADD on one of its ancestors — so any
|
|
4457
|
+
* ancestor that itself appears in this view's pending changes must
|
|
4458
|
+
* be encoded first.
|
|
4459
|
+
*
|
|
4460
|
+
* Implementation: DFS post-order over the parent chain. The `visited`
|
|
4461
|
+
* Set guards against duplicates; cycles are not expected in a
|
|
4462
|
+
* well-formed parent chain but the visited check is a cheap safety
|
|
4463
|
+
* net. Cost is O(n × d) for n entries with parent-chain depth d.
|
|
4464
|
+
*/
|
|
4465
|
+
topoOrderViewChanges(view) {
|
|
4466
|
+
const result = [];
|
|
4467
|
+
const visited = new Set();
|
|
4468
|
+
const visit = (refId) => {
|
|
4469
|
+
if (visited.has(refId)) {
|
|
4470
|
+
return;
|
|
4471
|
+
}
|
|
4472
|
+
visited.add(refId);
|
|
4473
|
+
const changeTree = this.root.changeTrees[refId];
|
|
4474
|
+
if (changeTree !== undefined) {
|
|
4475
|
+
let chain = changeTree.parentChain;
|
|
4476
|
+
while (chain) {
|
|
4477
|
+
const parentRefId = chain.ref[$refId];
|
|
4478
|
+
if (parentRefId !== undefined && view.changes.has(parentRefId)) {
|
|
4479
|
+
visit(parentRefId);
|
|
4480
|
+
}
|
|
4481
|
+
chain = chain.next;
|
|
4482
|
+
}
|
|
4483
|
+
}
|
|
4484
|
+
result.push(refId);
|
|
4485
|
+
};
|
|
4486
|
+
for (const refId of view.changes.keys()) {
|
|
4487
|
+
visit(refId);
|
|
4488
|
+
}
|
|
4489
|
+
return result;
|
|
4490
|
+
}
|
|
4431
4491
|
discardChanges() {
|
|
4432
4492
|
// discard shared changes
|
|
4433
4493
|
let current = this.root.changes.next;
|
|
@@ -5472,6 +5532,7 @@ const Callbacks = {
|
|
|
5472
5532
|
else if ('decoder' in roomOrDecoder.serializer) {
|
|
5473
5533
|
return getDecoderStateCallbacks(roomOrDecoder.serializer.decoder);
|
|
5474
5534
|
}
|
|
5535
|
+
throw new Error('Invalid room or decoder');
|
|
5475
5536
|
},
|
|
5476
5537
|
getRawChanges(decoder, callback) {
|
|
5477
5538
|
return getRawChangesCallback(decoder, callback);
|
|
@@ -5499,12 +5560,45 @@ class StateView {
|
|
|
5499
5560
|
* (This is used to force encoding a property, even if it was not changed)
|
|
5500
5561
|
*/
|
|
5501
5562
|
changes = new Map();
|
|
5563
|
+
/**
|
|
5564
|
+
* Set when an operation may have left `changes` out of topological
|
|
5565
|
+
* order (a parent that needs to be encoded before its descendants is
|
|
5566
|
+
* positioned after them in the Map). `Encoder.encodeView` consults
|
|
5567
|
+
* this flag and only runs the topo-ordering pass when it's true,
|
|
5568
|
+
* skipping the work in the common case where insertion order already
|
|
5569
|
+
* coincides with topo order.
|
|
5570
|
+
*
|
|
5571
|
+
* Only `remove()` can break the invariant: it writes entries that
|
|
5572
|
+
* bypass `addParentOf`'s deepest-ancestor-first ordering. Everything
|
|
5573
|
+
* else (including multi-parent re-adds) preserves order by
|
|
5574
|
+
* construction. Reset to false at the end of each encodeView pass
|
|
5575
|
+
* (when `changes` is cleared).
|
|
5576
|
+
*/
|
|
5577
|
+
changesOutOfOrder = false;
|
|
5502
5578
|
constructor(iterable = false) {
|
|
5503
5579
|
this.iterable = iterable;
|
|
5504
5580
|
if (iterable) {
|
|
5505
5581
|
this.items = [];
|
|
5506
5582
|
}
|
|
5507
5583
|
}
|
|
5584
|
+
/**
|
|
5585
|
+
* Get the IndexedOperations entry for `refId`, creating one if missing.
|
|
5586
|
+
*
|
|
5587
|
+
* Map insertion order alone doesn't guarantee parent-before-child
|
|
5588
|
+
* iteration in all cases (a `view.remove()` followed by `view.add()`
|
|
5589
|
+
* can put a child entry into the Map before its newly-visible
|
|
5590
|
+
* ancestor). The wire-order invariant (parent SWITCH_TO_STRUCTURE
|
|
5591
|
+
* before any of its children's) is enforced at encode time by
|
|
5592
|
+
* `Encoder.encodeView` via a topological pass over `view.changes`.
|
|
5593
|
+
*/
|
|
5594
|
+
touchChanges(refId) {
|
|
5595
|
+
let entry = this.changes.get(refId);
|
|
5596
|
+
if (entry === undefined) {
|
|
5597
|
+
entry = {};
|
|
5598
|
+
this.changes.set(refId, entry);
|
|
5599
|
+
}
|
|
5600
|
+
return entry;
|
|
5601
|
+
}
|
|
5508
5602
|
// TODO: allow to set multiple tags at once
|
|
5509
5603
|
add(obj, tag = DEFAULT_VIEW_TAG, checkIncludeParent = true) {
|
|
5510
5604
|
const changeTree = obj?.[$changes];
|
|
@@ -5538,12 +5632,8 @@ class StateView {
|
|
|
5538
5632
|
if (checkIncludeParent && parentChangeTree) {
|
|
5539
5633
|
this.addParentOf(changeTree, tag);
|
|
5540
5634
|
}
|
|
5541
|
-
|
|
5542
|
-
|
|
5543
|
-
changes = {};
|
|
5544
|
-
// FIXME / OPTIMIZE: do not add if no changes are needed
|
|
5545
|
-
this.changes.set(obj[$refId], changes);
|
|
5546
|
-
}
|
|
5635
|
+
// FIXME / OPTIMIZE: do not add if no changes are needed
|
|
5636
|
+
const changes = this.touchChanges(obj[$refId]);
|
|
5547
5637
|
let isChildAdded = false;
|
|
5548
5638
|
//
|
|
5549
5639
|
// Add children of this ChangeTree first.
|
|
@@ -5622,11 +5712,7 @@ class StateView {
|
|
|
5622
5712
|
}
|
|
5623
5713
|
// add parent's tag properties
|
|
5624
5714
|
if (changeTree.getChange(parentIndex) !== OPERATION.DELETE) {
|
|
5625
|
-
|
|
5626
|
-
if (changes === undefined) {
|
|
5627
|
-
changes = {};
|
|
5628
|
-
this.changes.set(changeTree.ref[$refId], changes);
|
|
5629
|
-
}
|
|
5715
|
+
const changes = this.touchChanges(changeTree.ref[$refId]);
|
|
5630
5716
|
if (!this.tags) {
|
|
5631
5717
|
this.tags = new WeakMap();
|
|
5632
5718
|
}
|
|
@@ -5648,6 +5734,9 @@ class StateView {
|
|
|
5648
5734
|
console.warn("StateView#remove(), invalid object:", obj);
|
|
5649
5735
|
return this;
|
|
5650
5736
|
}
|
|
5737
|
+
// remove() bypasses addParentOf's ordering guarantee — flag the
|
|
5738
|
+
// changeset as potentially out of topological order.
|
|
5739
|
+
this.changesOutOfOrder = true;
|
|
5651
5740
|
this.visible.delete(changeTree);
|
|
5652
5741
|
// remove from iterable list
|
|
5653
5742
|
if (this.iterable &&
|
|
@@ -5658,22 +5747,12 @@ class StateView {
|
|
|
5658
5747
|
const ref = changeTree.ref;
|
|
5659
5748
|
const metadata = ref.constructor[Symbol.metadata]; // ArraySchema/MapSchema do not have metadata
|
|
5660
5749
|
const refId = ref[$refId];
|
|
5661
|
-
let changes = this.changes.get(refId);
|
|
5662
|
-
if (changes === undefined) {
|
|
5663
|
-
changes = {};
|
|
5664
|
-
this.changes.set(refId, changes);
|
|
5665
|
-
}
|
|
5666
5750
|
if (tag === DEFAULT_VIEW_TAG) {
|
|
5667
5751
|
// parent is collection (Map/Array)
|
|
5668
5752
|
const parent = changeTree.parent;
|
|
5669
5753
|
if (parent && !Metadata.isValidInstance(parent) && changeTree.isFiltered) {
|
|
5670
|
-
const
|
|
5671
|
-
|
|
5672
|
-
if (changes === undefined) {
|
|
5673
|
-
changes = {};
|
|
5674
|
-
this.changes.set(parentRefId, changes);
|
|
5675
|
-
}
|
|
5676
|
-
else if (changes[changeTree.parentIndex] === OPERATION.ADD) {
|
|
5754
|
+
const parentChanges = this.touchChanges(parent[$refId]);
|
|
5755
|
+
if (parentChanges[changeTree.parentIndex] === OPERATION.ADD) {
|
|
5677
5756
|
//
|
|
5678
5757
|
// SAME PATCH ADD + REMOVE:
|
|
5679
5758
|
// The 'changes' of deleted structure should be ignored.
|
|
@@ -5681,12 +5760,13 @@ class StateView {
|
|
|
5681
5760
|
this.changes.delete(refId);
|
|
5682
5761
|
}
|
|
5683
5762
|
// DELETE / DELETE BY REF ID
|
|
5684
|
-
|
|
5763
|
+
parentChanges[changeTree.parentIndex] = OPERATION.DELETE;
|
|
5685
5764
|
// Remove child schema from visible set
|
|
5686
5765
|
this._recursiveDeleteVisibleChangeTree(changeTree);
|
|
5687
5766
|
}
|
|
5688
5767
|
else {
|
|
5689
5768
|
// delete all "tagged" properties.
|
|
5769
|
+
const changes = this.touchChanges(refId);
|
|
5690
5770
|
metadata?.[$viewFieldIndexes]?.forEach((index) => {
|
|
5691
5771
|
changes[index] = OPERATION.DELETE;
|
|
5692
5772
|
// Remove child structures of @view() fields from visible set.
|
|
@@ -5701,6 +5781,7 @@ class StateView {
|
|
|
5701
5781
|
}
|
|
5702
5782
|
else {
|
|
5703
5783
|
// delete only tagged properties
|
|
5784
|
+
const changes = this.touchChanges(refId);
|
|
5704
5785
|
metadata?.[$fieldIndexesByViewTag][tag].forEach((index) => {
|
|
5705
5786
|
changes[index] = OPERATION.DELETE;
|
|
5706
5787
|
// Remove child structures from visible set
|