@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/lib/partialLengths.js
CHANGED
|
@@ -3,10 +3,11 @@
|
|
|
3
3
|
* Licensed under the MIT License.
|
|
4
4
|
*/
|
|
5
5
|
import { assert } from "@fluidframework/core-utils/internal";
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
-
import { toRemovalInfo,
|
|
6
|
+
import { getMinSeqStamp, } from "./mergeTreeNodes.js";
|
|
7
|
+
import { LocalDefaultPerspective, LocalReconnectingPerspective, PriorPerspective, } from "./perspective.js";
|
|
8
|
+
import { toRemovalInfo, assertInserted, wasRemovedOnInsert } from "./segmentInfos.js";
|
|
9
9
|
import { SortedSet } from "./sortedSet.js";
|
|
10
|
+
import * as opstampUtils from "./stamps.js";
|
|
10
11
|
class PartialSequenceLengthsSet extends SortedSet {
|
|
11
12
|
compare(a, b) {
|
|
12
13
|
return a.seq - b.seq;
|
|
@@ -308,8 +309,8 @@ export class PartialSequenceLengths {
|
|
|
308
309
|
if (child.isLeaf()) {
|
|
309
310
|
// Leaf segment
|
|
310
311
|
const segment = child;
|
|
311
|
-
if (
|
|
312
|
-
PartialSequenceLengths.
|
|
312
|
+
if (wasRemovedOnInsert(segment)) {
|
|
313
|
+
PartialSequenceLengths.accountForRemoveOnInsert(combinedPartialLengths, segment, collabWindow);
|
|
313
314
|
}
|
|
314
315
|
else {
|
|
315
316
|
PartialSequenceLengths.accountForInsertion(combinedPartialLengths, segment, collabWindow);
|
|
@@ -321,22 +322,24 @@ export class PartialSequenceLengths {
|
|
|
321
322
|
return combinedPartialLengths;
|
|
322
323
|
}
|
|
323
324
|
/**
|
|
324
|
-
* Assuming this segment was
|
|
325
|
+
* Assuming this segment was removed on insertion, inserts length information about that operation
|
|
325
326
|
* into the appropriate per-client adjustments (the overall view needs no such adjustment since
|
|
326
327
|
* from an observing client's perspective, the segment never exists).
|
|
327
328
|
*/
|
|
328
|
-
static
|
|
329
|
+
static accountForRemoveOnInsert(combinedPartialLengths, segment, collabWindow) {
|
|
329
330
|
assertInserted(segment);
|
|
330
|
-
const
|
|
331
|
-
assert(
|
|
332
|
-
|
|
331
|
+
const removeInfo = toRemovalInfo(segment);
|
|
332
|
+
assert(removeInfo !== undefined && wasRemovedOnInsert(segment), 0xab7 /* Segment was not removed on insert */);
|
|
333
|
+
const firstRemove = removeInfo?.removes[0];
|
|
334
|
+
if (opstampUtils.lte(firstRemove, getMinSeqStamp(collabWindow))) {
|
|
333
335
|
// This segment was obliterated as soon as it was inserted, and everyone was aware of the obliterate.
|
|
334
336
|
// Thus every single client treats this segment as length 0 from every perspective, and no adjustments
|
|
335
337
|
// are necessary.
|
|
336
338
|
return;
|
|
337
339
|
}
|
|
338
|
-
const
|
|
339
|
-
const
|
|
340
|
+
const { insert, cachedLength } = segment;
|
|
341
|
+
const isLocal = opstampUtils.isLocal(insert);
|
|
342
|
+
const { clientId } = insert;
|
|
340
343
|
const partials = isLocal
|
|
341
344
|
? combinedPartialLengths.unsequencedRecords?.partialLengths
|
|
342
345
|
: combinedPartialLengths.partialLengths;
|
|
@@ -346,33 +349,51 @@ export class PartialSequenceLengths {
|
|
|
346
349
|
}
|
|
347
350
|
if (isLocal) {
|
|
348
351
|
// Implication -> this is a local segment which will be obliterated as soon as it is acked.
|
|
349
|
-
// For refSeqs preceding that
|
|
352
|
+
// For refSeqs preceding that removedSeq and localSeqs following the localSeq, it will be visible.
|
|
350
353
|
// For the rest, it will not be visible.
|
|
351
354
|
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
352
|
-
const localSeq =
|
|
355
|
+
const localSeq = insert.localSeq;
|
|
353
356
|
partials.addOrUpdate({
|
|
354
357
|
seq: localSeq,
|
|
355
358
|
len: 0,
|
|
356
|
-
seglen:
|
|
359
|
+
seglen: cachedLength,
|
|
357
360
|
clientId,
|
|
358
361
|
});
|
|
359
362
|
combinedPartialLengths.addLocalAdjustment({
|
|
360
|
-
refSeq:
|
|
363
|
+
refSeq: firstRemove.seq,
|
|
361
364
|
localSeq,
|
|
362
|
-
seglen: -
|
|
365
|
+
seglen: -cachedLength,
|
|
363
366
|
});
|
|
367
|
+
const lastRemove = removeInfo.removes[removeInfo.removes.length - 1];
|
|
368
|
+
if (opstampUtils.isLocal(lastRemove)) {
|
|
369
|
+
// In addition to a remote sliceRemove causing this segment to be removed as soon as its insertion is acked,
|
|
370
|
+
// the local client has also removed it before its insertion was acked.
|
|
371
|
+
// It will therefore not be visible for a local reconnecting perspective beyond the removed localSeq.
|
|
372
|
+
partials.addOrUpdate({
|
|
373
|
+
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
374
|
+
seq: lastRemove.localSeq,
|
|
375
|
+
len: 0,
|
|
376
|
+
seglen: -cachedLength,
|
|
377
|
+
clientId,
|
|
378
|
+
});
|
|
379
|
+
combinedPartialLengths.addLocalAdjustment({
|
|
380
|
+
refSeq: firstRemove.seq,
|
|
381
|
+
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
382
|
+
localSeq: lastRemove.localSeq,
|
|
383
|
+
seglen: cachedLength,
|
|
384
|
+
});
|
|
385
|
+
}
|
|
364
386
|
}
|
|
365
387
|
else {
|
|
366
388
|
// Segment was obliterated on insert. Generally this means it should be visible only to the
|
|
367
389
|
// inserting client (in which case we add an adjustment to only that client's perspective),
|
|
368
390
|
// but if that client has also removed it, we don't need to add anything.
|
|
369
|
-
const
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
combinedPartialLengths.addClientAdjustment(clientId, moveSeq, segment.cachedLength);
|
|
391
|
+
const wasRemovedByInsertingClient = removeInfo !== undefined &&
|
|
392
|
+
removeInfo.removes.some((remove) => remove.clientId === clientId);
|
|
393
|
+
if (!wasRemovedByInsertingClient) {
|
|
394
|
+
const removeSeq = firstRemove?.seq;
|
|
395
|
+
assert(removeSeq !== undefined, 0xab8 /* ObliterateOnInsertion implies removeSeq is defined */);
|
|
396
|
+
combinedPartialLengths.addClientAdjustment(clientId, removeSeq, cachedLength);
|
|
376
397
|
}
|
|
377
398
|
}
|
|
378
399
|
}
|
|
@@ -382,15 +403,15 @@ export class PartialSequenceLengths {
|
|
|
382
403
|
*/
|
|
383
404
|
static accountForInsertion(combinedPartialLengths, segment, collabWindow) {
|
|
384
405
|
assertInserted(segment);
|
|
385
|
-
if (
|
|
406
|
+
if (opstampUtils.lte(segment.insert, getMinSeqStamp(collabWindow))) {
|
|
386
407
|
combinedPartialLengths.minLength += segment.cachedLength;
|
|
387
408
|
return;
|
|
388
409
|
}
|
|
389
|
-
const
|
|
410
|
+
const { insert, cachedLength: segmentLen } = segment;
|
|
411
|
+
const isLocal = opstampUtils.isLocal(insert);
|
|
390
412
|
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
391
|
-
const seqOrLocalSeq = isLocal ?
|
|
392
|
-
const
|
|
393
|
-
const clientId = segment.clientId;
|
|
413
|
+
const seqOrLocalSeq = isLocal ? insert.localSeq : insert.seq;
|
|
414
|
+
const clientId = insert.clientId;
|
|
394
415
|
const partials = isLocal
|
|
395
416
|
? combinedPartialLengths.unsequencedRecords?.partialLengths
|
|
396
417
|
: combinedPartialLengths.partialLengths;
|
|
@@ -423,55 +444,27 @@ export class PartialSequenceLengths {
|
|
|
423
444
|
static accountForRemoval(combinedPartialLengths, segment, collabWindow) {
|
|
424
445
|
assertInserted(segment);
|
|
425
446
|
const removalInfo = toRemovalInfo(segment);
|
|
426
|
-
|
|
427
|
-
if (!removalInfo && !moveInfo) {
|
|
447
|
+
if (!removalInfo) {
|
|
428
448
|
return;
|
|
429
449
|
}
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
450
|
+
const firstRemove = removalInfo?.removes[0];
|
|
451
|
+
const minSeqStamp = getMinSeqStamp(collabWindow);
|
|
452
|
+
if (firstRemove !== undefined && opstampUtils.lte(firstRemove, minSeqStamp)) {
|
|
433
453
|
combinedPartialLengths.minLength -= segment.cachedLength;
|
|
434
454
|
return;
|
|
435
455
|
}
|
|
436
|
-
const removalIsLocal = !!
|
|
437
|
-
const
|
|
438
|
-
const
|
|
439
|
-
const
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
if (segment.seq === UnassignedSequenceNumber &&
|
|
443
|
-
!(removalIsLocal && (!moveInfo || moveIsLocal)) &&
|
|
444
|
-
!(moveIsLocal && (!removalInfo || removalIsLocal))) {
|
|
445
|
-
throw new Error("Should have handled this codepath in wasMovedOnInsertion");
|
|
456
|
+
const removalIsLocal = !!firstRemove && opstampUtils.isLocal(firstRemove);
|
|
457
|
+
const isLocalInsertion = opstampUtils.isLocal(segment.insert);
|
|
458
|
+
const isOnlyLocalRemoval = removalIsLocal;
|
|
459
|
+
const isLocal = isLocalInsertion || isOnlyLocalRemoval;
|
|
460
|
+
if (isLocalInsertion && !removalIsLocal) {
|
|
461
|
+
throw new Error("Should have handled this codepath in wasRemovedOnInsertion");
|
|
446
462
|
}
|
|
447
463
|
const lenDelta = -segment.cachedLength;
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
const clientsWithRemoveOrObliterate = new Set([
|
|
453
|
-
...(removalInfo?.removedClientIds ?? []),
|
|
454
|
-
...(moveInfo?.movedClientIds ?? []),
|
|
455
|
-
]);
|
|
456
|
-
const removeHappenedFirst = removalInfo &&
|
|
457
|
-
(!moveInfo ||
|
|
458
|
-
moveIsLocal ||
|
|
459
|
-
(!removalIsLocal && moveInfo.movedSeq > removalInfo.removedSeq));
|
|
460
|
-
if (removeHappenedFirst) {
|
|
461
|
-
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
462
|
-
seqOrLocalSeq = removalIsLocal ? removalInfo.localRemovedSeq : removalInfo.removedSeq;
|
|
463
|
-
// The client who performed the remove is always stored
|
|
464
|
-
// in the first position of removalInfo.
|
|
465
|
-
clientId = removalInfo.removedClientIds[0];
|
|
466
|
-
}
|
|
467
|
-
else {
|
|
468
|
-
assert(moveInfo !== undefined, 0xab9 /* Expected move to exist if remove either did not exist or didn't happen first */);
|
|
469
|
-
// The client who performed the move is always stored
|
|
470
|
-
// in the first position of moveInfo.
|
|
471
|
-
clientId = moveInfo.movedClientIds[0];
|
|
472
|
-
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
473
|
-
seqOrLocalSeq = moveIsLocal ? moveInfo.localMovedSeq : moveInfo.movedSeq;
|
|
474
|
-
}
|
|
464
|
+
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
465
|
+
const seqOrLocalSeq = removalIsLocal ? firstRemove.localSeq : firstRemove.seq;
|
|
466
|
+
const clientId = firstRemove.clientId;
|
|
467
|
+
const clientsWithRemoveOrObliterate = new Set(removalInfo?.removes.map((stamp) => stamp.clientId));
|
|
475
468
|
const partials = isLocal
|
|
476
469
|
? combinedPartialLengths.unsequencedRecords?.partialLengths
|
|
477
470
|
: combinedPartialLengths.partialLengths;
|
|
@@ -480,15 +473,30 @@ export class PartialSequenceLengths {
|
|
|
480
473
|
return;
|
|
481
474
|
}
|
|
482
475
|
if (isLocal) {
|
|
483
|
-
// The segment is either inserted only locally or removed
|
|
476
|
+
// The segment is either inserted only locally or removed only locally.
|
|
484
477
|
// We already accounted for the insertion in the accountForInsertion codepath.
|
|
485
|
-
// Only thing left to do is account for the removal.
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
478
|
+
// Only thing left to do is account for the removal in local partial lengths.
|
|
479
|
+
// One thing to be careful about is that the removal should only apply to perspectives that saw
|
|
480
|
+
// the segment's insertion in the first place.
|
|
481
|
+
// When the segment's insertion was common knowledge (at or below minSeq) or also by the local client,
|
|
482
|
+
// all possible perspectives will have seen it.
|
|
483
|
+
if (opstampUtils.lte(segment.insert, minSeqStamp) ||
|
|
484
|
+
collabWindow.clientId === segment.insert.clientId) {
|
|
485
|
+
partials.addOrUpdate({
|
|
486
|
+
seq: seqOrLocalSeq,
|
|
487
|
+
clientId,
|
|
488
|
+
len: 0,
|
|
489
|
+
seglen: lenDelta,
|
|
490
|
+
});
|
|
491
|
+
}
|
|
492
|
+
else {
|
|
493
|
+
// ... otherwise, it's only visible to reconnecting perspectives above the seq of the insert.
|
|
494
|
+
combinedPartialLengths.addLocalAdjustment({
|
|
495
|
+
localSeq: seqOrLocalSeq,
|
|
496
|
+
refSeq: segment.insert.seq,
|
|
497
|
+
seglen: lenDelta,
|
|
498
|
+
});
|
|
499
|
+
}
|
|
492
500
|
}
|
|
493
501
|
else {
|
|
494
502
|
partials.addOrUpdate({
|
|
@@ -500,7 +508,7 @@ export class PartialSequenceLengths {
|
|
|
500
508
|
for (const id of clientsWithRemoveOrObliterate) {
|
|
501
509
|
if (id === collabWindow.clientId) {
|
|
502
510
|
// The local client also removed or obliterated this segment.
|
|
503
|
-
const localSeq =
|
|
511
|
+
const { localSeq } = removalInfo.removes[removalInfo.removes.length - 1];
|
|
504
512
|
if (localSeq === undefined) {
|
|
505
513
|
// Sure, the local client did it--but that change was already acked.
|
|
506
514
|
// No need to account for it in the unsequenced records.
|
|
@@ -511,13 +519,50 @@ export class PartialSequenceLengths {
|
|
|
511
519
|
// Local partial but its computation isn't required.
|
|
512
520
|
continue;
|
|
513
521
|
}
|
|
514
|
-
assert(localSeq !== undefined, 0xaba /* Local client was in
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
522
|
+
assert(localSeq !== undefined, 0xaba /* Local client was in removed client ids but segment has no local seq for either */);
|
|
523
|
+
// Here...
|
|
524
|
+
// - The segment was removed locally at `localSeq`
|
|
525
|
+
// - The segment was also removed remotely at `seqOrLocalSeq`
|
|
526
|
+
// - We're ensuring that partial lengths works for arbitrary `LocalReconnectingPerspective`s.
|
|
527
|
+
//
|
|
528
|
+
// Visualize an arbitrary local reconnecting perspective in 2d space, where the x-axis is `seq` and the y-axis is `localSeq`.
|
|
529
|
+
// The events that have occurred to this segment divide the space into up to 6 regions:
|
|
530
|
+
//
|
|
531
|
+
// (localSeq)
|
|
532
|
+
// | | |
|
|
533
|
+
// | 1 | 2 | 3
|
|
534
|
+
// local remove |-----------------------------
|
|
535
|
+
// | | |
|
|
536
|
+
// | 4 | 5 | 6
|
|
537
|
+
// |----------------------------- (seq)
|
|
538
|
+
// insert remove
|
|
539
|
+
// 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
|
|
540
|
+
// in regions 2, 3, and 6).
|
|
541
|
+
// `accountForInsertion` already added a partial lengths adjustment of +segment.cachedLength for regions 2, 3, 5, and 6.
|
|
542
|
+
// Above in this function, we've added a partial lengths adjustment of -segment.cachedLength for regions 3 and 6.
|
|
543
|
+
//
|
|
544
|
+
// Note that in this picture:
|
|
545
|
+
// - Adding entries to `unsequencedRecords.partialLengths` is like adding adjustments that affect anything above a given Y value
|
|
546
|
+
// - Adding entries to `combinedPartialLengths.partialLengths` is like adding adjustments that affect anything above a given X value
|
|
547
|
+
// - Adding entries with `addLocalAdjustment` is like adding adjustments that affect anything above a given X *and* Y value
|
|
548
|
+
// The remainder this block adds the necessary adjustments to make the length appear 0 in region 2 as well, keeping in mind that
|
|
549
|
+
// region 1 may or may not exist depending on if the insertion is in the collab window.
|
|
550
|
+
if (opstampUtils.isAcked(segment.insert) &&
|
|
551
|
+
opstampUtils.greaterThan(segment.insert, minSeqStamp)) {
|
|
552
|
+
combinedPartialLengths.addLocalAdjustment({
|
|
553
|
+
refSeq: segment.insert.seq,
|
|
554
|
+
localSeq,
|
|
555
|
+
seglen: lenDelta,
|
|
556
|
+
});
|
|
557
|
+
}
|
|
558
|
+
else {
|
|
559
|
+
unsequencedRecords.partialLengths.addOrUpdate({
|
|
560
|
+
seq: localSeq,
|
|
561
|
+
clientId: collabWindow.clientId,
|
|
562
|
+
seglen: lenDelta,
|
|
563
|
+
len: 0,
|
|
564
|
+
});
|
|
565
|
+
}
|
|
521
566
|
// Because we've included deltas which take effect when either of localSeq or refSeq are high enough,
|
|
522
567
|
// we need to offset this with an adjustment that takes effect when both are high enough.
|
|
523
568
|
combinedPartialLengths.addLocalAdjustment({
|
|
@@ -533,15 +578,16 @@ export class PartialSequenceLengths {
|
|
|
533
578
|
}
|
|
534
579
|
else {
|
|
535
580
|
// Note that all clients that have a remove or obliterate operation on this segment
|
|
536
|
-
// use the seq of the winning
|
|
581
|
+
// use the seq of the winning obliterate in their per-client adjustments!
|
|
537
582
|
combinedPartialLengths.addClientAdjustment(id, seqOrLocalSeq, lenDelta);
|
|
538
583
|
// Also ensure that all these clients have seen the segment as inserted before being removed
|
|
539
|
-
// This is technically not necessary for
|
|
584
|
+
// This is technically not necessary for setRemoves (we never ask for the length of this block with
|
|
540
585
|
// respect to a refSeq which this entry would affect), but it's simpler to just add it here.
|
|
541
586
|
// We already add this entry as part of the accountForInsertion codepath for the client that
|
|
542
587
|
// actually did insert the segment, hence not doing so [again] here.
|
|
543
|
-
if (segment.
|
|
544
|
-
|
|
588
|
+
if (opstampUtils.greaterThan(segment.insert, minSeqStamp) &&
|
|
589
|
+
id !== segment.insert.clientId) {
|
|
590
|
+
combinedPartialLengths.addClientAdjustment(id, segment.insert.seq, segment.cachedLength);
|
|
545
591
|
}
|
|
546
592
|
}
|
|
547
593
|
}
|
|
@@ -585,15 +631,14 @@ export class PartialSequenceLengths {
|
|
|
585
631
|
if (child.isLeaf()) {
|
|
586
632
|
const segment = child;
|
|
587
633
|
const removalInfo = toRemovalInfo(segment);
|
|
588
|
-
const
|
|
589
|
-
if (seq === segment.seq) {
|
|
590
|
-
// if this segment was
|
|
634
|
+
const firstRemove = removalInfo?.removes[0];
|
|
635
|
+
if (seq === segment.insert.seq) {
|
|
636
|
+
// if this segment was removed on insert, its length should
|
|
591
637
|
// only be visible to the inserting client
|
|
592
|
-
if (segment.seq !== undefined &&
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
this.addClientAdjustment(clientId, moveInfo.movedSeq, segment.cachedLength);
|
|
638
|
+
if (segment.insert.seq !== undefined &&
|
|
639
|
+
firstRemove !== undefined &&
|
|
640
|
+
wasRemovedOnInsert(segment)) {
|
|
641
|
+
this.addClientAdjustment(clientId, firstRemove.seq, segment.cachedLength);
|
|
597
642
|
failIncrementalPropagation = true;
|
|
598
643
|
}
|
|
599
644
|
else {
|
|
@@ -601,13 +646,13 @@ export class PartialSequenceLengths {
|
|
|
601
646
|
this.addClientAdjustment(clientId, seq, segment.cachedLength);
|
|
602
647
|
}
|
|
603
648
|
}
|
|
604
|
-
|
|
605
|
-
if (segment.seq !== UnassignedSequenceNumber && seq === earlierDeletion) {
|
|
649
|
+
if (opstampUtils.isAcked(segment.insert) && seq === firstRemove?.seq) {
|
|
606
650
|
seqSeglen -= segment.cachedLength;
|
|
607
651
|
if (clientId !== collabWindow.clientId) {
|
|
608
652
|
this.addClientAdjustment(clientId, seq, -segment.cachedLength);
|
|
609
|
-
if (segment.
|
|
610
|
-
|
|
653
|
+
if (opstampUtils.greaterThan(segment.insert, getMinSeqStamp(collabWindow)) &&
|
|
654
|
+
segment.insert.clientId !== clientId) {
|
|
655
|
+
this.addClientAdjustment(clientId, segment.insert.seq, segment.cachedLength);
|
|
611
656
|
failIncrementalPropagation = true;
|
|
612
657
|
}
|
|
613
658
|
}
|
|
@@ -682,13 +727,12 @@ export class PartialSequenceLengths {
|
|
|
682
727
|
assert(this.unsequencedRecords !== undefined, 0x39f /* Local getPartialLength invoked without computing local partials. */);
|
|
683
728
|
const unsequencedPartialLengths = this.unsequencedRecords.partialLengths;
|
|
684
729
|
// Local segments at or before localSeq should also be included
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
}
|
|
730
|
+
length += unsequencedPartialLengths.latestLeq(localSeq)?.len ?? 0;
|
|
731
|
+
// Lastly, we must add in any additional adjustment that should only take effect when both
|
|
732
|
+
// refSeq AND localSeq are above some threshold. This accounts for things like double-counting
|
|
733
|
+
// local+remote removes, or only subtracting the length out of a local remove if we've also seen
|
|
734
|
+
// the insert of the segment it affects. (see addLocalAdjustment usages for examples)
|
|
735
|
+
length += this.computeOverallRefSeqAdjustment(refSeq, localSeq);
|
|
692
736
|
}
|
|
693
737
|
return length;
|
|
694
738
|
}
|
|
@@ -839,13 +883,18 @@ export function verifyExpectedPartialLengths(mergeTree, node, refSeq, clientId,
|
|
|
839
883
|
const partialLen = node.partialLengths?.getPartialLength(refSeq, clientId, localSeq);
|
|
840
884
|
let expected = 0;
|
|
841
885
|
const nodesToVisit = [node];
|
|
886
|
+
const perspective = clientId === mergeTree.collabWindow.clientId
|
|
887
|
+
? localSeq === undefined
|
|
888
|
+
? new LocalDefaultPerspective(clientId)
|
|
889
|
+
: new LocalReconnectingPerspective(refSeq, clientId, localSeq)
|
|
890
|
+
: new PriorPerspective(refSeq, clientId);
|
|
842
891
|
while (nodesToVisit.length > 0) {
|
|
843
892
|
const thisNode = nodesToVisit.pop();
|
|
844
893
|
if (!thisNode) {
|
|
845
894
|
continue;
|
|
846
895
|
}
|
|
847
896
|
if (thisNode.isLeaf()) {
|
|
848
|
-
expected += mergeTree["nodeLength"](thisNode,
|
|
897
|
+
expected += mergeTree["nodeLength"](thisNode, perspective) ?? 0;
|
|
849
898
|
}
|
|
850
899
|
else {
|
|
851
900
|
nodesToVisit.push(...thisNode.children.slice(0, thisNode.childCount));
|