@fluidframework/merge-tree 2.31.0 → 2.32.0
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/CHANGELOG.md +4 -0
- package/dist/client.d.ts +7 -1
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +153 -44
- package/dist/client.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -1
- package/dist/index.js.map +1 -1
- package/dist/mergeTree.d.ts +17 -5
- package/dist/mergeTree.d.ts.map +1 -1
- package/dist/mergeTree.js +188 -79
- package/dist/mergeTree.js.map +1 -1
- package/dist/mergeTreeNodes.d.ts +16 -18
- package/dist/mergeTreeNodes.d.ts.map +1 -1
- package/dist/mergeTreeNodes.js +6 -0
- package/dist/mergeTreeNodes.js.map +1 -1
- package/dist/perspective.d.ts +9 -0
- package/dist/perspective.d.ts.map +1 -1
- package/dist/perspective.js +14 -1
- package/dist/perspective.js.map +1 -1
- package/dist/segmentInfos.d.ts +32 -4
- package/dist/segmentInfos.d.ts.map +1 -1
- package/dist/segmentInfos.js +3 -1
- package/dist/segmentInfos.js.map +1 -1
- package/dist/sortedSegmentSet.d.ts +1 -0
- package/dist/sortedSegmentSet.d.ts.map +1 -1
- package/dist/sortedSegmentSet.js +3 -0
- package/dist/sortedSegmentSet.js.map +1 -1
- package/dist/test/beastTest.spec.js +5 -5
- package/dist/test/beastTest.spec.js.map +1 -1
- package/dist/test/client.localReference.spec.js +3 -3
- package/dist/test/client.localReference.spec.js.map +1 -1
- package/dist/test/client.rollback.spec.js +17 -0
- package/dist/test/client.rollback.spec.js.map +1 -1
- package/dist/test/clientTestHelper.d.ts +100 -0
- package/dist/test/clientTestHelper.d.ts.map +1 -0
- package/dist/test/clientTestHelper.js +196 -0
- package/dist/test/clientTestHelper.js.map +1 -0
- package/dist/test/mergeTree.annotate.spec.js +12 -12
- package/dist/test/mergeTree.annotate.spec.js.map +1 -1
- package/dist/test/mergeTree.markRangeRemoved.deltaCallback.spec.js +1 -1
- package/dist/test/mergeTree.markRangeRemoved.deltaCallback.spec.js.map +1 -1
- package/dist/test/obliterate.concurrent.spec.js +93 -90
- package/dist/test/obliterate.concurrent.spec.js.map +1 -1
- package/dist/test/obliterate.deltaCallback.spec.js +121 -116
- package/dist/test/obliterate.deltaCallback.spec.js.map +1 -1
- package/dist/test/obliterate.rangeExpansion.spec.js +29 -79
- package/dist/test/obliterate.rangeExpansion.spec.js.map +1 -1
- package/dist/test/obliterate.reconnect.spec.js +235 -58
- package/dist/test/obliterate.reconnect.spec.js.map +1 -1
- package/dist/test/testClient.js +1 -1
- package/dist/test/testClient.js.map +1 -1
- package/dist/test/testUtils.d.ts +13 -0
- package/dist/test/testUtils.d.ts.map +1 -1
- package/dist/test/testUtils.js +22 -1
- package/dist/test/testUtils.js.map +1 -1
- package/lib/client.d.ts +7 -1
- package/lib/client.d.ts.map +1 -1
- package/lib/client.js +155 -46
- package/lib/client.js.map +1 -1
- package/lib/index.d.ts +1 -1
- package/lib/index.d.ts.map +1 -1
- package/lib/index.js +1 -0
- package/lib/index.js.map +1 -1
- package/lib/mergeTree.d.ts +17 -5
- package/lib/mergeTree.d.ts.map +1 -1
- package/lib/mergeTree.js +192 -83
- package/lib/mergeTree.js.map +1 -1
- package/lib/mergeTreeNodes.d.ts +16 -18
- package/lib/mergeTreeNodes.d.ts.map +1 -1
- package/lib/mergeTreeNodes.js +7 -1
- package/lib/mergeTreeNodes.js.map +1 -1
- package/lib/perspective.d.ts +9 -0
- package/lib/perspective.d.ts.map +1 -1
- package/lib/perspective.js +12 -0
- package/lib/perspective.js.map +1 -1
- package/lib/segmentInfos.d.ts +32 -4
- package/lib/segmentInfos.d.ts.map +1 -1
- package/lib/segmentInfos.js +2 -1
- package/lib/segmentInfos.js.map +1 -1
- package/lib/sortedSegmentSet.d.ts +1 -0
- package/lib/sortedSegmentSet.d.ts.map +1 -1
- package/lib/sortedSegmentSet.js +3 -0
- package/lib/sortedSegmentSet.js.map +1 -1
- package/lib/test/beastTest.spec.js +5 -5
- package/lib/test/beastTest.spec.js.map +1 -1
- package/lib/test/client.localReference.spec.js +3 -3
- package/lib/test/client.localReference.spec.js.map +1 -1
- package/lib/test/client.rollback.spec.js +18 -1
- package/lib/test/client.rollback.spec.js.map +1 -1
- package/lib/test/clientTestHelper.d.ts +100 -0
- package/lib/test/clientTestHelper.d.ts.map +1 -0
- package/lib/test/clientTestHelper.js +192 -0
- package/lib/test/clientTestHelper.js.map +1 -0
- package/lib/test/mergeTree.annotate.spec.js +12 -12
- package/lib/test/mergeTree.annotate.spec.js.map +1 -1
- package/lib/test/mergeTree.markRangeRemoved.deltaCallback.spec.js +1 -1
- package/lib/test/mergeTree.markRangeRemoved.deltaCallback.spec.js.map +1 -1
- package/lib/test/obliterate.concurrent.spec.js +93 -90
- package/lib/test/obliterate.concurrent.spec.js.map +1 -1
- package/lib/test/obliterate.deltaCallback.spec.js +121 -116
- package/lib/test/obliterate.deltaCallback.spec.js.map +1 -1
- package/lib/test/obliterate.rangeExpansion.spec.js +1 -51
- package/lib/test/obliterate.rangeExpansion.spec.js.map +1 -1
- package/lib/test/obliterate.reconnect.spec.js +236 -59
- package/lib/test/obliterate.reconnect.spec.js.map +1 -1
- package/lib/test/testClient.js +1 -1
- package/lib/test/testClient.js.map +1 -1
- package/lib/test/testUtils.d.ts +13 -0
- package/lib/test/testUtils.d.ts.map +1 -1
- package/lib/test/testUtils.js +20 -0
- package/lib/test/testUtils.js.map +1 -1
- package/package.json +19 -18
- package/src/client.ts +286 -55
- package/src/index.ts +1 -1
- package/src/mergeTree.ts +265 -98
- package/src/mergeTreeNodes.ts +24 -18
- package/src/perspective.ts +21 -0
- package/src/segmentInfos.ts +48 -6
- package/src/sortedSegmentSet.ts +4 -0
- package/dist/test/partialSyncHelper.d.ts +0 -42
- package/dist/test/partialSyncHelper.d.ts.map +0 -1
- package/dist/test/partialSyncHelper.js +0 -96
- package/dist/test/partialSyncHelper.js.map +0 -1
- package/dist/test/reconnectHelper.d.ts +0 -50
- package/dist/test/reconnectHelper.d.ts.map +0 -1
- package/dist/test/reconnectHelper.js +0 -106
- package/dist/test/reconnectHelper.js.map +0 -1
- package/lib/test/partialSyncHelper.d.ts +0 -42
- package/lib/test/partialSyncHelper.d.ts.map +0 -1
- package/lib/test/partialSyncHelper.js +0 -92
- package/lib/test/partialSyncHelper.js.map +0 -1
- package/lib/test/reconnectHelper.d.ts +0 -50
- package/lib/test/reconnectHelper.d.ts.map +0 -1
- package/lib/test/reconnectHelper.js +0 -102
- package/lib/test/reconnectHelper.js.map +0 -1
package/lib/client.js
CHANGED
|
@@ -11,11 +11,12 @@ import { LoggingError, UsageError, } from "@fluidframework/telemetry-utils/inter
|
|
|
11
11
|
import { MergeTreeTextHelper } from "./MergeTreeTextHelper.js";
|
|
12
12
|
import { RedBlackTree } from "./collections/index.js";
|
|
13
13
|
import { NonCollabClient, UniversalSequenceNumber } from "./constants.js";
|
|
14
|
-
import {
|
|
14
|
+
import { SlidingPreference } from "./localReference.js";
|
|
15
|
+
import { MergeTree, errorIfOptionNotTrue, getSlideToSegoff, isRemovedAndAcked, } from "./mergeTree.js";
|
|
15
16
|
import { walkAllChildSegments } from "./mergeTreeNodeWalk.js";
|
|
16
17
|
import { compareStrings, isSegmentLeaf, } from "./mergeTreeNodes.js";
|
|
17
18
|
import { createAdjustRangeOp, createAnnotateMarkerOp, createAnnotateRangeOp, createGroupOp, createInsertSegmentOp, createObliterateRangeOp, createObliterateRangeOpSided, createRemoveRangeOp, } from "./opBuilder.js";
|
|
18
|
-
import { MergeTreeDeltaType, } from "./ops.js";
|
|
19
|
+
import { MergeTreeDeltaType, ReferenceType, } from "./ops.js";
|
|
19
20
|
import { LocalReconnectingPerspective, PriorPerspective, } from "./perspective.js";
|
|
20
21
|
import { DetachedReferencePosition } from "./referencePositions.js";
|
|
21
22
|
import { isInserted, isRemoved, overwriteInfo, toRemovalInfo, } from "./segmentInfos.js";
|
|
@@ -56,7 +57,7 @@ export class Client extends TypedEventEmitter {
|
|
|
56
57
|
this.getMinInFlightRefSeq = getMinInFlightRefSeq;
|
|
57
58
|
this.clientNameToIds = new RedBlackTree(compareStrings);
|
|
58
59
|
this.shortClientIdMap = [];
|
|
59
|
-
this.
|
|
60
|
+
this.cachedObliterateRebases = new Map();
|
|
60
61
|
this._mergeTree = new MergeTree(options);
|
|
61
62
|
this._mergeTree.mergeTreeDeltaCallback = (opArgs, deltaArgs) => {
|
|
62
63
|
this.emit("delta", opArgs, deltaArgs, this);
|
|
@@ -492,7 +493,7 @@ export class Client extends TypedEventEmitter {
|
|
|
492
493
|
ackPendingSegment(opArgs) {
|
|
493
494
|
if (opArgs.op.type === MergeTreeDeltaType.GROUP) {
|
|
494
495
|
for (const memberOp of opArgs.op.ops) {
|
|
495
|
-
this._mergeTree.
|
|
496
|
+
this._mergeTree.ackOp({
|
|
496
497
|
groupOp: opArgs.op,
|
|
497
498
|
op: memberOp,
|
|
498
499
|
sequencedMessage: opArgs.sequencedMessage,
|
|
@@ -500,7 +501,7 @@ export class Client extends TypedEventEmitter {
|
|
|
500
501
|
}
|
|
501
502
|
}
|
|
502
503
|
else {
|
|
503
|
-
this._mergeTree.
|
|
504
|
+
this._mergeTree.ackOp(opArgs);
|
|
504
505
|
}
|
|
505
506
|
}
|
|
506
507
|
getOrAddShortClientId(longClientId) {
|
|
@@ -540,22 +541,140 @@ export class Client extends TypedEventEmitter {
|
|
|
540
541
|
const perspective = new LocalReconnectingPerspective(currentSeq, clientId, localSeq);
|
|
541
542
|
return this._mergeTree.getPosition(segment, perspective);
|
|
542
543
|
}
|
|
544
|
+
/**
|
|
545
|
+
* Rebases a sided local reference to the best fitting position in the current tree.
|
|
546
|
+
*/
|
|
547
|
+
rebaseSidedLocalReference(ref, side, reconnectingPerspective, slidePreference) {
|
|
548
|
+
const oldSegment = ref.getSegment();
|
|
549
|
+
const oldOffset = ref.getOffset();
|
|
550
|
+
assert(oldSegment !== undefined && oldOffset !== undefined, 0xb61 /* Invalid old reference */);
|
|
551
|
+
const useNewSlidingBehavior = true;
|
|
552
|
+
// Destructuring segment + offset is convenient and segment is reassigned
|
|
553
|
+
// eslint-disable-next-line prefer-const
|
|
554
|
+
let { segment: newSegment, offset: newOffset } = getSlideToSegoff({ segment: oldSegment, offset: oldOffset }, slidePreference, reconnectingPerspective, useNewSlidingBehavior);
|
|
555
|
+
newSegment ??=
|
|
556
|
+
slidePreference === SlidingPreference.FORWARD
|
|
557
|
+
? this._mergeTree.endOfTree
|
|
558
|
+
: this._mergeTree.startOfTree;
|
|
559
|
+
assert(isSegmentLeaf(newSegment) && newOffset !== undefined, 0xb62 /* Invalid new segment on rebase */);
|
|
560
|
+
const newSide = newSegment === oldSegment
|
|
561
|
+
? side
|
|
562
|
+
: // If the reference slid to a new position, the closest fit to the original position will be independent of
|
|
563
|
+
// the original side and "in the direction of where the reference was".
|
|
564
|
+
slidePreference === SlidingPreference.FORWARD
|
|
565
|
+
? Side.Before
|
|
566
|
+
: Side.After;
|
|
567
|
+
return { segment: newSegment, offset: newOffset, side: newSide };
|
|
568
|
+
}
|
|
569
|
+
computeNewObliterateEndpoints(obliterateInfo) {
|
|
570
|
+
const { currentSeq, clientId } = this.getCollabWindow();
|
|
571
|
+
const reconnectingPerspective = new LocalReconnectingPerspective(currentSeq, clientId, obliterateInfo.stamp.localSeq - 1);
|
|
572
|
+
const newStart = this.rebaseSidedLocalReference(obliterateInfo.start, obliterateInfo.startSide, reconnectingPerspective, SlidingPreference.FORWARD);
|
|
573
|
+
const newEnd = this.rebaseSidedLocalReference(obliterateInfo.end, obliterateInfo.endSide, reconnectingPerspective, SlidingPreference.BACKWARD);
|
|
574
|
+
return {
|
|
575
|
+
start: newStart,
|
|
576
|
+
end: newEnd,
|
|
577
|
+
};
|
|
578
|
+
}
|
|
543
579
|
resetPendingDeltaToOps(resetOp, segmentGroup) {
|
|
544
580
|
assert(!!segmentGroup, 0x033 /* "Segment group undefined" */);
|
|
545
581
|
const NACKedSegmentGroup = this.pendingRebase?.shift()?.data;
|
|
546
582
|
assert(segmentGroup === NACKedSegmentGroup, 0x034 /* "Segment group not at head of pending rebase queue" */);
|
|
583
|
+
assert(segmentGroup.localSeq !== undefined, 0x867 /* expected segment group localSeq to be defined */);
|
|
547
584
|
if (this.pendingRebase?.empty) {
|
|
548
585
|
this.pendingRebase = undefined;
|
|
549
586
|
}
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
587
|
+
if (resetOp.type === MergeTreeDeltaType.OBLITERATE ||
|
|
588
|
+
resetOp.type === MergeTreeDeltaType.OBLITERATE_SIDED) {
|
|
589
|
+
errorIfOptionNotTrue(this._mergeTree.options, "mergeTreeEnableObliterateReconnect");
|
|
590
|
+
// sliceRemove reconnect logic is characteristically different from other ops (which can only apply to segments they originally saw).
|
|
591
|
+
// This is because the ranges that other ops apply to can be broken up by concurrent insertions, so even though setRemoves are originally
|
|
592
|
+
// applied to a contiguous set of segments, at resubmission time they may no longer be.
|
|
593
|
+
// On the other hand, the closest analog to a `sliceRemove` that we can submit is obtained by resolving the "closest" start and end points
|
|
594
|
+
// for that slice, updating the local obliterate metadata to reflect that slice, and submitting a single op.
|
|
595
|
+
const obliterateInfo = segmentGroup.obliterateInfo;
|
|
596
|
+
assert(obliterateInfo !== undefined, 0xb63 /* Resubmitting obliterate op without obliterate info in segment group */);
|
|
597
|
+
assert(obliterateInfo.stamp.localSeq === segmentGroup.localSeq, 0xb64 /* Local seq mismatch */);
|
|
598
|
+
const cachedNewPositions = this.cachedObliterateRebases.get(obliterateInfo.stamp.localSeq);
|
|
599
|
+
assert(cachedNewPositions !== undefined, 0xb65 /* didn't compute new positions for obliterate on reconnect early enough */);
|
|
600
|
+
const { start: { segment: newStartSegment, offset: newStartOffset, side: newStartSide }, end: { segment: newEndSegment, offset: newEndOffset, side: newEndSide }, } = cachedNewPositions;
|
|
601
|
+
const { currentSeq, clientId } = this.getCollabWindow();
|
|
602
|
+
if (newEndSegment.ordinal < newStartSegment.ordinal) {
|
|
603
|
+
for (const segment of segmentGroup.segments) {
|
|
604
|
+
assert(isRemovedAndAcked(segment), 0xb66 /* On reconnect, obliterate applied to new segments even though original ones were not removed. */);
|
|
605
|
+
const lastRemove = segment.removes[segment.removes.length - 1];
|
|
606
|
+
assert(lastRemove.type === "sliceRemove" && lastRemove.localSeq === segmentGroup.localSeq, 0xb67 /* Last remove should be the obliterate that is being resubmitted. */);
|
|
607
|
+
// The original obliterate affected this segment, but it has since been removed and overlapping removes
|
|
608
|
+
// are only possible when they are concurrent. We adjust the metadata on that segment now to reflect
|
|
609
|
+
// the fact that the obliterate no longer affects it.
|
|
610
|
+
segment.removes.pop();
|
|
611
|
+
}
|
|
612
|
+
this._mergeTree.rebaseObliterateTo(obliterateInfo, undefined);
|
|
613
|
+
return [];
|
|
614
|
+
}
|
|
615
|
+
assert(obliterateInfo.tiebreakTrackingGroup !== undefined, 0xb68 /* Tiebreak tracking group missing */);
|
|
616
|
+
const newObliterate = {
|
|
617
|
+
// Recreate the start position using the perspective that other clients will see.
|
|
618
|
+
// This may not be at the same position as the original reference, since the segment the original reference was on could have been removed.
|
|
619
|
+
start: this._mergeTree.createLocalReferencePosition(newStartSegment, newStartOffset, ReferenceType.StayOnRemove, undefined),
|
|
620
|
+
startSide: newStartSide,
|
|
621
|
+
end: this._mergeTree.createLocalReferencePosition(newEndSegment, newEndOffset, ReferenceType.StayOnRemove, undefined),
|
|
622
|
+
endSide: newEndSide,
|
|
623
|
+
refSeq: currentSeq,
|
|
624
|
+
// We reuse the localSeq from the original obliterate.
|
|
625
|
+
stamp: obliterateInfo.stamp,
|
|
626
|
+
segmentGroup: undefined,
|
|
627
|
+
tiebreakTrackingGroup: obliterateInfo.tiebreakTrackingGroup,
|
|
628
|
+
};
|
|
629
|
+
newObliterate.start.addProperties({ obliterate: newObliterate });
|
|
630
|
+
newObliterate.end.addProperties({ obliterate: newObliterate });
|
|
631
|
+
newObliterate.segmentGroup = {
|
|
632
|
+
segments: [],
|
|
633
|
+
localSeq: segmentGroup.localSeq,
|
|
634
|
+
refSeq: this.getCollabWindow().currentSeq,
|
|
635
|
+
obliterateInfo: newObliterate,
|
|
636
|
+
};
|
|
637
|
+
for (const segment of segmentGroup.segments) {
|
|
638
|
+
assert(segment.segmentGroups?.remove(segmentGroup) === true, 0x035 /* "Segment group not in segment pending queue" */);
|
|
639
|
+
if ((segment.ordinal > newStartSegment.ordinal &&
|
|
640
|
+
segment.ordinal < newEndSegment.ordinal) ||
|
|
641
|
+
(segment === newStartSegment && newStartSide === Side.Before) ||
|
|
642
|
+
(segment === newEndSegment && newEndSide === Side.After)) {
|
|
643
|
+
segment.segmentGroups.enqueue(newObliterate.segmentGroup);
|
|
644
|
+
}
|
|
645
|
+
else {
|
|
646
|
+
assert(isRemovedAndAcked(segment), 0xb69 /* On reconnect, obliterate applied to new segments even though original ones were not removed. */);
|
|
647
|
+
const lastRemove = segment.removes[segment.removes.length - 1];
|
|
648
|
+
assert(lastRemove.type === "sliceRemove" && lastRemove.localSeq === segmentGroup.localSeq, 0xb6a /* Last remove should be the obliterate that is being resubmitted. */);
|
|
649
|
+
// The original obliterate affected this segment, but it has since been removed and it's impossible to apply the
|
|
650
|
+
// local obliterate so that is so. We adjust the metadata on that segment now.
|
|
651
|
+
segment.removes.pop();
|
|
652
|
+
}
|
|
653
|
+
}
|
|
654
|
+
this._mergeTree.rebaseObliterateTo(obliterateInfo, newObliterate);
|
|
655
|
+
this._mergeTree.pendingSegments.push(newObliterate.segmentGroup);
|
|
656
|
+
const reconnectingPerspective = new LocalReconnectingPerspective(currentSeq, clientId, obliterateInfo.stamp.localSeq - 1);
|
|
657
|
+
const newStartPos = this._mergeTree.getPosition(newStartSegment, reconnectingPerspective) + newStartOffset;
|
|
658
|
+
const newEndPos = this._mergeTree.getPosition(newEndSegment, reconnectingPerspective) + newEndOffset;
|
|
659
|
+
if (resetOp.type === MergeTreeDeltaType.OBLITERATE) {
|
|
660
|
+
assert(newStartSide === Side.Before && newEndSide === Side.After, 0xb6b /* Non-sided obliterate should have start side before and end side after */);
|
|
661
|
+
// Use a non-sided obliterate op if the original op was non-sided. Some combinations of feature flags disallow sided obliterate ops
|
|
662
|
+
// but allow non-sided ones, and if we convert a non-sided op to a sided one on reconnect, we may cause errors.
|
|
663
|
+
return [
|
|
664
|
+
createObliterateRangeOp(newStartPos, newEndPos +
|
|
665
|
+
1 /* to make the end exclusive, see corresponding -1 in `createObliterateRangeOpSided` on converting non-sided to sided. */),
|
|
666
|
+
];
|
|
667
|
+
}
|
|
668
|
+
return [
|
|
669
|
+
createObliterateRangeOpSided({
|
|
670
|
+
pos: newStartPos,
|
|
671
|
+
side: newStartSide,
|
|
672
|
+
}, {
|
|
673
|
+
pos: newEndPos,
|
|
674
|
+
side: newEndSide,
|
|
675
|
+
}),
|
|
676
|
+
];
|
|
677
|
+
}
|
|
559
678
|
const opList = [];
|
|
560
679
|
// We need to sort the segments by ordinal, as the segments are not sorted in the segment group.
|
|
561
680
|
// The reason they need them sorted, as they have the same local sequence number and which means
|
|
@@ -563,8 +682,7 @@ export class Client extends TypedEventEmitter {
|
|
|
563
682
|
// By sorting we ensure the nearer segment will be applied and sequenced before the farther segments
|
|
564
683
|
// so their recalculated positions will be correct.
|
|
565
684
|
for (const segment of segmentGroup.segments.sort((a, b) => a.ordinal < b.ordinal ? -1 : 1)) {
|
|
566
|
-
assert(segment.segmentGroups?.remove(segmentGroup) === true,
|
|
567
|
-
assert(segmentGroup.localSeq !== undefined, 0x867 /* expected segment group localSeq to be defined */);
|
|
685
|
+
assert(segment.segmentGroups?.remove(segmentGroup) === true, 0xb6c /* Segment group not in segment pending queue */);
|
|
568
686
|
const segmentPosition = this.findReconnectionPosition(segment, segmentGroup.localSeq);
|
|
569
687
|
let newOp;
|
|
570
688
|
switch (resetOp.type) {
|
|
@@ -618,33 +736,11 @@ export class Client extends TypedEventEmitter {
|
|
|
618
736
|
}
|
|
619
737
|
break;
|
|
620
738
|
}
|
|
621
|
-
case MergeTreeDeltaType.OBLITERATE: {
|
|
622
|
-
errorIfOptionNotTrue(this._mergeTree.options, "mergeTreeEnableObliterateReconnect");
|
|
623
|
-
// Only bother resubmitting if nobody else has removed it in the meantime.
|
|
624
|
-
// When that happens, the first removal will have been acked.
|
|
625
|
-
if (isRemoved(segment) && opstampUtils.isLocal(segment.removes[0])) {
|
|
626
|
-
newOp = createObliterateRangeOp(segmentPosition, segmentPosition + segment.cachedLength);
|
|
627
|
-
}
|
|
628
|
-
break;
|
|
629
|
-
}
|
|
630
739
|
default: {
|
|
631
740
|
throw new Error(`Invalid op type`);
|
|
632
741
|
}
|
|
633
742
|
}
|
|
634
|
-
if (newOp
|
|
635
|
-
segment.segmentGroups.enqueue(obliterateSegmentGroup);
|
|
636
|
-
const first = opList[0];
|
|
637
|
-
if (!!first &&
|
|
638
|
-
first.pos2 !== undefined &&
|
|
639
|
-
first.type !== MergeTreeDeltaType.OBLITERATE_SIDED &&
|
|
640
|
-
newOp.type !== MergeTreeDeltaType.OBLITERATE_SIDED) {
|
|
641
|
-
first.pos2 += newOp.pos2 - newOp.pos1;
|
|
642
|
-
}
|
|
643
|
-
else {
|
|
644
|
-
opList.push(newOp);
|
|
645
|
-
}
|
|
646
|
-
}
|
|
647
|
-
else if (newOp) {
|
|
743
|
+
if (newOp) {
|
|
648
744
|
const newSegmentGroup = {
|
|
649
745
|
segments: [],
|
|
650
746
|
localSeq: segmentGroup.localSeq,
|
|
@@ -655,10 +751,6 @@ export class Client extends TypedEventEmitter {
|
|
|
655
751
|
opList.push(newOp);
|
|
656
752
|
}
|
|
657
753
|
}
|
|
658
|
-
if (resetOp.type === MergeTreeDeltaType.OBLITERATE &&
|
|
659
|
-
obliterateSegmentGroup.segments.length > 0) {
|
|
660
|
-
this._mergeTree.pendingSegments.push(obliterateSegmentGroup);
|
|
661
|
-
}
|
|
662
754
|
return opList;
|
|
663
755
|
}
|
|
664
756
|
applyRemoteOp(opArgs) {
|
|
@@ -789,11 +881,28 @@ export class Client extends TypedEventEmitter {
|
|
|
789
881
|
assert(firstGroupNode !== undefined, 0x70e /* segment group must exist in pending list */);
|
|
790
882
|
this.pendingRebase = this._mergeTree.pendingSegments.splice(firstGroupNode);
|
|
791
883
|
}
|
|
792
|
-
const
|
|
793
|
-
if (
|
|
884
|
+
const collabWindow = this.getCollabWindow();
|
|
885
|
+
if (this.lastNormalization === undefined ||
|
|
886
|
+
collabWindow.currentSeq !== this.lastNormalization.refSeq ||
|
|
887
|
+
collabWindow.localSeq !== this.lastNormalization.localRefSeq) {
|
|
888
|
+
// Compute obliterate endpoint destinations before segments are normalized.
|
|
889
|
+
// Segment normalization can affect what should be the semantically correct segments for the endpoints to be placed on.
|
|
890
|
+
this.cachedObliterateRebases.clear();
|
|
891
|
+
for (const group of [...this._mergeTree.pendingSegments, ...this.pendingRebase]) {
|
|
892
|
+
const { obliterateInfo } = group.data;
|
|
893
|
+
if (obliterateInfo !== undefined) {
|
|
894
|
+
const { start, end } = this.computeNewObliterateEndpoints(obliterateInfo);
|
|
895
|
+
const { localSeq } = obliterateInfo.stamp;
|
|
896
|
+
assert(localSeq !== undefined, 0xb6d /* Local seq must be defined */);
|
|
897
|
+
this.cachedObliterateRebases.set(localSeq, { start, end });
|
|
898
|
+
}
|
|
899
|
+
}
|
|
794
900
|
this.emit("normalize", this);
|
|
795
901
|
this._mergeTree.normalizeSegmentsOnRebase();
|
|
796
|
-
this.
|
|
902
|
+
this.lastNormalization = {
|
|
903
|
+
refSeq: collabWindow.currentSeq,
|
|
904
|
+
localRefSeq: collabWindow.localSeq,
|
|
905
|
+
};
|
|
797
906
|
}
|
|
798
907
|
const opList = [];
|
|
799
908
|
if (resetOp.type === MergeTreeDeltaType.GROUP) {
|