@fluidframework/merge-tree 2.30.0 → 2.31.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 +403 -399
- package/api-report/merge-tree.legacy.alpha.api.md +1 -0
- package/dist/MergeTreeTextHelper.d.ts +9 -3
- package/dist/MergeTreeTextHelper.d.ts.map +1 -1
- package/dist/MergeTreeTextHelper.js +5 -5
- package/dist/MergeTreeTextHelper.js.map +1 -1
- package/dist/client.d.ts +7 -13
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +136 -110
- package/dist/client.js.map +1 -1
- package/dist/endOfTreeSegment.d.ts +12 -8
- package/dist/endOfTreeSegment.d.ts.map +1 -1
- package/dist/endOfTreeSegment.js +2 -4
- package/dist/endOfTreeSegment.js.map +1 -1
- package/dist/index.d.ts +6 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -3
- package/dist/index.js.map +1 -1
- package/dist/mergeTree.d.ts +37 -23
- package/dist/mergeTree.d.ts.map +1 -1
- package/dist/mergeTree.js +400 -483
- package/dist/mergeTree.js.map +1 -1
- package/dist/mergeTreeDeltaCallback.d.ts +4 -8
- package/dist/mergeTreeDeltaCallback.d.ts.map +1 -1
- package/dist/mergeTreeDeltaCallback.js.map +1 -1
- package/dist/mergeTreeNodes.d.ts +32 -10
- package/dist/mergeTreeNodes.d.ts.map +1 -1
- package/dist/mergeTreeNodes.js +43 -28
- package/dist/mergeTreeNodes.js.map +1 -1
- package/dist/partialLengths.d.ts +2 -2
- package/dist/partialLengths.d.ts.map +1 -1
- package/dist/partialLengths.js +181 -109
- package/dist/partialLengths.js.map +1 -1
- package/dist/perspective.d.ts +8 -27
- package/dist/perspective.d.ts.map +1 -1
- package/dist/perspective.js +7 -67
- package/dist/perspective.js.map +1 -1
- package/dist/revertibles.d.ts.map +1 -1
- package/dist/revertibles.js +2 -2
- package/dist/revertibles.js.map +1 -1
- package/dist/segmentInfos.d.ts +20 -106
- package/dist/segmentInfos.d.ts.map +1 -1
- package/dist/segmentInfos.js +28 -42
- package/dist/segmentInfos.js.map +1 -1
- package/dist/segmentPropertiesManager.d.ts +1 -14
- package/dist/segmentPropertiesManager.d.ts.map +1 -1
- package/dist/segmentPropertiesManager.js +3 -17
- package/dist/segmentPropertiesManager.js.map +1 -1
- package/dist/snapshotLoader.d.ts.map +1 -1
- package/dist/snapshotLoader.js +62 -19
- package/dist/snapshotLoader.js.map +1 -1
- package/dist/snapshotV1.d.ts.map +1 -1
- package/dist/snapshotV1.js +55 -24
- package/dist/snapshotV1.js.map +1 -1
- package/dist/snapshotlegacy.d.ts.map +1 -1
- package/dist/snapshotlegacy.js +6 -9
- package/dist/snapshotlegacy.js.map +1 -1
- package/dist/stamps.d.ts +1 -1
- package/dist/stamps.js +1 -1
- package/dist/stamps.js.map +1 -1
- package/dist/test/Insertion.perf.spec.js +6 -51
- package/dist/test/Insertion.perf.spec.js.map +1 -1
- package/dist/test/PartialLengths.perf.spec.js +18 -25
- package/dist/test/PartialLengths.perf.spec.js.map +1 -1
- package/dist/test/Removal.perf.spec.js +13 -41
- package/dist/test/Removal.perf.spec.js.map +1 -1
- package/dist/test/beastTest.spec.d.ts.map +1 -1
- package/dist/test/beastTest.spec.js +41 -66
- package/dist/test/beastTest.spec.js.map +1 -1
- package/dist/test/client.annotateMarker.spec.js +1 -11
- package/dist/test/client.annotateMarker.spec.js.map +1 -1
- package/dist/test/client.applyMsg.spec.js +14 -14
- package/dist/test/client.applyMsg.spec.js.map +1 -1
- package/dist/test/client.getPosition.spec.js +1 -1
- package/dist/test/client.getPosition.spec.js.map +1 -1
- package/dist/test/client.localReference.spec.js +1 -1
- package/dist/test/client.localReference.spec.js.map +1 -1
- package/dist/test/client.rollback.spec.js +49 -58
- package/dist/test/client.rollback.spec.js.map +1 -1
- package/dist/test/client.rollbackFarm.spec.js +1 -1
- package/dist/test/client.rollbackFarm.spec.js.map +1 -1
- package/dist/test/client.searchForMarker.spec.js +4 -21
- package/dist/test/client.searchForMarker.spec.js.map +1 -1
- package/dist/test/index.d.ts +2 -2
- package/dist/test/index.d.ts.map +1 -1
- package/dist/test/index.js +2 -6
- package/dist/test/index.js.map +1 -1
- package/dist/test/mergeTree.annotate.deltaCallback.spec.js +14 -59
- package/dist/test/mergeTree.annotate.deltaCallback.spec.js.map +1 -1
- package/dist/test/mergeTree.annotate.spec.js +47 -63
- package/dist/test/mergeTree.annotate.spec.js.map +1 -1
- package/dist/test/mergeTree.insert.deltaCallback.spec.js +9 -62
- package/dist/test/mergeTree.insert.deltaCallback.spec.js.map +1 -1
- package/dist/test/mergeTree.insertingWalk.spec.js +59 -125
- package/dist/test/mergeTree.insertingWalk.spec.js.map +1 -1
- package/dist/test/mergeTree.markRangeRemoved.deltaCallback.spec.js +12 -93
- package/dist/test/mergeTree.markRangeRemoved.deltaCallback.spec.js.map +1 -1
- package/dist/test/mergeTree.markRangeRemoved.spec.js +10 -7
- package/dist/test/mergeTree.markRangeRemoved.spec.js.map +1 -1
- package/dist/test/mergeTree.walk.spec.js +2 -14
- package/dist/test/mergeTree.walk.spec.js.map +1 -1
- package/dist/test/mergeTreeOperationRunner.js +2 -2
- package/dist/test/mergeTreeOperationRunner.js.map +1 -1
- package/dist/test/obliterate.concurrent.spec.js +18 -23
- package/dist/test/obliterate.concurrent.spec.js.map +1 -1
- package/dist/test/obliterate.partialLength.spec.js +166 -136
- package/dist/test/obliterate.partialLength.spec.js.map +1 -1
- package/dist/test/obliterate.spec.js +16 -126
- package/dist/test/obliterate.spec.js.map +1 -1
- package/dist/test/partialLength.spec.js +28 -196
- package/dist/test/partialLength.spec.js.map +1 -1
- package/dist/test/perspective.spec.js +34 -0
- package/dist/test/perspective.spec.js.map +1 -1
- package/dist/test/propertyManager.spec.js +1 -1
- package/dist/test/propertyManager.spec.js.map +1 -1
- package/dist/test/resetPendingSegmentsToOp.spec.js +0 -2
- package/dist/test/resetPendingSegmentsToOp.spec.js.map +1 -1
- package/dist/test/segmentGroupCollection.spec.js +10 -4
- package/dist/test/segmentGroupCollection.spec.js.map +1 -1
- package/dist/test/testClient.d.ts +1 -0
- package/dist/test/testClient.d.ts.map +1 -1
- package/dist/test/testClient.js +16 -26
- package/dist/test/testClient.js.map +1 -1
- package/dist/test/testClientLogger.d.ts.map +1 -1
- package/dist/test/testClientLogger.js +3 -10
- package/dist/test/testClientLogger.js.map +1 -1
- package/dist/test/testServer.d.ts +2 -1
- package/dist/test/testServer.d.ts.map +1 -1
- package/dist/test/testServer.js +7 -5
- package/dist/test/testServer.js.map +1 -1
- package/dist/test/testUtils.d.ts +36 -56
- package/dist/test/testUtils.d.ts.map +1 -1
- package/dist/test/testUtils.js +68 -77
- package/dist/test/testUtils.js.map +1 -1
- package/dist/test/text.d.ts +2 -2
- package/dist/test/text.d.ts.map +1 -1
- package/dist/test/text.js +5 -2
- package/dist/test/text.js.map +1 -1
- package/dist/textSegment.d.ts +0 -6
- package/dist/textSegment.d.ts.map +1 -1
- package/dist/textSegment.js.map +1 -1
- package/dist/zamboni.d.ts.map +1 -1
- package/dist/zamboni.js +53 -26
- package/dist/zamboni.js.map +1 -1
- package/lib/MergeTreeTextHelper.d.ts +9 -3
- package/lib/MergeTreeTextHelper.d.ts.map +1 -1
- package/lib/MergeTreeTextHelper.js +5 -5
- package/lib/MergeTreeTextHelper.js.map +1 -1
- package/lib/client.d.ts +7 -13
- package/lib/client.d.ts.map +1 -1
- package/lib/client.js +117 -116
- package/lib/client.js.map +1 -1
- package/lib/endOfTreeSegment.d.ts +12 -8
- package/lib/endOfTreeSegment.d.ts.map +1 -1
- package/lib/endOfTreeSegment.js +2 -4
- package/lib/endOfTreeSegment.js.map +1 -1
- package/lib/index.d.ts +6 -3
- package/lib/index.d.ts.map +1 -1
- package/lib/index.js +1 -1
- package/lib/index.js.map +1 -1
- package/lib/mergeTree.d.ts +37 -23
- package/lib/mergeTree.d.ts.map +1 -1
- package/lib/mergeTree.js +381 -488
- package/lib/mergeTree.js.map +1 -1
- package/lib/mergeTreeDeltaCallback.d.ts +4 -8
- package/lib/mergeTreeDeltaCallback.d.ts.map +1 -1
- package/lib/mergeTreeDeltaCallback.js.map +1 -1
- package/lib/mergeTreeNodes.d.ts +32 -10
- package/lib/mergeTreeNodes.d.ts.map +1 -1
- package/lib/mergeTreeNodes.js +42 -29
- package/lib/mergeTreeNodes.js.map +1 -1
- package/lib/partialLengths.d.ts +2 -2
- package/lib/partialLengths.d.ts.map +1 -1
- package/lib/partialLengths.js +160 -111
- package/lib/partialLengths.js.map +1 -1
- package/lib/perspective.d.ts +8 -27
- package/lib/perspective.d.ts.map +1 -1
- package/lib/perspective.js +8 -68
- package/lib/perspective.js.map +1 -1
- package/lib/revertibles.d.ts.map +1 -1
- package/lib/revertibles.js +2 -2
- package/lib/revertibles.js.map +1 -1
- package/lib/segmentInfos.d.ts +20 -106
- package/lib/segmentInfos.d.ts.map +1 -1
- package/lib/segmentInfos.js +26 -37
- package/lib/segmentInfos.js.map +1 -1
- package/lib/segmentPropertiesManager.d.ts +1 -14
- package/lib/segmentPropertiesManager.d.ts.map +1 -1
- package/lib/segmentPropertiesManager.js +2 -16
- package/lib/segmentPropertiesManager.js.map +1 -1
- package/lib/snapshotLoader.d.ts.map +1 -1
- package/lib/snapshotLoader.js +39 -19
- package/lib/snapshotLoader.js.map +1 -1
- package/lib/snapshotV1.d.ts.map +1 -1
- package/lib/snapshotV1.js +34 -26
- package/lib/snapshotV1.js.map +1 -1
- package/lib/snapshotlegacy.d.ts.map +1 -1
- package/lib/snapshotlegacy.js +7 -10
- package/lib/snapshotlegacy.js.map +1 -1
- package/lib/stamps.d.ts +1 -1
- package/lib/stamps.js +1 -1
- package/lib/stamps.js.map +1 -1
- package/lib/test/Insertion.perf.spec.js +6 -51
- package/lib/test/Insertion.perf.spec.js.map +1 -1
- package/lib/test/PartialLengths.perf.spec.js +18 -25
- package/lib/test/PartialLengths.perf.spec.js.map +1 -1
- package/lib/test/Removal.perf.spec.js +13 -41
- package/lib/test/Removal.perf.spec.js.map +1 -1
- package/lib/test/beastTest.spec.d.ts.map +1 -1
- package/lib/test/beastTest.spec.js +42 -67
- package/lib/test/beastTest.spec.js.map +1 -1
- package/lib/test/client.annotateMarker.spec.js +1 -11
- package/lib/test/client.annotateMarker.spec.js.map +1 -1
- package/lib/test/client.applyMsg.spec.js +14 -14
- package/lib/test/client.applyMsg.spec.js.map +1 -1
- package/lib/test/client.getPosition.spec.js +1 -1
- package/lib/test/client.getPosition.spec.js.map +1 -1
- package/lib/test/client.localReference.spec.js +1 -1
- package/lib/test/client.localReference.spec.js.map +1 -1
- package/lib/test/client.rollback.spec.js +50 -59
- package/lib/test/client.rollback.spec.js.map +1 -1
- package/lib/test/client.rollbackFarm.spec.js +1 -1
- package/lib/test/client.rollbackFarm.spec.js.map +1 -1
- package/lib/test/client.searchForMarker.spec.js +4 -21
- package/lib/test/client.searchForMarker.spec.js.map +1 -1
- package/lib/test/index.d.ts +2 -2
- package/lib/test/index.d.ts.map +1 -1
- package/lib/test/index.js +1 -1
- package/lib/test/index.js.map +1 -1
- package/lib/test/mergeTree.annotate.deltaCallback.spec.js +15 -60
- package/lib/test/mergeTree.annotate.deltaCallback.spec.js.map +1 -1
- package/lib/test/mergeTree.annotate.spec.js +48 -64
- package/lib/test/mergeTree.annotate.spec.js.map +1 -1
- package/lib/test/mergeTree.insert.deltaCallback.spec.js +10 -63
- package/lib/test/mergeTree.insert.deltaCallback.spec.js.map +1 -1
- package/lib/test/mergeTree.insertingWalk.spec.js +61 -127
- package/lib/test/mergeTree.insertingWalk.spec.js.map +1 -1
- package/lib/test/mergeTree.markRangeRemoved.deltaCallback.spec.js +13 -94
- package/lib/test/mergeTree.markRangeRemoved.deltaCallback.spec.js.map +1 -1
- package/lib/test/mergeTree.markRangeRemoved.spec.js +10 -7
- package/lib/test/mergeTree.markRangeRemoved.spec.js.map +1 -1
- package/lib/test/mergeTree.walk.spec.js +2 -14
- package/lib/test/mergeTree.walk.spec.js.map +1 -1
- package/lib/test/mergeTreeOperationRunner.js +3 -3
- package/lib/test/mergeTreeOperationRunner.js.map +1 -1
- package/lib/test/obliterate.concurrent.spec.js +18 -23
- package/lib/test/obliterate.concurrent.spec.js.map +1 -1
- package/lib/test/obliterate.partialLength.spec.js +167 -137
- package/lib/test/obliterate.partialLength.spec.js.map +1 -1
- package/lib/test/obliterate.spec.js +17 -127
- package/lib/test/obliterate.spec.js.map +1 -1
- package/lib/test/partialLength.spec.js +29 -197
- package/lib/test/partialLength.spec.js.map +1 -1
- package/lib/test/perspective.spec.js +34 -0
- package/lib/test/perspective.spec.js.map +1 -1
- package/lib/test/propertyManager.spec.js +2 -2
- package/lib/test/propertyManager.spec.js.map +1 -1
- package/lib/test/resetPendingSegmentsToOp.spec.js +0 -2
- package/lib/test/resetPendingSegmentsToOp.spec.js.map +1 -1
- package/lib/test/segmentGroupCollection.spec.js +10 -4
- package/lib/test/segmentGroupCollection.spec.js.map +1 -1
- package/lib/test/testClient.d.ts +1 -0
- package/lib/test/testClient.d.ts.map +1 -1
- package/lib/test/testClient.js +18 -28
- package/lib/test/testClient.js.map +1 -1
- package/lib/test/testClientLogger.d.ts.map +1 -1
- package/lib/test/testClientLogger.js +3 -10
- package/lib/test/testClientLogger.js.map +1 -1
- package/lib/test/testServer.d.ts +2 -1
- package/lib/test/testServer.d.ts.map +1 -1
- package/lib/test/testServer.js +7 -5
- package/lib/test/testServer.js.map +1 -1
- package/lib/test/testUtils.d.ts +36 -56
- package/lib/test/testUtils.d.ts.map +1 -1
- package/lib/test/testUtils.js +66 -48
- package/lib/test/testUtils.js.map +1 -1
- package/lib/test/text.d.ts +2 -2
- package/lib/test/text.d.ts.map +1 -1
- package/lib/test/text.js +6 -3
- package/lib/test/text.js.map +1 -1
- package/lib/textSegment.d.ts +0 -6
- package/lib/textSegment.d.ts.map +1 -1
- package/lib/textSegment.js.map +1 -1
- package/lib/tsdoc-metadata.json +1 -1
- package/lib/zamboni.d.ts.map +1 -1
- package/lib/zamboni.js +32 -28
- package/lib/zamboni.js.map +1 -1
- package/package.json +17 -20
- package/src/MergeTreeTextHelper.ts +17 -12
- package/src/client.ts +141 -197
- package/src/endOfTreeSegment.ts +11 -8
- package/src/index.ts +4 -3
- package/src/mergeTree.ts +482 -633
- package/src/mergeTreeDeltaCallback.ts +4 -8
- package/src/mergeTreeNodes.ts +66 -45
- package/src/partialLengths.ts +181 -137
- package/src/perspective.ts +17 -95
- package/src/revertibles.ts +2 -7
- package/src/segmentInfos.ts +48 -141
- package/src/segmentPropertiesManager.ts +2 -16
- package/src/snapshotLoader.ts +62 -30
- package/src/snapshotV1.ts +36 -28
- package/src/snapshotlegacy.ts +7 -16
- package/src/stamps.ts +1 -1
- package/src/textSegment.ts +0 -13
- package/src/zamboni.ts +38 -32
- package/tsconfig.json +1 -0
- package/prettier.config.cjs +0 -8
package/src/partialLengths.ts
CHANGED
|
@@ -5,22 +5,22 @@
|
|
|
5
5
|
|
|
6
6
|
import { assert } from "@fluidframework/core-utils/internal";
|
|
7
7
|
|
|
8
|
-
import { UnassignedSequenceNumber } from "./constants.js";
|
|
9
8
|
import { MergeTree } from "./mergeTree.js";
|
|
10
9
|
import {
|
|
11
10
|
CollaborationWindow,
|
|
11
|
+
getMinSeqStamp,
|
|
12
12
|
IMergeNode,
|
|
13
13
|
ISegmentPrivate,
|
|
14
|
-
seqLTE,
|
|
15
14
|
type MergeBlock,
|
|
16
15
|
} from "./mergeTreeNodes.js";
|
|
17
16
|
import {
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
} from "./segmentInfos.js";
|
|
17
|
+
LocalDefaultPerspective,
|
|
18
|
+
LocalReconnectingPerspective,
|
|
19
|
+
PriorPerspective,
|
|
20
|
+
} from "./perspective.js";
|
|
21
|
+
import { toRemovalInfo, assertInserted, wasRemovedOnInsert } from "./segmentInfos.js";
|
|
23
22
|
import { SortedSet } from "./sortedSet.js";
|
|
23
|
+
import * as opstampUtils from "./stamps.js";
|
|
24
24
|
|
|
25
25
|
class PartialSequenceLengthsSet extends SortedSet<PartialSequenceLength> {
|
|
26
26
|
protected compare(a: PartialSequenceLength, b: PartialSequenceLength): number {
|
|
@@ -477,8 +477,8 @@ export class PartialSequenceLengths {
|
|
|
477
477
|
if (child.isLeaf()) {
|
|
478
478
|
// Leaf segment
|
|
479
479
|
const segment = child;
|
|
480
|
-
if (
|
|
481
|
-
PartialSequenceLengths.
|
|
480
|
+
if (wasRemovedOnInsert(segment)) {
|
|
481
|
+
PartialSequenceLengths.accountForRemoveOnInsert(
|
|
482
482
|
combinedPartialLengths,
|
|
483
483
|
segment,
|
|
484
484
|
collabWindow,
|
|
@@ -504,30 +504,32 @@ export class PartialSequenceLengths {
|
|
|
504
504
|
}
|
|
505
505
|
|
|
506
506
|
/**
|
|
507
|
-
* Assuming this segment was
|
|
507
|
+
* Assuming this segment was removed on insertion, inserts length information about that operation
|
|
508
508
|
* into the appropriate per-client adjustments (the overall view needs no such adjustment since
|
|
509
509
|
* from an observing client's perspective, the segment never exists).
|
|
510
510
|
*/
|
|
511
|
-
private static
|
|
511
|
+
private static accountForRemoveOnInsert(
|
|
512
512
|
combinedPartialLengths: PartialSequenceLengths,
|
|
513
513
|
segment: ISegmentPrivate,
|
|
514
514
|
collabWindow: CollaborationWindow,
|
|
515
515
|
): void {
|
|
516
516
|
assertInserted(segment);
|
|
517
|
-
const
|
|
517
|
+
const removeInfo = toRemovalInfo(segment);
|
|
518
518
|
assert(
|
|
519
|
-
|
|
520
|
-
0xab7 /* Segment was not
|
|
519
|
+
removeInfo !== undefined && wasRemovedOnInsert(segment),
|
|
520
|
+
0xab7 /* Segment was not removed on insert */,
|
|
521
521
|
);
|
|
522
|
-
|
|
522
|
+
const firstRemove = removeInfo?.removes[0];
|
|
523
|
+
if (opstampUtils.lte(firstRemove, getMinSeqStamp(collabWindow))) {
|
|
523
524
|
// This segment was obliterated as soon as it was inserted, and everyone was aware of the obliterate.
|
|
524
525
|
// Thus every single client treats this segment as length 0 from every perspective, and no adjustments
|
|
525
526
|
// are necessary.
|
|
526
527
|
return;
|
|
527
528
|
}
|
|
528
529
|
|
|
529
|
-
const
|
|
530
|
-
const
|
|
530
|
+
const { insert, cachedLength } = segment;
|
|
531
|
+
const isLocal = opstampUtils.isLocal(insert);
|
|
532
|
+
const { clientId } = insert;
|
|
531
533
|
|
|
532
534
|
const partials = isLocal
|
|
533
535
|
? combinedPartialLengths.unsequencedRecords?.partialLengths
|
|
@@ -539,40 +541,58 @@ export class PartialSequenceLengths {
|
|
|
539
541
|
|
|
540
542
|
if (isLocal) {
|
|
541
543
|
// Implication -> this is a local segment which will be obliterated as soon as it is acked.
|
|
542
|
-
// For refSeqs preceding that
|
|
544
|
+
// For refSeqs preceding that removedSeq and localSeqs following the localSeq, it will be visible.
|
|
543
545
|
// For the rest, it will not be visible.
|
|
544
546
|
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
545
|
-
const localSeq =
|
|
547
|
+
const localSeq = insert.localSeq!;
|
|
546
548
|
partials.addOrUpdate({
|
|
547
549
|
seq: localSeq,
|
|
548
550
|
len: 0,
|
|
549
|
-
seglen:
|
|
551
|
+
seglen: cachedLength,
|
|
550
552
|
clientId,
|
|
551
553
|
});
|
|
552
554
|
|
|
553
555
|
combinedPartialLengths.addLocalAdjustment({
|
|
554
|
-
refSeq:
|
|
556
|
+
refSeq: firstRemove.seq,
|
|
555
557
|
localSeq,
|
|
556
|
-
seglen: -
|
|
558
|
+
seglen: -cachedLength,
|
|
557
559
|
});
|
|
560
|
+
|
|
561
|
+
const lastRemove = removeInfo.removes[removeInfo.removes.length - 1];
|
|
562
|
+
if (opstampUtils.isLocal(lastRemove)) {
|
|
563
|
+
// In addition to a remote sliceRemove causing this segment to be removed as soon as its insertion is acked,
|
|
564
|
+
// the local client has also removed it before its insertion was acked.
|
|
565
|
+
// It will therefore not be visible for a local reconnecting perspective beyond the removed localSeq.
|
|
566
|
+
partials.addOrUpdate({
|
|
567
|
+
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
568
|
+
seq: lastRemove.localSeq!,
|
|
569
|
+
len: 0,
|
|
570
|
+
seglen: -cachedLength,
|
|
571
|
+
clientId,
|
|
572
|
+
});
|
|
573
|
+
|
|
574
|
+
combinedPartialLengths.addLocalAdjustment({
|
|
575
|
+
refSeq: firstRemove.seq,
|
|
576
|
+
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
577
|
+
localSeq: lastRemove.localSeq!,
|
|
578
|
+
seglen: cachedLength,
|
|
579
|
+
});
|
|
580
|
+
}
|
|
558
581
|
} else {
|
|
559
582
|
// Segment was obliterated on insert. Generally this means it should be visible only to the
|
|
560
583
|
// inserting client (in which case we add an adjustment to only that client's perspective),
|
|
561
584
|
// but if that client has also removed it, we don't need to add anything.
|
|
562
|
-
const removeInfo = toRemovalInfo(segment);
|
|
563
|
-
|
|
564
585
|
const wasRemovedByInsertingClient =
|
|
565
|
-
removeInfo !== undefined &&
|
|
566
|
-
|
|
567
|
-
moveInfo !== undefined && moveInfo.movedClientIds.includes(clientId);
|
|
586
|
+
removeInfo !== undefined &&
|
|
587
|
+
removeInfo.removes.some((remove) => remove.clientId === clientId);
|
|
568
588
|
|
|
569
|
-
if (!wasRemovedByInsertingClient
|
|
570
|
-
const
|
|
589
|
+
if (!wasRemovedByInsertingClient) {
|
|
590
|
+
const removeSeq = firstRemove?.seq;
|
|
571
591
|
assert(
|
|
572
|
-
|
|
573
|
-
0xab8 /* ObliterateOnInsertion implies
|
|
592
|
+
removeSeq !== undefined,
|
|
593
|
+
0xab8 /* ObliterateOnInsertion implies removeSeq is defined */,
|
|
574
594
|
);
|
|
575
|
-
combinedPartialLengths.addClientAdjustment(clientId,
|
|
595
|
+
combinedPartialLengths.addClientAdjustment(clientId, removeSeq, cachedLength);
|
|
576
596
|
}
|
|
577
597
|
}
|
|
578
598
|
}
|
|
@@ -587,16 +607,17 @@ export class PartialSequenceLengths {
|
|
|
587
607
|
collabWindow: CollaborationWindow,
|
|
588
608
|
): void {
|
|
589
609
|
assertInserted(segment);
|
|
590
|
-
if (
|
|
610
|
+
if (opstampUtils.lte(segment.insert, getMinSeqStamp(collabWindow))) {
|
|
591
611
|
combinedPartialLengths.minLength += segment.cachedLength;
|
|
592
612
|
return;
|
|
593
613
|
}
|
|
594
614
|
|
|
595
|
-
const
|
|
615
|
+
const { insert, cachedLength: segmentLen } = segment;
|
|
616
|
+
|
|
617
|
+
const isLocal = opstampUtils.isLocal(insert);
|
|
596
618
|
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
597
|
-
const seqOrLocalSeq = isLocal ?
|
|
598
|
-
const
|
|
599
|
-
const clientId = segment.clientId;
|
|
619
|
+
const seqOrLocalSeq = isLocal ? insert.localSeq! : insert.seq;
|
|
620
|
+
const clientId = insert.clientId;
|
|
600
621
|
|
|
601
622
|
const partials = isLocal
|
|
602
623
|
? combinedPartialLengths.unsequencedRecords?.partialLengths
|
|
@@ -636,76 +657,35 @@ export class PartialSequenceLengths {
|
|
|
636
657
|
assertInserted(segment);
|
|
637
658
|
|
|
638
659
|
const removalInfo = toRemovalInfo(segment);
|
|
639
|
-
|
|
640
|
-
if (!removalInfo && !moveInfo) {
|
|
660
|
+
if (!removalInfo) {
|
|
641
661
|
return;
|
|
642
662
|
}
|
|
643
663
|
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
(moveInfo?.movedSeq !== undefined && seqLTE(moveInfo.movedSeq, collabWindow.minSeq))
|
|
648
|
-
) {
|
|
664
|
+
const firstRemove = removalInfo?.removes[0];
|
|
665
|
+
const minSeqStamp = getMinSeqStamp(collabWindow);
|
|
666
|
+
if (firstRemove !== undefined && opstampUtils.lte(firstRemove, minSeqStamp)) {
|
|
649
667
|
combinedPartialLengths.minLength -= segment.cachedLength;
|
|
650
668
|
return;
|
|
651
669
|
}
|
|
652
670
|
|
|
653
|
-
const removalIsLocal =
|
|
654
|
-
|
|
655
|
-
const
|
|
656
|
-
const
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
if (
|
|
662
|
-
segment.seq === UnassignedSequenceNumber &&
|
|
663
|
-
!(removalIsLocal && (!moveInfo || moveIsLocal)) &&
|
|
664
|
-
!(moveIsLocal && (!removalInfo || removalIsLocal))
|
|
665
|
-
) {
|
|
666
|
-
throw new Error("Should have handled this codepath in wasMovedOnInsertion");
|
|
671
|
+
const removalIsLocal = !!firstRemove && opstampUtils.isLocal(firstRemove);
|
|
672
|
+
const isLocalInsertion = opstampUtils.isLocal(segment.insert);
|
|
673
|
+
const isOnlyLocalRemoval = removalIsLocal;
|
|
674
|
+
const isLocal = isLocalInsertion || isOnlyLocalRemoval;
|
|
675
|
+
|
|
676
|
+
if (isLocalInsertion && !removalIsLocal) {
|
|
677
|
+
throw new Error("Should have handled this codepath in wasRemovedOnInsertion");
|
|
667
678
|
}
|
|
668
679
|
|
|
669
680
|
const lenDelta = -segment.cachedLength;
|
|
670
|
-
|
|
671
|
-
|
|
681
|
+
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
682
|
+
const seqOrLocalSeq = removalIsLocal ? firstRemove.localSeq! : firstRemove.seq;
|
|
683
|
+
const clientId = firstRemove.clientId;
|
|
672
684
|
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
(!moveIsLocal && !removalIsLocal) || moveIsLocal !== removalIsLocal,
|
|
676
|
-
0x870 /* overlapping local obliterate and remove */,
|
|
685
|
+
const clientsWithRemoveOrObliterate = new Set<number>(
|
|
686
|
+
removalInfo?.removes.map((stamp) => stamp.clientId),
|
|
677
687
|
);
|
|
678
688
|
|
|
679
|
-
const clientsWithRemoveOrObliterate = new Set<number>([
|
|
680
|
-
...(removalInfo?.removedClientIds ?? []),
|
|
681
|
-
...(moveInfo?.movedClientIds ?? []),
|
|
682
|
-
]);
|
|
683
|
-
|
|
684
|
-
const removeHappenedFirst =
|
|
685
|
-
removalInfo &&
|
|
686
|
-
(!moveInfo ||
|
|
687
|
-
moveIsLocal ||
|
|
688
|
-
(!removalIsLocal && moveInfo.movedSeq > removalInfo.removedSeq));
|
|
689
|
-
|
|
690
|
-
if (removeHappenedFirst) {
|
|
691
|
-
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
692
|
-
seqOrLocalSeq = removalIsLocal ? removalInfo.localRemovedSeq! : removalInfo.removedSeq;
|
|
693
|
-
// The client who performed the remove is always stored
|
|
694
|
-
// in the first position of removalInfo.
|
|
695
|
-
clientId = removalInfo.removedClientIds[0];
|
|
696
|
-
} else {
|
|
697
|
-
assert(
|
|
698
|
-
moveInfo !== undefined,
|
|
699
|
-
0xab9 /* Expected move to exist if remove either did not exist or didn't happen first */,
|
|
700
|
-
);
|
|
701
|
-
// The client who performed the move is always stored
|
|
702
|
-
// in the first position of moveInfo.
|
|
703
|
-
clientId = moveInfo.movedClientIds[0];
|
|
704
|
-
|
|
705
|
-
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
706
|
-
seqOrLocalSeq = moveIsLocal ? moveInfo.localMovedSeq! : moveInfo.movedSeq;
|
|
707
|
-
}
|
|
708
|
-
|
|
709
689
|
const partials = isLocal
|
|
710
690
|
? combinedPartialLengths.unsequencedRecords?.partialLengths
|
|
711
691
|
: combinedPartialLengths.partialLengths;
|
|
@@ -715,15 +695,31 @@ export class PartialSequenceLengths {
|
|
|
715
695
|
}
|
|
716
696
|
|
|
717
697
|
if (isLocal) {
|
|
718
|
-
// The segment is either inserted only locally or removed
|
|
698
|
+
// The segment is either inserted only locally or removed only locally.
|
|
719
699
|
// We already accounted for the insertion in the accountForInsertion codepath.
|
|
720
|
-
// Only thing left to do is account for the removal.
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
700
|
+
// Only thing left to do is account for the removal in local partial lengths.
|
|
701
|
+
// One thing to be careful about is that the removal should only apply to perspectives that saw
|
|
702
|
+
// the segment's insertion in the first place.
|
|
703
|
+
// When the segment's insertion was common knowledge (at or below minSeq) or also by the local client,
|
|
704
|
+
// all possible perspectives will have seen it.
|
|
705
|
+
if (
|
|
706
|
+
opstampUtils.lte(segment.insert, minSeqStamp) ||
|
|
707
|
+
collabWindow.clientId === segment.insert.clientId
|
|
708
|
+
) {
|
|
709
|
+
partials.addOrUpdate({
|
|
710
|
+
seq: seqOrLocalSeq,
|
|
711
|
+
clientId,
|
|
712
|
+
len: 0,
|
|
713
|
+
seglen: lenDelta,
|
|
714
|
+
});
|
|
715
|
+
} else {
|
|
716
|
+
// ... otherwise, it's only visible to reconnecting perspectives above the seq of the insert.
|
|
717
|
+
combinedPartialLengths.addLocalAdjustment({
|
|
718
|
+
localSeq: seqOrLocalSeq,
|
|
719
|
+
refSeq: segment.insert.seq,
|
|
720
|
+
seglen: lenDelta,
|
|
721
|
+
});
|
|
722
|
+
}
|
|
727
723
|
} else {
|
|
728
724
|
partials.addOrUpdate({
|
|
729
725
|
seq: seqOrLocalSeq,
|
|
@@ -735,7 +731,7 @@ export class PartialSequenceLengths {
|
|
|
735
731
|
for (const id of clientsWithRemoveOrObliterate) {
|
|
736
732
|
if (id === collabWindow.clientId) {
|
|
737
733
|
// The local client also removed or obliterated this segment.
|
|
738
|
-
const localSeq =
|
|
734
|
+
const { localSeq } = removalInfo.removes[removalInfo.removes.length - 1];
|
|
739
735
|
if (localSeq === undefined) {
|
|
740
736
|
// Sure, the local client did it--but that change was already acked.
|
|
741
737
|
// No need to account for it in the unsequenced records.
|
|
@@ -748,15 +744,53 @@ export class PartialSequenceLengths {
|
|
|
748
744
|
}
|
|
749
745
|
assert(
|
|
750
746
|
localSeq !== undefined,
|
|
751
|
-
0xaba /* Local client was in
|
|
747
|
+
0xaba /* Local client was in removed client ids but segment has no local seq for either */,
|
|
752
748
|
);
|
|
753
749
|
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
750
|
+
// Here...
|
|
751
|
+
// - The segment was removed locally at `localSeq`
|
|
752
|
+
// - The segment was also removed remotely at `seqOrLocalSeq`
|
|
753
|
+
// - We're ensuring that partial lengths works for arbitrary `LocalReconnectingPerspective`s.
|
|
754
|
+
//
|
|
755
|
+
// Visualize an arbitrary local reconnecting perspective in 2d space, where the x-axis is `seq` and the y-axis is `localSeq`.
|
|
756
|
+
// The events that have occurred to this segment divide the space into up to 6 regions:
|
|
757
|
+
//
|
|
758
|
+
// (localSeq)
|
|
759
|
+
// | | |
|
|
760
|
+
// | 1 | 2 | 3
|
|
761
|
+
// local remove |-----------------------------
|
|
762
|
+
// | | |
|
|
763
|
+
// | 4 | 5 | 6
|
|
764
|
+
// |----------------------------- (seq)
|
|
765
|
+
// insert remove
|
|
766
|
+
// In all regions but region 5, the segment has length 0 (it was not inserted yet in regions 1 or 4, and removed locally and/or remotely
|
|
767
|
+
// in regions 2, 3, and 6).
|
|
768
|
+
// `accountForInsertion` already added a partial lengths adjustment of +segment.cachedLength for regions 2, 3, 5, and 6.
|
|
769
|
+
// Above in this function, we've added a partial lengths adjustment of -segment.cachedLength for regions 3 and 6.
|
|
770
|
+
//
|
|
771
|
+
// Note that in this picture:
|
|
772
|
+
// - Adding entries to `unsequencedRecords.partialLengths` is like adding adjustments that affect anything above a given Y value
|
|
773
|
+
// - Adding entries to `combinedPartialLengths.partialLengths` is like adding adjustments that affect anything above a given X value
|
|
774
|
+
// - Adding entries with `addLocalAdjustment` is like adding adjustments that affect anything above a given X *and* Y value
|
|
775
|
+
// The remainder this block adds the necessary adjustments to make the length appear 0 in region 2 as well, keeping in mind that
|
|
776
|
+
// region 1 may or may not exist depending on if the insertion is in the collab window.
|
|
777
|
+
if (
|
|
778
|
+
opstampUtils.isAcked(segment.insert) &&
|
|
779
|
+
opstampUtils.greaterThan(segment.insert, minSeqStamp)
|
|
780
|
+
) {
|
|
781
|
+
combinedPartialLengths.addLocalAdjustment({
|
|
782
|
+
refSeq: segment.insert.seq,
|
|
783
|
+
localSeq,
|
|
784
|
+
seglen: lenDelta,
|
|
785
|
+
});
|
|
786
|
+
} else {
|
|
787
|
+
unsequencedRecords.partialLengths.addOrUpdate({
|
|
788
|
+
seq: localSeq,
|
|
789
|
+
clientId: collabWindow.clientId,
|
|
790
|
+
seglen: lenDelta,
|
|
791
|
+
len: 0,
|
|
792
|
+
});
|
|
793
|
+
}
|
|
760
794
|
|
|
761
795
|
// Because we've included deltas which take effect when either of localSeq or refSeq are high enough,
|
|
762
796
|
// we need to offset this with an adjustment that takes effect when both are high enough.
|
|
@@ -772,16 +806,23 @@ export class PartialSequenceLengths {
|
|
|
772
806
|
});
|
|
773
807
|
} else {
|
|
774
808
|
// Note that all clients that have a remove or obliterate operation on this segment
|
|
775
|
-
// use the seq of the winning
|
|
809
|
+
// use the seq of the winning obliterate in their per-client adjustments!
|
|
776
810
|
combinedPartialLengths.addClientAdjustment(id, seqOrLocalSeq, lenDelta);
|
|
777
811
|
|
|
778
812
|
// Also ensure that all these clients have seen the segment as inserted before being removed
|
|
779
|
-
// This is technically not necessary for
|
|
813
|
+
// This is technically not necessary for setRemoves (we never ask for the length of this block with
|
|
780
814
|
// respect to a refSeq which this entry would affect), but it's simpler to just add it here.
|
|
781
815
|
// We already add this entry as part of the accountForInsertion codepath for the client that
|
|
782
816
|
// actually did insert the segment, hence not doing so [again] here.
|
|
783
|
-
if (
|
|
784
|
-
|
|
817
|
+
if (
|
|
818
|
+
opstampUtils.greaterThan(segment.insert, minSeqStamp) &&
|
|
819
|
+
id !== segment.insert.clientId
|
|
820
|
+
) {
|
|
821
|
+
combinedPartialLengths.addClientAdjustment(
|
|
822
|
+
id,
|
|
823
|
+
segment.insert.seq,
|
|
824
|
+
segment.cachedLength,
|
|
825
|
+
);
|
|
785
826
|
}
|
|
786
827
|
}
|
|
787
828
|
}
|
|
@@ -842,17 +883,16 @@ export class PartialSequenceLengths {
|
|
|
842
883
|
if (child.isLeaf()) {
|
|
843
884
|
const segment = child;
|
|
844
885
|
const removalInfo = toRemovalInfo(segment);
|
|
845
|
-
const
|
|
846
|
-
if (seq === segment.seq) {
|
|
847
|
-
// if this segment was
|
|
886
|
+
const firstRemove = removalInfo?.removes[0];
|
|
887
|
+
if (seq === segment.insert.seq) {
|
|
888
|
+
// if this segment was removed on insert, its length should
|
|
848
889
|
// only be visible to the inserting client
|
|
849
890
|
if (
|
|
850
|
-
segment.seq !== undefined &&
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
wasMovedOnInsert(segment)
|
|
891
|
+
segment.insert.seq !== undefined &&
|
|
892
|
+
firstRemove !== undefined &&
|
|
893
|
+
wasRemovedOnInsert(segment)
|
|
854
894
|
) {
|
|
855
|
-
this.addClientAdjustment(clientId,
|
|
895
|
+
this.addClientAdjustment(clientId, firstRemove.seq, segment.cachedLength);
|
|
856
896
|
failIncrementalPropagation = true;
|
|
857
897
|
} else {
|
|
858
898
|
seqSeglen += segment.cachedLength;
|
|
@@ -860,16 +900,15 @@ export class PartialSequenceLengths {
|
|
|
860
900
|
}
|
|
861
901
|
}
|
|
862
902
|
|
|
863
|
-
|
|
864
|
-
removalInfo?.removedSeq ?? Number.MAX_VALUE,
|
|
865
|
-
moveInfo?.movedSeq ?? Number.MAX_VALUE,
|
|
866
|
-
);
|
|
867
|
-
if (segment.seq !== UnassignedSequenceNumber && seq === earlierDeletion) {
|
|
903
|
+
if (opstampUtils.isAcked(segment.insert) && seq === firstRemove?.seq) {
|
|
868
904
|
seqSeglen -= segment.cachedLength;
|
|
869
905
|
if (clientId !== collabWindow.clientId) {
|
|
870
906
|
this.addClientAdjustment(clientId, seq, -segment.cachedLength);
|
|
871
|
-
if (
|
|
872
|
-
|
|
907
|
+
if (
|
|
908
|
+
opstampUtils.greaterThan(segment.insert, getMinSeqStamp(collabWindow)) &&
|
|
909
|
+
segment.insert.clientId !== clientId
|
|
910
|
+
) {
|
|
911
|
+
this.addClientAdjustment(clientId, segment.insert.seq, segment.cachedLength);
|
|
873
912
|
failIncrementalPropagation = true;
|
|
874
913
|
}
|
|
875
914
|
}
|
|
@@ -951,14 +990,13 @@ export class PartialSequenceLengths {
|
|
|
951
990
|
);
|
|
952
991
|
const unsequencedPartialLengths = this.unsequencedRecords.partialLengths;
|
|
953
992
|
// Local segments at or before localSeq should also be included
|
|
954
|
-
|
|
955
|
-
if (local) {
|
|
956
|
-
length += local.len;
|
|
993
|
+
length += unsequencedPartialLengths.latestLeq(localSeq)?.len ?? 0;
|
|
957
994
|
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
995
|
+
// Lastly, we must add in any additional adjustment that should only take effect when both
|
|
996
|
+
// refSeq AND localSeq are above some threshold. This accounts for things like double-counting
|
|
997
|
+
// local+remote removes, or only subtracting the length out of a local remove if we've also seen
|
|
998
|
+
// the insert of the segment it affects. (see addLocalAdjustment usages for examples)
|
|
999
|
+
length += this.computeOverallRefSeqAdjustment(refSeq, localSeq);
|
|
962
1000
|
}
|
|
963
1001
|
return length;
|
|
964
1002
|
}
|
|
@@ -1159,6 +1197,12 @@ export function verifyExpectedPartialLengths(
|
|
|
1159
1197
|
|
|
1160
1198
|
let expected = 0;
|
|
1161
1199
|
const nodesToVisit: IMergeNode[] = [node];
|
|
1200
|
+
const perspective =
|
|
1201
|
+
clientId === mergeTree.collabWindow.clientId
|
|
1202
|
+
? localSeq === undefined
|
|
1203
|
+
? new LocalDefaultPerspective(clientId)
|
|
1204
|
+
: new LocalReconnectingPerspective(refSeq, clientId, localSeq)
|
|
1205
|
+
: new PriorPerspective(refSeq, clientId);
|
|
1162
1206
|
|
|
1163
1207
|
while (nodesToVisit.length > 0) {
|
|
1164
1208
|
const thisNode = nodesToVisit.pop();
|
|
@@ -1166,7 +1210,7 @@ export function verifyExpectedPartialLengths(
|
|
|
1166
1210
|
continue;
|
|
1167
1211
|
}
|
|
1168
1212
|
if (thisNode.isLeaf()) {
|
|
1169
|
-
expected += mergeTree["nodeLength"](thisNode,
|
|
1213
|
+
expected += mergeTree["nodeLength"](thisNode, perspective) ?? 0;
|
|
1170
1214
|
} else {
|
|
1171
1215
|
nodesToVisit.push(...thisNode.children.slice(0, thisNode.childCount));
|
|
1172
1216
|
}
|
package/src/perspective.ts
CHANGED
|
@@ -3,18 +3,16 @@
|
|
|
3
3
|
* Licensed under the MIT License.
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
-
import { LeafAction, backwardExcursion, forwardExcursion } from "./mergeTreeNodeWalk.js";
|
|
9
|
-
import { seqLTE, type ISegmentLeaf } from "./mergeTreeNodes.js";
|
|
10
|
-
import { isInserted, toMoveInfo, toRemovalInfo } from "./segmentInfos.js";
|
|
6
|
+
import { seqLTE, type ISegment } from "./mergeTreeNodes.js";
|
|
7
|
+
import { isInserted, isRemoved } from "./segmentInfos.js";
|
|
11
8
|
import * as opstampUtils from "./stamps.js";
|
|
12
|
-
import type { OperationStamp,
|
|
9
|
+
import type { OperationStamp, RemoveOperationStamp } from "./stamps.js";
|
|
13
10
|
|
|
14
11
|
/**
|
|
15
12
|
* A perspective which includes some subset of operations known to the local client.
|
|
16
13
|
*
|
|
17
14
|
* This helps the local client reason about the state of other clients when they issued an operation.
|
|
15
|
+
* @internal
|
|
18
16
|
*/
|
|
19
17
|
export interface Perspective {
|
|
20
18
|
/**
|
|
@@ -47,104 +45,23 @@ export interface Perspective {
|
|
|
47
45
|
/**
|
|
48
46
|
* @returns Whether the segment is present (visible) from this perspective
|
|
49
47
|
*/
|
|
50
|
-
isSegmentPresent(segment:
|
|
48
|
+
isSegmentPresent(segment: ISegment): boolean;
|
|
51
49
|
|
|
52
50
|
/**
|
|
53
51
|
* @returns Whether this perspective has seen the given operation.
|
|
54
52
|
*/
|
|
55
|
-
hasOccurred(stamp:
|
|
56
|
-
|
|
57
|
-
nextSegment(mergeTree: MergeTree, segment: ISegmentLeaf, forward?: boolean): ISegmentLeaf;
|
|
58
|
-
previousSegment(mergeTree: MergeTree, segment: ISegmentLeaf): ISegmentLeaf;
|
|
53
|
+
hasOccurred(stamp: OperationStamp): boolean;
|
|
59
54
|
}
|
|
60
55
|
|
|
61
56
|
abstract class PerspectiveBase {
|
|
62
|
-
abstract hasOccurred(stamp:
|
|
57
|
+
abstract hasOccurred(stamp: OperationStamp): boolean;
|
|
63
58
|
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
* There may actually be multiple segments between the given segment and the returned segment,
|
|
67
|
-
* but they were either inserted after this perspective, or have been removed before this perspective.
|
|
68
|
-
*
|
|
69
|
-
* @param segment - The segment to start from.
|
|
70
|
-
* @param forward - The direction to search.
|
|
71
|
-
* @returns the next segment in the specified direction, or the start or end of the tree if there is no next segment.
|
|
72
|
-
*/
|
|
73
|
-
public nextSegment(
|
|
74
|
-
mergeTree: MergeTree,
|
|
75
|
-
segment: ISegmentLeaf,
|
|
76
|
-
forward: boolean = true,
|
|
77
|
-
): ISegmentLeaf {
|
|
78
|
-
let next: ISegmentLeaf | undefined;
|
|
79
|
-
const action = (seg: ISegmentLeaf): boolean | undefined => {
|
|
80
|
-
if (this.isSegmentPresent(seg)) {
|
|
81
|
-
next = seg;
|
|
82
|
-
return LeafAction.Exit;
|
|
83
|
-
}
|
|
84
|
-
};
|
|
85
|
-
(forward ? forwardExcursion : backwardExcursion)(segment, action);
|
|
86
|
-
return next ?? (forward ? mergeTree.endOfTree : mergeTree.startOfTree);
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
/**
|
|
90
|
-
* Finds the segment prior to the given segment.
|
|
91
|
-
* @param segment - The segment to start from.
|
|
92
|
-
* @returns the previous segment, or the start of the tree if there is no previous segment.
|
|
93
|
-
* @remarks This is a convenient equivalent to calling `nextSegment(segment, false)`.
|
|
94
|
-
*/
|
|
95
|
-
public previousSegment(mergeTree: MergeTree, segment: ISegmentLeaf): ISegmentLeaf {
|
|
96
|
-
return this.nextSegment(mergeTree, segment, false);
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
public isSegmentPresent(seg: ISegmentLeaf): boolean {
|
|
100
|
-
const insert: InsertOperationStamp = {
|
|
101
|
-
type: "insert",
|
|
102
|
-
clientId: seg.clientId,
|
|
103
|
-
seq: seg.seq,
|
|
104
|
-
localSeq: seg.localSeq,
|
|
105
|
-
};
|
|
106
|
-
if (isInserted(seg) && !this.hasOccurred(insert)) {
|
|
59
|
+
public isSegmentPresent(seg: ISegment): boolean {
|
|
60
|
+
if (isInserted(seg) && !this.hasOccurred(seg.insert)) {
|
|
107
61
|
return false;
|
|
108
62
|
}
|
|
109
63
|
|
|
110
|
-
|
|
111
|
-
const removalInfo = toRemovalInfo(seg);
|
|
112
|
-
if (removalInfo !== undefined) {
|
|
113
|
-
removes.push(
|
|
114
|
-
...removalInfo.removedClientIds.map((clientId) =>
|
|
115
|
-
(clientId === LocalClientId || clientId === 0) &&
|
|
116
|
-
removalInfo.localRemovedSeq !== undefined
|
|
117
|
-
? ({
|
|
118
|
-
type: "setRemove",
|
|
119
|
-
seq: UnassignedSequenceNumber,
|
|
120
|
-
clientId,
|
|
121
|
-
localSeq: removalInfo.localRemovedSeq,
|
|
122
|
-
} as const)
|
|
123
|
-
: // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
124
|
-
({ type: "setRemove", seq: removalInfo.removedSeq, clientId } as const),
|
|
125
|
-
),
|
|
126
|
-
);
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
const moveInfo = toMoveInfo(seg);
|
|
130
|
-
if (moveInfo !== undefined) {
|
|
131
|
-
removes.push(
|
|
132
|
-
...moveInfo.movedClientIds.map((clientId, index) =>
|
|
133
|
-
(clientId === LocalClientId || clientId === 0) &&
|
|
134
|
-
moveInfo.localMovedSeq !== undefined
|
|
135
|
-
? ({
|
|
136
|
-
type: "sliceRemove",
|
|
137
|
-
seq: UnassignedSequenceNumber,
|
|
138
|
-
clientId,
|
|
139
|
-
localSeq: moveInfo.localMovedSeq,
|
|
140
|
-
} as const)
|
|
141
|
-
: // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
142
|
-
({ type: "setRemove", seq: moveInfo.movedSeqs[index]!, clientId } as const),
|
|
143
|
-
),
|
|
144
|
-
);
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
if (removes.some((remove) => this.hasOccurred(remove))) {
|
|
64
|
+
if (isRemoved(seg) && seg.removes.some((remove) => this.hasOccurred(remove))) {
|
|
148
65
|
return false;
|
|
149
66
|
}
|
|
150
67
|
|
|
@@ -234,16 +151,21 @@ export class RemoteObliteratePerspective extends PerspectiveBase implements Pers
|
|
|
234
151
|
super();
|
|
235
152
|
}
|
|
236
153
|
|
|
237
|
-
public hasOccurred(stamp:
|
|
154
|
+
public hasOccurred(stamp: OperationStamp): boolean {
|
|
238
155
|
// Local-only removals are not visible to an obliterate operation, since this means the local removal was concurrent
|
|
239
156
|
// to a remote obliterate and we may need to mark the segment appropriately to reflect this overlapping remove.
|
|
240
157
|
// Every other type of operation is visible: obliterates do not affect segments that have already been removed and acked,
|
|
241
158
|
// and they always affect segments within their range that have not been removed, even if those segments were inserted
|
|
242
159
|
// after the obliterate's refSeq.
|
|
243
|
-
if (stamp
|
|
160
|
+
if (isRemoveOperationStamp(stamp) && opstampUtils.isLocal(stamp)) {
|
|
244
161
|
return false;
|
|
245
162
|
}
|
|
246
163
|
|
|
247
164
|
return true;
|
|
248
165
|
}
|
|
249
166
|
}
|
|
167
|
+
|
|
168
|
+
function isRemoveOperationStamp(stamp: OperationStamp): stamp is RemoveOperationStamp {
|
|
169
|
+
const { type } = stamp as unknown as RemoveOperationStamp;
|
|
170
|
+
return type === "setRemove" || type === "sliceRemove";
|
|
171
|
+
}
|