@colyseus/schema 4.0.20 → 4.0.22
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 +17 -0
- package/build/encoder/StateView.d.ts +26 -0
- package/build/index.cjs +108 -29
- package/build/index.cjs.map +1 -1
- package/build/index.js +108 -29
- package/build/index.mjs +108 -29
- package/build/index.mjs.map +1 -1
- package/package.json +2 -2
- package/src/encoder/ChangeTree.ts +2 -3
- package/src/encoder/Encoder.ts +67 -2
- package/src/encoder/StateView.ts +47 -24
package/build/index.js
CHANGED
|
@@ -1370,14 +1370,13 @@
|
|
|
1370
1370
|
|| this.root.types.parentFiltered[key]
|
|
1371
1371
|
|| fieldHasViewTag;
|
|
1372
1372
|
//
|
|
1373
|
-
// "isFiltered" may not be
|
|
1373
|
+
// "isFiltered" may not be immediately available during `change()` due to the instance not being attached to the root yet.
|
|
1374
1374
|
// when it's available, we need to enqueue the "changes" changeset into the "filteredChanges" changeset.
|
|
1375
1375
|
//
|
|
1376
1376
|
if (this.isFiltered) {
|
|
1377
1377
|
this.isVisibilitySharedWithParent = (parentChangeTree.isFiltered &&
|
|
1378
1378
|
typeof (refType) !== "string" &&
|
|
1379
|
-
!fieldHasViewTag
|
|
1380
|
-
parentIsCollection);
|
|
1379
|
+
!fieldHasViewTag);
|
|
1381
1380
|
if (!this.filteredChanges) {
|
|
1382
1381
|
this.filteredChanges = createChangeSet();
|
|
1383
1382
|
this.allFilteredChanges = createChangeSet();
|
|
@@ -4394,8 +4393,25 @@
|
|
|
4394
4393
|
}
|
|
4395
4394
|
encodeView(view, sharedOffset, it, bytes = this.sharedBuffer) {
|
|
4396
4395
|
const viewOffset = it.offset;
|
|
4397
|
-
//
|
|
4398
|
-
|
|
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);
|
|
4399
4415
|
const changeTree = this.root.changeTrees[refId];
|
|
4400
4416
|
if (changeTree === undefined) {
|
|
4401
4417
|
// detached instance, remove from view and skip.
|
|
@@ -4431,10 +4447,53 @@
|
|
|
4431
4447
|
//
|
|
4432
4448
|
// clear "view" changes after encoding
|
|
4433
4449
|
view.changes.clear();
|
|
4450
|
+
view.changesOutOfOrder = false;
|
|
4434
4451
|
// try to encode "filtered" changes
|
|
4435
4452
|
this.encode(it, view, bytes, "filteredChanges", false, viewOffset);
|
|
4436
4453
|
return concatBytes(bytes.subarray(0, sharedOffset), bytes.subarray(viewOffset, it.offset));
|
|
4437
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
|
+
}
|
|
4438
4497
|
discardChanges() {
|
|
4439
4498
|
// discard shared changes
|
|
4440
4499
|
let current = this.root.changes.next;
|
|
@@ -5506,12 +5565,45 @@
|
|
|
5506
5565
|
* (This is used to force encoding a property, even if it was not changed)
|
|
5507
5566
|
*/
|
|
5508
5567
|
changes = new Map();
|
|
5568
|
+
/**
|
|
5569
|
+
* Set when an operation may have left `changes` out of topological
|
|
5570
|
+
* order (a parent that needs to be encoded before its descendants is
|
|
5571
|
+
* positioned after them in the Map). `Encoder.encodeView` consults
|
|
5572
|
+
* this flag and only runs the topo-ordering pass when it's true,
|
|
5573
|
+
* skipping the work in the common case where insertion order already
|
|
5574
|
+
* coincides with topo order.
|
|
5575
|
+
*
|
|
5576
|
+
* Only `remove()` can break the invariant: it writes entries that
|
|
5577
|
+
* bypass `addParentOf`'s deepest-ancestor-first ordering. Everything
|
|
5578
|
+
* else (including multi-parent re-adds) preserves order by
|
|
5579
|
+
* construction. Reset to false at the end of each encodeView pass
|
|
5580
|
+
* (when `changes` is cleared).
|
|
5581
|
+
*/
|
|
5582
|
+
changesOutOfOrder = false;
|
|
5509
5583
|
constructor(iterable = false) {
|
|
5510
5584
|
this.iterable = iterable;
|
|
5511
5585
|
if (iterable) {
|
|
5512
5586
|
this.items = [];
|
|
5513
5587
|
}
|
|
5514
5588
|
}
|
|
5589
|
+
/**
|
|
5590
|
+
* Get the IndexedOperations entry for `refId`, creating one if missing.
|
|
5591
|
+
*
|
|
5592
|
+
* Map insertion order alone doesn't guarantee parent-before-child
|
|
5593
|
+
* iteration in all cases (a `view.remove()` followed by `view.add()`
|
|
5594
|
+
* can put a child entry into the Map before its newly-visible
|
|
5595
|
+
* ancestor). The wire-order invariant (parent SWITCH_TO_STRUCTURE
|
|
5596
|
+
* before any of its children's) is enforced at encode time by
|
|
5597
|
+
* `Encoder.encodeView` via a topological pass over `view.changes`.
|
|
5598
|
+
*/
|
|
5599
|
+
touchChanges(refId) {
|
|
5600
|
+
let entry = this.changes.get(refId);
|
|
5601
|
+
if (entry === undefined) {
|
|
5602
|
+
entry = {};
|
|
5603
|
+
this.changes.set(refId, entry);
|
|
5604
|
+
}
|
|
5605
|
+
return entry;
|
|
5606
|
+
}
|
|
5515
5607
|
// TODO: allow to set multiple tags at once
|
|
5516
5608
|
add(obj, tag = DEFAULT_VIEW_TAG, checkIncludeParent = true) {
|
|
5517
5609
|
const changeTree = obj?.[$changes];
|
|
@@ -5545,12 +5637,8 @@
|
|
|
5545
5637
|
if (checkIncludeParent && parentChangeTree) {
|
|
5546
5638
|
this.addParentOf(changeTree, tag);
|
|
5547
5639
|
}
|
|
5548
|
-
|
|
5549
|
-
|
|
5550
|
-
changes = {};
|
|
5551
|
-
// FIXME / OPTIMIZE: do not add if no changes are needed
|
|
5552
|
-
this.changes.set(obj[$refId], changes);
|
|
5553
|
-
}
|
|
5640
|
+
// FIXME / OPTIMIZE: do not add if no changes are needed
|
|
5641
|
+
const changes = this.touchChanges(obj[$refId]);
|
|
5554
5642
|
let isChildAdded = false;
|
|
5555
5643
|
//
|
|
5556
5644
|
// Add children of this ChangeTree first.
|
|
@@ -5629,11 +5717,7 @@
|
|
|
5629
5717
|
}
|
|
5630
5718
|
// add parent's tag properties
|
|
5631
5719
|
if (changeTree.getChange(parentIndex) !== exports.OPERATION.DELETE) {
|
|
5632
|
-
|
|
5633
|
-
if (changes === undefined) {
|
|
5634
|
-
changes = {};
|
|
5635
|
-
this.changes.set(changeTree.ref[$refId], changes);
|
|
5636
|
-
}
|
|
5720
|
+
const changes = this.touchChanges(changeTree.ref[$refId]);
|
|
5637
5721
|
if (!this.tags) {
|
|
5638
5722
|
this.tags = new WeakMap();
|
|
5639
5723
|
}
|
|
@@ -5655,6 +5739,9 @@
|
|
|
5655
5739
|
console.warn("StateView#remove(), invalid object:", obj);
|
|
5656
5740
|
return this;
|
|
5657
5741
|
}
|
|
5742
|
+
// remove() bypasses addParentOf's ordering guarantee — flag the
|
|
5743
|
+
// changeset as potentially out of topological order.
|
|
5744
|
+
this.changesOutOfOrder = true;
|
|
5658
5745
|
this.visible.delete(changeTree);
|
|
5659
5746
|
// remove from iterable list
|
|
5660
5747
|
if (this.iterable &&
|
|
@@ -5665,22 +5752,12 @@
|
|
|
5665
5752
|
const ref = changeTree.ref;
|
|
5666
5753
|
const metadata = ref.constructor[Symbol.metadata]; // ArraySchema/MapSchema do not have metadata
|
|
5667
5754
|
const refId = ref[$refId];
|
|
5668
|
-
let changes = this.changes.get(refId);
|
|
5669
|
-
if (changes === undefined) {
|
|
5670
|
-
changes = {};
|
|
5671
|
-
this.changes.set(refId, changes);
|
|
5672
|
-
}
|
|
5673
5755
|
if (tag === DEFAULT_VIEW_TAG) {
|
|
5674
5756
|
// parent is collection (Map/Array)
|
|
5675
5757
|
const parent = changeTree.parent;
|
|
5676
5758
|
if (parent && !Metadata.isValidInstance(parent) && changeTree.isFiltered) {
|
|
5677
|
-
const
|
|
5678
|
-
|
|
5679
|
-
if (changes === undefined) {
|
|
5680
|
-
changes = {};
|
|
5681
|
-
this.changes.set(parentRefId, changes);
|
|
5682
|
-
}
|
|
5683
|
-
else if (changes[changeTree.parentIndex] === exports.OPERATION.ADD) {
|
|
5759
|
+
const parentChanges = this.touchChanges(parent[$refId]);
|
|
5760
|
+
if (parentChanges[changeTree.parentIndex] === exports.OPERATION.ADD) {
|
|
5684
5761
|
//
|
|
5685
5762
|
// SAME PATCH ADD + REMOVE:
|
|
5686
5763
|
// The 'changes' of deleted structure should be ignored.
|
|
@@ -5688,12 +5765,13 @@
|
|
|
5688
5765
|
this.changes.delete(refId);
|
|
5689
5766
|
}
|
|
5690
5767
|
// DELETE / DELETE BY REF ID
|
|
5691
|
-
|
|
5768
|
+
parentChanges[changeTree.parentIndex] = exports.OPERATION.DELETE;
|
|
5692
5769
|
// Remove child schema from visible set
|
|
5693
5770
|
this._recursiveDeleteVisibleChangeTree(changeTree);
|
|
5694
5771
|
}
|
|
5695
5772
|
else {
|
|
5696
5773
|
// delete all "tagged" properties.
|
|
5774
|
+
const changes = this.touchChanges(refId);
|
|
5697
5775
|
metadata?.[$viewFieldIndexes]?.forEach((index) => {
|
|
5698
5776
|
changes[index] = exports.OPERATION.DELETE;
|
|
5699
5777
|
// Remove child structures of @view() fields from visible set.
|
|
@@ -5708,6 +5786,7 @@
|
|
|
5708
5786
|
}
|
|
5709
5787
|
else {
|
|
5710
5788
|
// delete only tagged properties
|
|
5789
|
+
const changes = this.touchChanges(refId);
|
|
5711
5790
|
metadata?.[$fieldIndexesByViewTag][tag].forEach((index) => {
|
|
5712
5791
|
changes[index] = exports.OPERATION.DELETE;
|
|
5713
5792
|
// Remove child structures from visible set
|
package/build/index.mjs
CHANGED
|
@@ -1364,14 +1364,13 @@ class ChangeTree {
|
|
|
1364
1364
|
|| this.root.types.parentFiltered[key]
|
|
1365
1365
|
|| fieldHasViewTag;
|
|
1366
1366
|
//
|
|
1367
|
-
// "isFiltered" may not be
|
|
1367
|
+
// "isFiltered" may not be immediately available during `change()` due to the instance not being attached to the root yet.
|
|
1368
1368
|
// when it's available, we need to enqueue the "changes" changeset into the "filteredChanges" changeset.
|
|
1369
1369
|
//
|
|
1370
1370
|
if (this.isFiltered) {
|
|
1371
1371
|
this.isVisibilitySharedWithParent = (parentChangeTree.isFiltered &&
|
|
1372
1372
|
typeof (refType) !== "string" &&
|
|
1373
|
-
!fieldHasViewTag
|
|
1374
|
-
parentIsCollection);
|
|
1373
|
+
!fieldHasViewTag);
|
|
1375
1374
|
if (!this.filteredChanges) {
|
|
1376
1375
|
this.filteredChanges = createChangeSet();
|
|
1377
1376
|
this.allFilteredChanges = createChangeSet();
|
|
@@ -4388,8 +4387,25 @@ class Encoder {
|
|
|
4388
4387
|
}
|
|
4389
4388
|
encodeView(view, sharedOffset, it, bytes = this.sharedBuffer) {
|
|
4390
4389
|
const viewOffset = it.offset;
|
|
4391
|
-
//
|
|
4392
|
-
|
|
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);
|
|
4393
4409
|
const changeTree = this.root.changeTrees[refId];
|
|
4394
4410
|
if (changeTree === undefined) {
|
|
4395
4411
|
// detached instance, remove from view and skip.
|
|
@@ -4425,10 +4441,53 @@ class Encoder {
|
|
|
4425
4441
|
//
|
|
4426
4442
|
// clear "view" changes after encoding
|
|
4427
4443
|
view.changes.clear();
|
|
4444
|
+
view.changesOutOfOrder = false;
|
|
4428
4445
|
// try to encode "filtered" changes
|
|
4429
4446
|
this.encode(it, view, bytes, "filteredChanges", false, viewOffset);
|
|
4430
4447
|
return concatBytes(bytes.subarray(0, sharedOffset), bytes.subarray(viewOffset, it.offset));
|
|
4431
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
|
+
}
|
|
4432
4491
|
discardChanges() {
|
|
4433
4492
|
// discard shared changes
|
|
4434
4493
|
let current = this.root.changes.next;
|
|
@@ -5500,12 +5559,45 @@ class StateView {
|
|
|
5500
5559
|
* (This is used to force encoding a property, even if it was not changed)
|
|
5501
5560
|
*/
|
|
5502
5561
|
changes = new Map();
|
|
5562
|
+
/**
|
|
5563
|
+
* Set when an operation may have left `changes` out of topological
|
|
5564
|
+
* order (a parent that needs to be encoded before its descendants is
|
|
5565
|
+
* positioned after them in the Map). `Encoder.encodeView` consults
|
|
5566
|
+
* this flag and only runs the topo-ordering pass when it's true,
|
|
5567
|
+
* skipping the work in the common case where insertion order already
|
|
5568
|
+
* coincides with topo order.
|
|
5569
|
+
*
|
|
5570
|
+
* Only `remove()` can break the invariant: it writes entries that
|
|
5571
|
+
* bypass `addParentOf`'s deepest-ancestor-first ordering. Everything
|
|
5572
|
+
* else (including multi-parent re-adds) preserves order by
|
|
5573
|
+
* construction. Reset to false at the end of each encodeView pass
|
|
5574
|
+
* (when `changes` is cleared).
|
|
5575
|
+
*/
|
|
5576
|
+
changesOutOfOrder = false;
|
|
5503
5577
|
constructor(iterable = false) {
|
|
5504
5578
|
this.iterable = iterable;
|
|
5505
5579
|
if (iterable) {
|
|
5506
5580
|
this.items = [];
|
|
5507
5581
|
}
|
|
5508
5582
|
}
|
|
5583
|
+
/**
|
|
5584
|
+
* Get the IndexedOperations entry for `refId`, creating one if missing.
|
|
5585
|
+
*
|
|
5586
|
+
* Map insertion order alone doesn't guarantee parent-before-child
|
|
5587
|
+
* iteration in all cases (a `view.remove()` followed by `view.add()`
|
|
5588
|
+
* can put a child entry into the Map before its newly-visible
|
|
5589
|
+
* ancestor). The wire-order invariant (parent SWITCH_TO_STRUCTURE
|
|
5590
|
+
* before any of its children's) is enforced at encode time by
|
|
5591
|
+
* `Encoder.encodeView` via a topological pass over `view.changes`.
|
|
5592
|
+
*/
|
|
5593
|
+
touchChanges(refId) {
|
|
5594
|
+
let entry = this.changes.get(refId);
|
|
5595
|
+
if (entry === undefined) {
|
|
5596
|
+
entry = {};
|
|
5597
|
+
this.changes.set(refId, entry);
|
|
5598
|
+
}
|
|
5599
|
+
return entry;
|
|
5600
|
+
}
|
|
5509
5601
|
// TODO: allow to set multiple tags at once
|
|
5510
5602
|
add(obj, tag = DEFAULT_VIEW_TAG, checkIncludeParent = true) {
|
|
5511
5603
|
const changeTree = obj?.[$changes];
|
|
@@ -5539,12 +5631,8 @@ class StateView {
|
|
|
5539
5631
|
if (checkIncludeParent && parentChangeTree) {
|
|
5540
5632
|
this.addParentOf(changeTree, tag);
|
|
5541
5633
|
}
|
|
5542
|
-
|
|
5543
|
-
|
|
5544
|
-
changes = {};
|
|
5545
|
-
// FIXME / OPTIMIZE: do not add if no changes are needed
|
|
5546
|
-
this.changes.set(obj[$refId], changes);
|
|
5547
|
-
}
|
|
5634
|
+
// FIXME / OPTIMIZE: do not add if no changes are needed
|
|
5635
|
+
const changes = this.touchChanges(obj[$refId]);
|
|
5548
5636
|
let isChildAdded = false;
|
|
5549
5637
|
//
|
|
5550
5638
|
// Add children of this ChangeTree first.
|
|
@@ -5623,11 +5711,7 @@ class StateView {
|
|
|
5623
5711
|
}
|
|
5624
5712
|
// add parent's tag properties
|
|
5625
5713
|
if (changeTree.getChange(parentIndex) !== OPERATION.DELETE) {
|
|
5626
|
-
|
|
5627
|
-
if (changes === undefined) {
|
|
5628
|
-
changes = {};
|
|
5629
|
-
this.changes.set(changeTree.ref[$refId], changes);
|
|
5630
|
-
}
|
|
5714
|
+
const changes = this.touchChanges(changeTree.ref[$refId]);
|
|
5631
5715
|
if (!this.tags) {
|
|
5632
5716
|
this.tags = new WeakMap();
|
|
5633
5717
|
}
|
|
@@ -5649,6 +5733,9 @@ class StateView {
|
|
|
5649
5733
|
console.warn("StateView#remove(), invalid object:", obj);
|
|
5650
5734
|
return this;
|
|
5651
5735
|
}
|
|
5736
|
+
// remove() bypasses addParentOf's ordering guarantee — flag the
|
|
5737
|
+
// changeset as potentially out of topological order.
|
|
5738
|
+
this.changesOutOfOrder = true;
|
|
5652
5739
|
this.visible.delete(changeTree);
|
|
5653
5740
|
// remove from iterable list
|
|
5654
5741
|
if (this.iterable &&
|
|
@@ -5659,22 +5746,12 @@ class StateView {
|
|
|
5659
5746
|
const ref = changeTree.ref;
|
|
5660
5747
|
const metadata = ref.constructor[Symbol.metadata]; // ArraySchema/MapSchema do not have metadata
|
|
5661
5748
|
const refId = ref[$refId];
|
|
5662
|
-
let changes = this.changes.get(refId);
|
|
5663
|
-
if (changes === undefined) {
|
|
5664
|
-
changes = {};
|
|
5665
|
-
this.changes.set(refId, changes);
|
|
5666
|
-
}
|
|
5667
5749
|
if (tag === DEFAULT_VIEW_TAG) {
|
|
5668
5750
|
// parent is collection (Map/Array)
|
|
5669
5751
|
const parent = changeTree.parent;
|
|
5670
5752
|
if (parent && !Metadata.isValidInstance(parent) && changeTree.isFiltered) {
|
|
5671
|
-
const
|
|
5672
|
-
|
|
5673
|
-
if (changes === undefined) {
|
|
5674
|
-
changes = {};
|
|
5675
|
-
this.changes.set(parentRefId, changes);
|
|
5676
|
-
}
|
|
5677
|
-
else if (changes[changeTree.parentIndex] === OPERATION.ADD) {
|
|
5753
|
+
const parentChanges = this.touchChanges(parent[$refId]);
|
|
5754
|
+
if (parentChanges[changeTree.parentIndex] === OPERATION.ADD) {
|
|
5678
5755
|
//
|
|
5679
5756
|
// SAME PATCH ADD + REMOVE:
|
|
5680
5757
|
// The 'changes' of deleted structure should be ignored.
|
|
@@ -5682,12 +5759,13 @@ class StateView {
|
|
|
5682
5759
|
this.changes.delete(refId);
|
|
5683
5760
|
}
|
|
5684
5761
|
// DELETE / DELETE BY REF ID
|
|
5685
|
-
|
|
5762
|
+
parentChanges[changeTree.parentIndex] = OPERATION.DELETE;
|
|
5686
5763
|
// Remove child schema from visible set
|
|
5687
5764
|
this._recursiveDeleteVisibleChangeTree(changeTree);
|
|
5688
5765
|
}
|
|
5689
5766
|
else {
|
|
5690
5767
|
// delete all "tagged" properties.
|
|
5768
|
+
const changes = this.touchChanges(refId);
|
|
5691
5769
|
metadata?.[$viewFieldIndexes]?.forEach((index) => {
|
|
5692
5770
|
changes[index] = OPERATION.DELETE;
|
|
5693
5771
|
// Remove child structures of @view() fields from visible set.
|
|
@@ -5702,6 +5780,7 @@ class StateView {
|
|
|
5702
5780
|
}
|
|
5703
5781
|
else {
|
|
5704
5782
|
// delete only tagged properties
|
|
5783
|
+
const changes = this.touchChanges(refId);
|
|
5705
5784
|
metadata?.[$fieldIndexesByViewTag][tag].forEach((index) => {
|
|
5706
5785
|
changes[index] = OPERATION.DELETE;
|
|
5707
5786
|
// Remove child structures from visible set
|