@fluidframework/merge-tree 2.2.0 → 2.3.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/api-report/merge-tree.legacy.alpha.api.md +20 -0
- package/dist/attributionCollection.d.ts.map +1 -1
- package/dist/attributionCollection.js +1 -29
- package/dist/attributionCollection.js.map +1 -1
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +3 -4
- 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.map +1 -1
- package/dist/legacy.d.ts +1 -0
- package/dist/localReference.d.ts.map +1 -1
- package/dist/localReference.js +0 -2
- package/dist/localReference.js.map +1 -1
- package/dist/mergeTree.d.ts +1 -30
- package/dist/mergeTree.d.ts.map +1 -1
- package/dist/mergeTree.js +131 -167
- package/dist/mergeTree.js.map +1 -1
- package/dist/mergeTreeNodes.d.ts +16 -1
- package/dist/mergeTreeNodes.d.ts.map +1 -1
- package/dist/mergeTreeNodes.js +5 -2
- package/dist/mergeTreeNodes.js.map +1 -1
- package/dist/partialLengths.d.ts.map +1 -1
- package/dist/partialLengths.js +8 -54
- package/dist/partialLengths.js.map +1 -1
- package/dist/properties.d.ts.map +1 -1
- package/dist/properties.js +0 -2
- package/dist/properties.js.map +1 -1
- package/dist/revertibles.d.ts.map +1 -1
- package/dist/revertibles.js +0 -14
- package/dist/revertibles.js.map +1 -1
- package/dist/segmentGroupCollection.d.ts.map +1 -1
- package/dist/segmentGroupCollection.js +0 -2
- package/dist/segmentGroupCollection.js.map +1 -1
- package/dist/segmentPropertiesManager.d.ts.map +1 -1
- package/dist/segmentPropertiesManager.js +1 -3
- package/dist/segmentPropertiesManager.js.map +1 -1
- package/dist/snapshotLoader.d.ts.map +1 -1
- package/dist/snapshotLoader.js +1 -4
- package/dist/snapshotLoader.js.map +1 -1
- package/dist/snapshotV1.d.ts.map +1 -1
- package/dist/snapshotV1.js +1 -11
- package/dist/snapshotV1.js.map +1 -1
- package/dist/snapshotlegacy.d.ts.map +1 -1
- package/dist/snapshotlegacy.js +0 -1
- package/dist/snapshotlegacy.js.map +1 -1
- package/dist/sortedSegmentSet.d.ts +0 -1
- package/dist/sortedSegmentSet.d.ts.map +1 -1
- package/dist/sortedSegmentSet.js +1 -9
- package/dist/sortedSegmentSet.js.map +1 -1
- package/dist/sortedSet.d.ts.map +1 -1
- package/dist/sortedSet.js +0 -4
- package/dist/sortedSet.js.map +1 -1
- package/dist/test/client.conflictFarm.spec.d.ts.map +1 -1
- package/dist/test/client.conflictFarm.spec.js +36 -27
- package/dist/test/client.conflictFarm.spec.js.map +1 -1
- package/dist/test/client.replay.spec.js +1 -1
- package/dist/test/client.replay.spec.js.map +1 -1
- package/dist/test/mergeTreeOperationRunner.d.ts +2 -1
- package/dist/test/mergeTreeOperationRunner.d.ts.map +1 -1
- package/dist/test/mergeTreeOperationRunner.js +29 -11
- package/dist/test/mergeTreeOperationRunner.js.map +1 -1
- package/dist/test/obliterate.spec.js +55 -0
- package/dist/test/obliterate.spec.js.map +1 -1
- package/dist/test/reconnectHelper.d.ts +0 -1
- package/dist/test/reconnectHelper.d.ts.map +1 -1
- package/dist/test/reconnectHelper.js +1 -1
- package/dist/test/reconnectHelper.js.map +1 -1
- package/dist/test/testClientLogger.d.ts.map +1 -1
- package/dist/test/testClientLogger.js +17 -7
- package/dist/test/testClientLogger.js.map +1 -1
- package/dist/zamboni.d.ts.map +1 -1
- package/dist/zamboni.js +0 -4
- package/dist/zamboni.js.map +1 -1
- package/lib/attributionCollection.d.ts.map +1 -1
- package/lib/attributionCollection.js +1 -29
- package/lib/attributionCollection.js.map +1 -1
- package/lib/client.d.ts.map +1 -1
- package/lib/client.js +3 -4
- 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.map +1 -1
- package/lib/legacy.d.ts +1 -0
- package/lib/localReference.d.ts.map +1 -1
- package/lib/localReference.js +0 -2
- package/lib/localReference.js.map +1 -1
- package/lib/mergeTree.d.ts +1 -30
- package/lib/mergeTree.d.ts.map +1 -1
- package/lib/mergeTree.js +132 -168
- package/lib/mergeTree.js.map +1 -1
- package/lib/mergeTreeNodes.d.ts +16 -1
- package/lib/mergeTreeNodes.d.ts.map +1 -1
- package/lib/mergeTreeNodes.js +5 -2
- package/lib/mergeTreeNodes.js.map +1 -1
- package/lib/partialLengths.d.ts.map +1 -1
- package/lib/partialLengths.js +8 -54
- package/lib/partialLengths.js.map +1 -1
- package/lib/properties.d.ts.map +1 -1
- package/lib/properties.js +0 -2
- package/lib/properties.js.map +1 -1
- package/lib/revertibles.d.ts.map +1 -1
- package/lib/revertibles.js +0 -14
- package/lib/revertibles.js.map +1 -1
- package/lib/segmentGroupCollection.d.ts.map +1 -1
- package/lib/segmentGroupCollection.js +0 -2
- package/lib/segmentGroupCollection.js.map +1 -1
- package/lib/segmentPropertiesManager.d.ts.map +1 -1
- package/lib/segmentPropertiesManager.js +1 -3
- package/lib/segmentPropertiesManager.js.map +1 -1
- package/lib/snapshotLoader.d.ts.map +1 -1
- package/lib/snapshotLoader.js +1 -4
- package/lib/snapshotLoader.js.map +1 -1
- package/lib/snapshotV1.d.ts.map +1 -1
- package/lib/snapshotV1.js +1 -11
- package/lib/snapshotV1.js.map +1 -1
- package/lib/snapshotlegacy.d.ts.map +1 -1
- package/lib/snapshotlegacy.js +0 -1
- package/lib/snapshotlegacy.js.map +1 -1
- package/lib/sortedSegmentSet.d.ts +0 -1
- package/lib/sortedSegmentSet.d.ts.map +1 -1
- package/lib/sortedSegmentSet.js +1 -9
- package/lib/sortedSegmentSet.js.map +1 -1
- package/lib/sortedSet.d.ts.map +1 -1
- package/lib/sortedSet.js +0 -4
- package/lib/sortedSet.js.map +1 -1
- package/lib/test/client.conflictFarm.spec.d.ts.map +1 -1
- package/lib/test/client.conflictFarm.spec.js +37 -28
- package/lib/test/client.conflictFarm.spec.js.map +1 -1
- package/lib/test/client.replay.spec.js +1 -1
- package/lib/test/client.replay.spec.js.map +1 -1
- package/lib/test/mergeTreeOperationRunner.d.ts +2 -1
- package/lib/test/mergeTreeOperationRunner.d.ts.map +1 -1
- package/lib/test/mergeTreeOperationRunner.js +30 -12
- package/lib/test/mergeTreeOperationRunner.js.map +1 -1
- package/lib/test/obliterate.spec.js +55 -0
- package/lib/test/obliterate.spec.js.map +1 -1
- package/lib/test/reconnectHelper.d.ts +0 -1
- package/lib/test/reconnectHelper.d.ts.map +1 -1
- package/lib/test/reconnectHelper.js +1 -1
- package/lib/test/reconnectHelper.js.map +1 -1
- package/lib/test/testClientLogger.d.ts.map +1 -1
- package/lib/test/testClientLogger.js +18 -8
- package/lib/test/testClientLogger.js.map +1 -1
- package/lib/tsdoc-metadata.json +1 -1
- package/lib/zamboni.d.ts.map +1 -1
- package/lib/zamboni.js +0 -4
- package/lib/zamboni.js.map +1 -1
- package/package.json +22 -21
- package/src/attributionCollection.ts +14 -42
- package/src/client.ts +8 -9
- package/src/index.ts +1 -0
- package/src/localReference.ts +1 -3
- package/src/mergeTree.ts +185 -208
- package/src/mergeTreeNodes.ts +22 -3
- package/src/partialLengths.ts +23 -68
- package/src/properties.ts +1 -3
- package/src/revertibles.ts +7 -21
- package/src/segmentGroupCollection.ts +1 -3
- package/src/segmentPropertiesManager.ts +0 -1
- package/src/snapshotLoader.ts +2 -4
- package/src/snapshotV1.ts +5 -15
- package/src/snapshotlegacy.ts +1 -2
- package/src/sortedSegmentSet.ts +3 -10
- package/src/sortedSet.ts +2 -6
- package/src/zamboni.ts +4 -8
- package/tsconfig.json +1 -0
package/src/mergeTree.ts
CHANGED
|
@@ -25,6 +25,7 @@ import {
|
|
|
25
25
|
LocalReferencePosition,
|
|
26
26
|
SlidingPreference,
|
|
27
27
|
anyLocalReferencePosition,
|
|
28
|
+
createDetachedLocalReferencePosition,
|
|
28
29
|
filterLocalReferencePositions,
|
|
29
30
|
} from "./localReference.js";
|
|
30
31
|
import {
|
|
@@ -62,6 +63,8 @@ import {
|
|
|
62
63
|
seqLTE,
|
|
63
64
|
toMoveInfo,
|
|
64
65
|
toRemovalInfo,
|
|
66
|
+
// eslint-disable-next-line import/no-deprecated
|
|
67
|
+
type ObliterateInfo,
|
|
65
68
|
} from "./mergeTreeNodes.js";
|
|
66
69
|
import type { TrackingGroup } from "./mergeTreeTracking.js";
|
|
67
70
|
import {
|
|
@@ -88,15 +91,9 @@ import {
|
|
|
88
91
|
// eslint-disable-next-line import/no-deprecated
|
|
89
92
|
import { PropertiesRollback } from "./segmentPropertiesManager.js";
|
|
90
93
|
import { endpointPosAndSide, type SequencePlace } from "./sequencePlace.js";
|
|
94
|
+
import { SortedSegmentSet } from "./sortedSegmentSet.js";
|
|
91
95
|
import { zamboniSegments } from "./zamboni.js";
|
|
92
96
|
|
|
93
|
-
function wasRemovedAfter(seg: ISegment, seq: number): boolean {
|
|
94
|
-
return (
|
|
95
|
-
seg.removedSeq !== UnassignedSequenceNumber &&
|
|
96
|
-
(seg.removedSeq === undefined || seg.removedSeq > seq)
|
|
97
|
-
);
|
|
98
|
-
}
|
|
99
|
-
|
|
100
97
|
function markSegmentMoved(seg: ISegment, moveInfo: IMoveInfo): void {
|
|
101
98
|
seg.moveDst = moveInfo.moveDst;
|
|
102
99
|
seg.movedClientIds = [...moveInfo.movedClientIds];
|
|
@@ -414,6 +411,71 @@ const forwardPred = (ref: LocalReferencePosition): boolean =>
|
|
|
414
411
|
const backwardPred = (ref: LocalReferencePosition): boolean =>
|
|
415
412
|
ref.slidingPreference === SlidingPreference.BACKWARD;
|
|
416
413
|
|
|
414
|
+
class Obliterates {
|
|
415
|
+
/**
|
|
416
|
+
* Array containing the all move operations within the
|
|
417
|
+
* collab window.
|
|
418
|
+
*
|
|
419
|
+
* The moves are stored in sequence order which accelerates clean up in setMinSeq
|
|
420
|
+
*
|
|
421
|
+
* See https://github.com/microsoft/FluidFramework/blob/main/packages/dds/merge-tree/docs/Obliterate.md#remote-perspective
|
|
422
|
+
* for additional context
|
|
423
|
+
*/
|
|
424
|
+
// eslint-disable-next-line import/no-deprecated
|
|
425
|
+
private readonly seqOrdered = new DoublyLinkedList<ObliterateInfo>();
|
|
426
|
+
|
|
427
|
+
/**
|
|
428
|
+
* This contains a sorted lists of all obliterate starts
|
|
429
|
+
* and is used to accelerate finding overlapping obliterates
|
|
430
|
+
* as well as determining if there are any obliterates at all.
|
|
431
|
+
*/
|
|
432
|
+
private readonly startOrdered = new SortedSegmentSet<LocalReferencePosition>();
|
|
433
|
+
|
|
434
|
+
constructor(private readonly mergeTree: MergeTree) {}
|
|
435
|
+
|
|
436
|
+
public setMinSeq(minSeq: number): void {
|
|
437
|
+
// eslint-disable-next-line @typescript-eslint/no-non-null-asserted-optional-chain
|
|
438
|
+
while (!this.seqOrdered.empty && this.seqOrdered.first?.data.seq! <= minSeq) {
|
|
439
|
+
const ob = this.seqOrdered.shift()!;
|
|
440
|
+
this.startOrdered.remove(ob.data.start);
|
|
441
|
+
this.mergeTree.removeLocalReferencePosition(ob.data.start);
|
|
442
|
+
this.mergeTree.removeLocalReferencePosition(ob.data.end);
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
// eslint-disable-next-line import/no-deprecated
|
|
447
|
+
public addOrUpdate(obliterateInfo: ObliterateInfo): void {
|
|
448
|
+
const { seq, start } = obliterateInfo;
|
|
449
|
+
if (seq !== UnassignedSequenceNumber) {
|
|
450
|
+
this.seqOrdered.push(obliterateInfo);
|
|
451
|
+
}
|
|
452
|
+
this.startOrdered.addOrUpdate(start);
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
public empty(): boolean {
|
|
456
|
+
return this.startOrdered.size === 0;
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
// eslint-disable-next-line import/no-deprecated
|
|
460
|
+
public findOverlapping(seg: ISegment): Iterable<ObliterateInfo> {
|
|
461
|
+
// eslint-disable-next-line import/no-deprecated
|
|
462
|
+
const overlapping: ObliterateInfo[] = [];
|
|
463
|
+
for (const start of this.startOrdered.items) {
|
|
464
|
+
if (start.getSegment()!.ordinal <= seg.ordinal) {
|
|
465
|
+
// eslint-disable-next-line import/no-deprecated
|
|
466
|
+
const ob = start.properties?.obliterate as ObliterateInfo;
|
|
467
|
+
if (ob.end.getSegment()!.ordinal >= seg.ordinal) {
|
|
468
|
+
overlapping.push(ob);
|
|
469
|
+
}
|
|
470
|
+
} else {
|
|
471
|
+
// the start is past the seg, so exit
|
|
472
|
+
break;
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
return overlapping;
|
|
476
|
+
}
|
|
477
|
+
}
|
|
478
|
+
|
|
417
479
|
/**
|
|
418
480
|
* @internal
|
|
419
481
|
*/
|
|
@@ -448,38 +510,7 @@ export class MergeTree {
|
|
|
448
510
|
public mergeTreeDeltaCallback?: MergeTreeDeltaCallback;
|
|
449
511
|
public mergeTreeMaintenanceCallback?: MergeTreeMaintenanceCallback;
|
|
450
512
|
|
|
451
|
-
|
|
452
|
-
* Array containing the sequence number of all move operations within the
|
|
453
|
-
* collab window
|
|
454
|
-
*
|
|
455
|
-
* When a segment is inserted, we must traverse to the left and right of it
|
|
456
|
-
* to determine whether the segment was inserted into an obliterated range.
|
|
457
|
-
* By keeping track of all move seqs, we can significantly reduce the search
|
|
458
|
-
* space we must traverse.
|
|
459
|
-
*
|
|
460
|
-
* Sequence numbers in `moveSeqs` are sorted to accelerate bookkeeping.
|
|
461
|
-
*
|
|
462
|
-
* See https://github.com/microsoft/FluidFramework/blob/main/packages/dds/merge-tree/docs/Obliterate.md#remote-perspective
|
|
463
|
-
* for additional context
|
|
464
|
-
*/
|
|
465
|
-
private moveSeqs: number[] = [];
|
|
466
|
-
|
|
467
|
-
/**
|
|
468
|
-
* Similar to moveSeqs, but tracks local moves. These are not the move
|
|
469
|
-
* operations within the collab window, but rather local moves that have
|
|
470
|
-
* not been acked.
|
|
471
|
-
*/
|
|
472
|
-
private readonly localMoveSeqs: Set<number> = new Set();
|
|
473
|
-
|
|
474
|
-
/**
|
|
475
|
-
* Groups of segments moved by local moves/obliterates
|
|
476
|
-
*
|
|
477
|
-
* When a local obliterate is acked, we must also ack segments that were
|
|
478
|
-
* concurrently obliterated on insert. We check this segment group to find
|
|
479
|
-
* such segments
|
|
480
|
-
*/
|
|
481
|
-
// eslint-disable-next-line import/no-deprecated
|
|
482
|
-
private readonly locallyMovedSegments: Map<number, SegmentGroup> = new Map();
|
|
513
|
+
private readonly obliterates = new Obliterates(this);
|
|
483
514
|
|
|
484
515
|
public constructor(public options?: IMergeTreeOptions) {
|
|
485
516
|
this._root = this.makeBlock(0);
|
|
@@ -617,8 +648,7 @@ export class MergeTree {
|
|
|
617
648
|
childIndex++, nodeIndex++ // Advance to next child & node
|
|
618
649
|
) {
|
|
619
650
|
// Insert the next node into the current block
|
|
620
|
-
|
|
621
|
-
this.addNode(block, nodes[nodeIndex]!);
|
|
651
|
+
this.addNode(block, nodes[nodeIndex]);
|
|
622
652
|
}
|
|
623
653
|
|
|
624
654
|
// Calculate this block's info. Previously this was inlined into the above loop as a micro-optimization,
|
|
@@ -628,8 +658,7 @@ export class MergeTree {
|
|
|
628
658
|
}
|
|
629
659
|
|
|
630
660
|
return blocks.length === 1 // If there is only one block at this layer...
|
|
631
|
-
? //
|
|
632
|
-
blocks[0]! // ...then we're done. Return the root.
|
|
661
|
+
? blocks[0] // ...then we're done. Return the root.
|
|
633
662
|
: buildMergeBlock(blocks); // ...otherwise recursively build the next layer above blocks.
|
|
634
663
|
};
|
|
635
664
|
if (segments.length > 0) {
|
|
@@ -688,8 +717,7 @@ export class MergeTree {
|
|
|
688
717
|
while (parent) {
|
|
689
718
|
const children = parent.children;
|
|
690
719
|
for (let childIndex = 0; childIndex < parent.childCount; childIndex++) {
|
|
691
|
-
|
|
692
|
-
const child = children[childIndex]!;
|
|
720
|
+
const child = children[childIndex];
|
|
693
721
|
if ((!!prevParent && child === prevParent) || child === node) {
|
|
694
722
|
break;
|
|
695
723
|
}
|
|
@@ -1052,8 +1080,7 @@ export class MergeTree {
|
|
|
1052
1080
|
|
|
1053
1081
|
if (minSeq > this.collabWindow.minSeq) {
|
|
1054
1082
|
this.collabWindow.minSeq = minSeq;
|
|
1055
|
-
|
|
1056
|
-
this.moveSeqs = firstMoveSeqIdx === -1 ? [] : this.moveSeqs.slice(firstMoveSeqIdx);
|
|
1083
|
+
this.obliterates.setMinSeq(minSeq);
|
|
1057
1084
|
if (MergeTree.options.zamboniSegments) {
|
|
1058
1085
|
zamboniSegments(this);
|
|
1059
1086
|
}
|
|
@@ -1192,42 +1219,10 @@ export class MergeTree {
|
|
|
1192
1219
|
const deltaSegments: IMergeTreeSegmentDelta[] = [];
|
|
1193
1220
|
const overlappingRemoves: boolean[] = [];
|
|
1194
1221
|
pendingSegmentGroup.segments.map((pendingSegment: ISegmentLeaf) => {
|
|
1195
|
-
const localMovedSeq = pendingSegment.localMovedSeq;
|
|
1196
1222
|
const overlappingRemove = !pendingSegment.ack(pendingSegmentGroup, opArgs);
|
|
1197
1223
|
|
|
1198
|
-
if (opArgs.op.type === MergeTreeDeltaType.OBLITERATE && localMovedSeq !== undefined) {
|
|
1199
|
-
const locallyMovedSegments = this.locallyMovedSegments.get(localMovedSeq);
|
|
1200
|
-
|
|
1201
|
-
if (locallyMovedSegments) {
|
|
1202
|
-
// Disabling because a for of loop causes the type of segment to be ISegment, which does not have parent information stored
|
|
1203
|
-
// eslint-disable-next-line unicorn/no-array-for-each
|
|
1204
|
-
locallyMovedSegments.segments.forEach((segment: ISegmentLeaf) => {
|
|
1205
|
-
segment.localMovedSeq = undefined;
|
|
1206
|
-
|
|
1207
|
-
if (!nodesToUpdate.includes(segment.parent!)) {
|
|
1208
|
-
nodesToUpdate.push(segment.parent!);
|
|
1209
|
-
}
|
|
1210
|
-
|
|
1211
|
-
if (segment.movedSeq === UnassignedSequenceNumber) {
|
|
1212
|
-
segment.movedSeq = seq;
|
|
1213
|
-
}
|
|
1214
|
-
});
|
|
1215
|
-
|
|
1216
|
-
this.locallyMovedSegments.delete(localMovedSeq);
|
|
1217
|
-
}
|
|
1218
|
-
}
|
|
1219
|
-
|
|
1220
1224
|
overwrite = overlappingRemove || overwrite;
|
|
1221
1225
|
|
|
1222
|
-
if (opArgs.op.type === MergeTreeDeltaType.OBLITERATE) {
|
|
1223
|
-
if (seq !== this.moveSeqs[this.moveSeqs.length - 1]) {
|
|
1224
|
-
this.moveSeqs.push(seq);
|
|
1225
|
-
}
|
|
1226
|
-
if (localMovedSeq !== undefined) {
|
|
1227
|
-
this.localMoveSeqs.delete(localMovedSeq);
|
|
1228
|
-
}
|
|
1229
|
-
}
|
|
1230
|
-
|
|
1231
1226
|
overlappingRemoves.push(overlappingRemove);
|
|
1232
1227
|
if (MergeTree.options.zamboniSegments) {
|
|
1233
1228
|
this.addToLRUSet(pendingSegment, seq);
|
|
@@ -1240,6 +1235,10 @@ export class MergeTree {
|
|
|
1240
1235
|
});
|
|
1241
1236
|
});
|
|
1242
1237
|
|
|
1238
|
+
if (opArgs.op.type === MergeTreeDeltaType.OBLITERATE) {
|
|
1239
|
+
this.obliterates.addOrUpdate(pendingSegmentGroup.obliterateInfo!);
|
|
1240
|
+
}
|
|
1241
|
+
|
|
1243
1242
|
// Perform slides after all segments have been acked, so that
|
|
1244
1243
|
// positions after slide are final
|
|
1245
1244
|
if (
|
|
@@ -1516,115 +1515,62 @@ export class MergeTree {
|
|
|
1516
1515
|
|
|
1517
1516
|
insertPos += newSegment.cachedLength;
|
|
1518
1517
|
|
|
1519
|
-
if (!this.options?.mergeTreeEnableObliterate) {
|
|
1520
|
-
continue;
|
|
1521
|
-
}
|
|
1522
|
-
|
|
1523
|
-
let moveUpperBound = Number.POSITIVE_INFINITY;
|
|
1524
|
-
const smallestSeqMoveOp = this.getSmallestSeqMoveOp();
|
|
1525
|
-
|
|
1526
|
-
if (smallestSeqMoveOp === undefined) {
|
|
1518
|
+
if (!this.options?.mergeTreeEnableObliterate || this.obliterates.empty()) {
|
|
1527
1519
|
continue;
|
|
1528
1520
|
}
|
|
1529
1521
|
|
|
1530
|
-
|
|
1531
|
-
|
|
1532
|
-
|
|
1533
|
-
|
|
1534
|
-
let
|
|
1535
|
-
let
|
|
1536
|
-
|
|
1537
|
-
const
|
|
1538
|
-
|
|
1539
|
-
|
|
1540
|
-
|
|
1541
|
-
|
|
1542
|
-
|
|
1543
|
-
|
|
1544
|
-
|
|
1545
|
-
|
|
1546
|
-
|
|
1547
|
-
|
|
1548
|
-
|
|
1549
|
-
|
|
1550
|
-
|
|
1551
|
-
|
|
1552
|
-
|
|
1553
|
-
|
|
1554
|
-
|
|
1555
|
-
|
|
1556
|
-
|
|
1557
|
-
|
|
1558
|
-
|
|
1559
|
-
|
|
1560
|
-
const findRightMovedSegment = (seg: ISegment): boolean => {
|
|
1561
|
-
const movedSeqs = seg.movedSeqs?.filter((movedSeq) => movedSeq >= refSeq) ?? [];
|
|
1562
|
-
const localMovedSeqs = seg.localMovedSeq ? [seg.localMovedSeq] : [];
|
|
1563
|
-
|
|
1564
|
-
for (const movedSeq of movedSeqs) {
|
|
1565
|
-
const left = leftAckedSegments[movedSeq];
|
|
1566
|
-
if (left) {
|
|
1567
|
-
_movedSeq = movedSeq;
|
|
1568
|
-
const clientIdIdx = left.movedSeqs?.indexOf(movedSeq) ?? -1;
|
|
1569
|
-
const movedClientId = left.movedClientIds?.[clientIdIdx];
|
|
1570
|
-
assert(movedClientId !== undefined, 0x869 /* expected client id to exist */);
|
|
1571
|
-
movedClientIds = [movedClientId];
|
|
1572
|
-
return false;
|
|
1573
|
-
}
|
|
1574
|
-
}
|
|
1575
|
-
|
|
1576
|
-
for (const localMovedSeq of localMovedSeqs) {
|
|
1577
|
-
const left = leftLocalSegments[localMovedSeq];
|
|
1578
|
-
if (left) {
|
|
1579
|
-
_localMovedSeq = localMovedSeq;
|
|
1580
|
-
const clientIdIdx = left.movedSeqs?.indexOf(UnassignedSequenceNumber) ?? -1;
|
|
1581
|
-
const movedClientId = left.movedClientIds?.[clientIdIdx];
|
|
1582
|
-
assert(movedClientId !== undefined, 0x86a /* expected client id to exist */);
|
|
1583
|
-
movedClientIds = [movedClientId];
|
|
1584
|
-
return false;
|
|
1522
|
+
// eslint-disable-next-line import/no-deprecated
|
|
1523
|
+
let oldest: ObliterateInfo | undefined;
|
|
1524
|
+
let normalizedOldestSeq: number = 0;
|
|
1525
|
+
// eslint-disable-next-line import/no-deprecated
|
|
1526
|
+
let newest: ObliterateInfo | undefined;
|
|
1527
|
+
let normalizedNewestSeq: number = 0;
|
|
1528
|
+
const movedClientIds: number[] = [];
|
|
1529
|
+
const movedSeqs: number[] = [];
|
|
1530
|
+
for (const ob of this.obliterates.findOverlapping(newSegment)) {
|
|
1531
|
+
// compute a normalized seq that takes into account local seqs
|
|
1532
|
+
// but is still comparable to remote seqs to keep the checks below easy
|
|
1533
|
+
// REMOTE SEQUENCE NUMBERS LOCAL SEQUENCE NUMBERS
|
|
1534
|
+
// [0, 1, 2, 3, ..., 100, ..., 1000, ..., (MAX - MaxLocalSeq), L1, L2, L3, L4, ..., L100, ..., L1000, ...(MAX)]
|
|
1535
|
+
const normalizedObSeq =
|
|
1536
|
+
ob.seq === UnassignedSequenceNumber
|
|
1537
|
+
? Number.MAX_SAFE_INTEGER - this.collabWindow.localSeq + ob.localSeq!
|
|
1538
|
+
: ob.seq;
|
|
1539
|
+
if (normalizedObSeq > refSeq) {
|
|
1540
|
+
if (oldest === undefined || normalizedOldestSeq > normalizedObSeq) {
|
|
1541
|
+
normalizedOldestSeq = normalizedObSeq;
|
|
1542
|
+
oldest = ob;
|
|
1543
|
+
movedClientIds.unshift(ob.clientId);
|
|
1544
|
+
movedSeqs.unshift(ob.seq);
|
|
1545
|
+
} else {
|
|
1546
|
+
if (newest === undefined || normalizedNewestSeq < normalizedObSeq) {
|
|
1547
|
+
normalizedNewestSeq = normalizedObSeq;
|
|
1548
|
+
newest = ob;
|
|
1549
|
+
}
|
|
1550
|
+
movedClientIds.push(ob.clientId);
|
|
1551
|
+
movedSeqs.push(ob.seq);
|
|
1585
1552
|
}
|
|
1586
1553
|
}
|
|
1554
|
+
}
|
|
1587
1555
|
|
|
1588
|
-
|
|
1589
|
-
|
|
1590
|
-
}
|
|
1591
|
-
|
|
1592
|
-
if (!isRemoved(seg) || wasRemovedAfter(seg, moveUpperBound)) {
|
|
1593
|
-
moveUpperBound = Math.min(moveUpperBound, seg.seq ?? Number.POSITIVE_INFINITY);
|
|
1594
|
-
}
|
|
1595
|
-
// If we've reached a segment that existed before any of our in-collab-window move ops
|
|
1596
|
-
// happened, no need to continue.
|
|
1597
|
-
return moveUpperBound >= smallestSeqMoveOp;
|
|
1598
|
-
};
|
|
1599
|
-
|
|
1600
|
-
backwardExcursion(newSegment, findLeftMovedSegment);
|
|
1601
|
-
moveUpperBound = Number.POSITIVE_INFINITY;
|
|
1602
|
-
forwardExcursion(newSegment, findRightMovedSegment);
|
|
1603
|
-
|
|
1604
|
-
if (_localMovedSeq !== undefined || _movedSeq !== undefined) {
|
|
1605
|
-
assert(
|
|
1606
|
-
movedClientIds !== undefined,
|
|
1607
|
-
0x86b /* movedClientIds should be set if local/moved seq is set */,
|
|
1608
|
-
);
|
|
1609
|
-
const moveInfo = {
|
|
1556
|
+
if (oldest && newest?.clientId !== clientId) {
|
|
1557
|
+
const moveInfo: IMoveInfo = {
|
|
1610
1558
|
movedClientIds,
|
|
1611
|
-
movedSeq:
|
|
1612
|
-
movedSeqs
|
|
1613
|
-
localMovedSeq:
|
|
1614
|
-
wasMovedOnInsert:
|
|
1559
|
+
movedSeq: oldest.seq,
|
|
1560
|
+
movedSeqs,
|
|
1561
|
+
localMovedSeq: oldest.localSeq,
|
|
1562
|
+
wasMovedOnInsert: oldest.seq !== UnassignedSequenceNumber,
|
|
1615
1563
|
};
|
|
1616
1564
|
|
|
1617
1565
|
markSegmentMoved(newSegment, moveInfo);
|
|
1618
1566
|
|
|
1619
1567
|
if (moveInfo.localMovedSeq !== undefined) {
|
|
1620
|
-
const movedSegmentGroup = this.locallyMovedSegments.get(moveInfo.localMovedSeq);
|
|
1621
|
-
|
|
1622
1568
|
assert(
|
|
1623
|
-
|
|
1569
|
+
oldest.segmentGroup !== undefined,
|
|
1624
1570
|
0x86c /* expected segment group to exist */,
|
|
1625
1571
|
);
|
|
1626
1572
|
|
|
1627
|
-
this.addToPendingList(newSegment,
|
|
1573
|
+
this.addToPendingList(newSegment, oldest.segmentGroup);
|
|
1628
1574
|
}
|
|
1629
1575
|
|
|
1630
1576
|
if (newSegment.parent) {
|
|
@@ -1701,10 +1647,6 @@ export class MergeTree {
|
|
|
1701
1647
|
}
|
|
1702
1648
|
}
|
|
1703
1649
|
|
|
1704
|
-
private getSmallestSeqMoveOp(): number | undefined {
|
|
1705
|
-
return this.moveSeqs[0] ?? (this.localMoveSeqs.size > 0 ? -1 : undefined);
|
|
1706
|
-
}
|
|
1707
|
-
|
|
1708
1650
|
private insertingWalk(
|
|
1709
1651
|
block: MergeBlock,
|
|
1710
1652
|
pos: number | "start" | "end",
|
|
@@ -1729,8 +1671,7 @@ export class MergeTree {
|
|
|
1729
1671
|
let newNode: IMergeNode | undefined;
|
|
1730
1672
|
let fromSplit: MergeBlock | undefined;
|
|
1731
1673
|
for (childIndex = 0; childIndex < block.childCount; childIndex++) {
|
|
1732
|
-
|
|
1733
|
-
child = children[childIndex]!;
|
|
1674
|
+
child = children[childIndex];
|
|
1734
1675
|
// ensure we walk down the far edge of the tree, even if all sub-tree is eligible for zamboni
|
|
1735
1676
|
const isLastNonLeafBlock =
|
|
1736
1677
|
isLastChildBlock && !child.isLeaf() && childIndex === block.childCount - 1;
|
|
@@ -1801,10 +1742,8 @@ export class MergeTree {
|
|
|
1801
1742
|
}
|
|
1802
1743
|
if (newNode) {
|
|
1803
1744
|
for (let i = block.childCount; i > childIndex; i--) {
|
|
1804
|
-
|
|
1805
|
-
block.children[i] =
|
|
1806
|
-
// TODO Non null asserting, why is this not null?
|
|
1807
|
-
block.children[i]!.index = i;
|
|
1745
|
+
block.children[i] = block.children[i - 1];
|
|
1746
|
+
block.children[i].index = i;
|
|
1808
1747
|
}
|
|
1809
1748
|
block.assignChild(newNode, childIndex, false);
|
|
1810
1749
|
block.childCount++;
|
|
@@ -1841,8 +1780,7 @@ export class MergeTree {
|
|
|
1841
1780
|
// Update ordinals to reflect lowered child count
|
|
1842
1781
|
this.nodeUpdateOrdinals(node);
|
|
1843
1782
|
for (let i = 0; i < halfCount; i++) {
|
|
1844
|
-
|
|
1845
|
-
newNode.assignChild(node.children[halfCount + i]!, i, false);
|
|
1783
|
+
newNode.assignChild(node.children[halfCount + i], i, false);
|
|
1846
1784
|
node.children[halfCount + i] = undefined!;
|
|
1847
1785
|
}
|
|
1848
1786
|
this.nodeUpdateLengthNewStructure(node);
|
|
@@ -1852,8 +1790,7 @@ export class MergeTree {
|
|
|
1852
1790
|
|
|
1853
1791
|
public nodeUpdateOrdinals(block: MergeBlock): void {
|
|
1854
1792
|
for (let i = 0; i < block.childCount; i++) {
|
|
1855
|
-
|
|
1856
|
-
const child = block.children[i]!;
|
|
1793
|
+
const child = block.children[i];
|
|
1857
1794
|
block.setOrdinal(child, i);
|
|
1858
1795
|
if (!child.isLeaf()) {
|
|
1859
1796
|
this.nodeUpdateOrdinals(child);
|
|
@@ -1972,13 +1909,53 @@ export class MergeTree {
|
|
|
1972
1909
|
const movedSegments: IMergeTreeSegmentDelta[] = [];
|
|
1973
1910
|
const localSeq =
|
|
1974
1911
|
seq === UnassignedSequenceNumber ? ++this.collabWindow.localSeq : undefined;
|
|
1975
|
-
if (seq !== UnassignedSequenceNumber && seq !== this.moveSeqs[this.moveSeqs.length - 1]) {
|
|
1976
|
-
this.moveSeqs.push(seq);
|
|
1977
|
-
} else if (seq === UnassignedSequenceNumber && localSeq !== undefined) {
|
|
1978
|
-
this.localMoveSeqs.add(localSeq);
|
|
1979
|
-
}
|
|
1980
1912
|
// eslint-disable-next-line import/no-deprecated
|
|
1981
|
-
|
|
1913
|
+
const obliterate: ObliterateInfo = {
|
|
1914
|
+
clientId,
|
|
1915
|
+
end: createDetachedLocalReferencePosition(undefined),
|
|
1916
|
+
refSeq,
|
|
1917
|
+
seq,
|
|
1918
|
+
start: createDetachedLocalReferencePosition(undefined),
|
|
1919
|
+
localSeq,
|
|
1920
|
+
segmentGroup: undefined,
|
|
1921
|
+
};
|
|
1922
|
+
const normalizedStartPos = startPos === "start" || startPos === undefined ? 0 : startPos;
|
|
1923
|
+
const normalizedEndPos =
|
|
1924
|
+
endPos === "end" || endPos === undefined ? this.getLength(refSeq, clientId) : endPos;
|
|
1925
|
+
|
|
1926
|
+
const { segment: startSeg } = this.getContainingSegment(
|
|
1927
|
+
normalizedStartPos,
|
|
1928
|
+
refSeq,
|
|
1929
|
+
clientId,
|
|
1930
|
+
);
|
|
1931
|
+
const { segment: endSeg } = this.getContainingSegment(
|
|
1932
|
+
normalizedEndPos - 1,
|
|
1933
|
+
refSeq,
|
|
1934
|
+
clientId,
|
|
1935
|
+
);
|
|
1936
|
+
assert(
|
|
1937
|
+
startSeg !== undefined && endSeg !== undefined,
|
|
1938
|
+
0xa3f /* segments cannot be undefined */,
|
|
1939
|
+
);
|
|
1940
|
+
|
|
1941
|
+
obliterate.start = this.createLocalReferencePosition(
|
|
1942
|
+
startSeg,
|
|
1943
|
+
0,
|
|
1944
|
+
ReferenceType.StayOnRemove,
|
|
1945
|
+
{
|
|
1946
|
+
obliterate,
|
|
1947
|
+
},
|
|
1948
|
+
);
|
|
1949
|
+
|
|
1950
|
+
obliterate.end = this.createLocalReferencePosition(
|
|
1951
|
+
endSeg,
|
|
1952
|
+
endSeg.cachedLength - 1,
|
|
1953
|
+
ReferenceType.StayOnRemove,
|
|
1954
|
+
{
|
|
1955
|
+
obliterate,
|
|
1956
|
+
},
|
|
1957
|
+
);
|
|
1958
|
+
|
|
1982
1959
|
const markMoved = (
|
|
1983
1960
|
segment: ISegment,
|
|
1984
1961
|
pos: number,
|
|
@@ -1986,9 +1963,6 @@ export class MergeTree {
|
|
|
1986
1963
|
_end: number,
|
|
1987
1964
|
): boolean => {
|
|
1988
1965
|
const existingMoveInfo = toMoveInfo(segment);
|
|
1989
|
-
if (startSide) segment.startSide = startSide;
|
|
1990
|
-
if (endSide) segment.endSide = endSide;
|
|
1991
|
-
|
|
1992
1966
|
if (
|
|
1993
1967
|
clientId !== segment.clientId &&
|
|
1994
1968
|
segment.seq !== undefined &&
|
|
@@ -2034,7 +2008,12 @@ export class MergeTree {
|
|
|
2034
2008
|
segment.movedSeq === UnassignedSequenceNumber &&
|
|
2035
2009
|
clientId === this.collabWindow.clientId
|
|
2036
2010
|
) {
|
|
2037
|
-
segmentGroup = this.addToPendingList(
|
|
2011
|
+
obliterate.segmentGroup = this.addToPendingList(
|
|
2012
|
+
segment,
|
|
2013
|
+
obliterate.segmentGroup,
|
|
2014
|
+
localSeq,
|
|
2015
|
+
);
|
|
2016
|
+
obliterate.segmentGroup.obliterateInfo ??= obliterate;
|
|
2038
2017
|
} else {
|
|
2039
2018
|
if (MergeTree.options.zamboniSegments) {
|
|
2040
2019
|
this.addToLRUSet(segment, seq);
|
|
@@ -2070,6 +2049,8 @@ export class MergeTree {
|
|
|
2070
2049
|
seq === UnassignedSequenceNumber ? undefined : seq,
|
|
2071
2050
|
);
|
|
2072
2051
|
|
|
2052
|
+
this.obliterates.addOrUpdate(obliterate);
|
|
2053
|
+
|
|
2073
2054
|
this.slideAckedRemovedSegmentReferences(localOverlapWithRefs);
|
|
2074
2055
|
// opArgs == undefined => test code
|
|
2075
2056
|
if (movedSegments.length > 0) {
|
|
@@ -2079,10 +2060,6 @@ export class MergeTree {
|
|
|
2079
2060
|
});
|
|
2080
2061
|
}
|
|
2081
2062
|
|
|
2082
|
-
if (segmentGroup! && localSeq !== undefined) {
|
|
2083
|
-
this.locallyMovedSegments.set(localSeq, segmentGroup);
|
|
2084
|
-
}
|
|
2085
|
-
|
|
2086
2063
|
// these events are newly removed
|
|
2087
2064
|
// so we slide after eventing in case the consumer wants to make reference
|
|
2088
2065
|
// changes at remove time, like add a ref to track undo redo.
|
|
@@ -2292,8 +2269,7 @@ export class MergeTree {
|
|
|
2292
2269
|
{ op: removeOp },
|
|
2293
2270
|
);
|
|
2294
2271
|
} /* op.type === MergeTreeDeltaType.ANNOTATE */ else {
|
|
2295
|
-
|
|
2296
|
-
const props = pendingSegmentGroup.previousProps![i]!;
|
|
2272
|
+
const props = pendingSegmentGroup.previousProps![i];
|
|
2297
2273
|
const annotateOp = createAnnotateRangeOp(start, start + segment.cachedLength, props);
|
|
2298
2274
|
this.annotateRange(
|
|
2299
2275
|
start,
|
|
@@ -2366,7 +2342,10 @@ export class MergeTree {
|
|
|
2366
2342
|
_segment !== "start" &&
|
|
2367
2343
|
_segment !== "end" &&
|
|
2368
2344
|
isRemovedAndAckedOrMovedAndAcked(_segment) &&
|
|
2369
|
-
!refTypeIncludesFlag(
|
|
2345
|
+
!refTypeIncludesFlag(
|
|
2346
|
+
refType,
|
|
2347
|
+
ReferenceType.SlideOnRemove | ReferenceType.Transient | ReferenceType.StayOnRemove,
|
|
2348
|
+
) &&
|
|
2370
2349
|
_segment.endpointType === undefined
|
|
2371
2350
|
) {
|
|
2372
2351
|
throw new UsageError(
|
|
@@ -2466,9 +2445,8 @@ export class MergeTree {
|
|
|
2466
2445
|
}
|
|
2467
2446
|
|
|
2468
2447
|
for (let i = 0; i < newOrder.length; i++) {
|
|
2469
|
-
|
|
2470
|
-
const
|
|
2471
|
-
const { parent, index, ordinal } = currentOrder[i]!;
|
|
2448
|
+
const seg = newOrder[i];
|
|
2449
|
+
const { parent, index, ordinal } = currentOrder[i];
|
|
2472
2450
|
parent?.assignChild(seg, index, false);
|
|
2473
2451
|
seg.ordinal = ordinal;
|
|
2474
2452
|
}
|
|
@@ -2561,8 +2539,7 @@ export class MergeTree {
|
|
|
2561
2539
|
const leftmostTiles = createMap<Marker>();
|
|
2562
2540
|
|
|
2563
2541
|
for (let i = 0; i < block.childCount; i++) {
|
|
2564
|
-
|
|
2565
|
-
const node = block.children[i]!;
|
|
2542
|
+
const node = block.children[i];
|
|
2566
2543
|
const nodeLength = nodeTotalLength(this, node);
|
|
2567
2544
|
if (nodeLength !== undefined) {
|
|
2568
2545
|
len ??= 0;
|
package/src/mergeTreeNodes.ts
CHANGED
|
@@ -14,7 +14,7 @@ import {
|
|
|
14
14
|
UnassignedSequenceNumber,
|
|
15
15
|
UniversalSequenceNumber,
|
|
16
16
|
} from "./constants.js";
|
|
17
|
-
import { LocalReferenceCollection } from "./localReference.js";
|
|
17
|
+
import { LocalReferenceCollection, type LocalReferencePosition } from "./localReference.js";
|
|
18
18
|
import { IMergeTreeDeltaOpArgs } from "./mergeTreeDeltaCallback.js";
|
|
19
19
|
import { TrackingGroupCollection } from "./mergeTreeTracking.js";
|
|
20
20
|
import { IJSONSegment, IMarkerDef, MergeTreeDeltaType, ReferenceType } from "./ops.js";
|
|
@@ -393,6 +393,21 @@ export interface SegmentActions<TClientData> {
|
|
|
393
393
|
post?: BlockAction<TClientData>;
|
|
394
394
|
}
|
|
395
395
|
|
|
396
|
+
/**
|
|
397
|
+
* @deprecated This functionality was not meant to be exported and will be removed in a future release
|
|
398
|
+
* @legacy
|
|
399
|
+
* @alpha
|
|
400
|
+
*/
|
|
401
|
+
export interface ObliterateInfo {
|
|
402
|
+
start: LocalReferencePosition;
|
|
403
|
+
end: LocalReferencePosition;
|
|
404
|
+
refSeq: number;
|
|
405
|
+
clientId: number;
|
|
406
|
+
seq: number;
|
|
407
|
+
localSeq: number | undefined;
|
|
408
|
+
segmentGroup: SegmentGroup | undefined;
|
|
409
|
+
}
|
|
410
|
+
|
|
396
411
|
/**
|
|
397
412
|
* @deprecated This functionality was not meant to be exported and will be removed in a future release
|
|
398
413
|
* @legacy
|
|
@@ -403,6 +418,7 @@ export interface SegmentGroup {
|
|
|
403
418
|
previousProps?: PropertySet[];
|
|
404
419
|
localSeq?: number;
|
|
405
420
|
refSeq: number;
|
|
421
|
+
obliterateInfo?: ObliterateInfo;
|
|
406
422
|
}
|
|
407
423
|
|
|
408
424
|
/**
|
|
@@ -657,10 +673,13 @@ export abstract class BaseSegment implements ISegment {
|
|
|
657
673
|
case MergeTreeDeltaType.OBLITERATE: {
|
|
658
674
|
const moveInfo: IMoveInfo | undefined = toMoveInfo(this);
|
|
659
675
|
assert(moveInfo !== undefined, 0x86e /* On obliterate ack, missing move info! */);
|
|
660
|
-
|
|
676
|
+
const obliterateInfo = segmentGroup.obliterateInfo;
|
|
677
|
+
assert(obliterateInfo !== undefined, 0xa40 /* must have obliterate info */);
|
|
678
|
+
this.localMovedSeq = obliterateInfo.localSeq = undefined;
|
|
661
679
|
const seqIdx = moveInfo.movedSeqs.indexOf(UnassignedSequenceNumber);
|
|
662
680
|
assert(seqIdx !== -1, 0x86f /* expected movedSeqs to contain unacked seq */);
|
|
663
|
-
moveInfo.movedSeqs[seqIdx] =
|
|
681
|
+
moveInfo.movedSeqs[seqIdx] = obliterateInfo.seq =
|
|
682
|
+
opArgs.sequencedMessage!.sequenceNumber;
|
|
664
683
|
|
|
665
684
|
if (moveInfo.movedSeq === UnassignedSequenceNumber) {
|
|
666
685
|
moveInfo.movedSeq = opArgs.sequencedMessage!.sequenceNumber;
|