@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/lib/mergeTree.js
CHANGED
|
@@ -9,7 +9,7 @@ import { DataProcessingError, UsageError } from "@fluidframework/telemetry-utils
|
|
|
9
9
|
import { DoublyLinkedList } from "./collections/index.js";
|
|
10
10
|
import { NonCollabClient, TreeMaintenanceSequenceNumber, UnassignedSequenceNumber, UniversalSequenceNumber, } from "./constants.js";
|
|
11
11
|
import { EndOfTreeSegment, StartOfTreeSegment } from "./endOfTreeSegment.js";
|
|
12
|
-
import { LocalReferenceCollection, SlidingPreference, anyLocalReferencePosition, filterLocalReferencePositions, } from "./localReference.js";
|
|
12
|
+
import { LocalReferenceCollection, SlidingPreference, anyLocalReferencePosition, createDetachedLocalReferencePosition, filterLocalReferencePositions, } from "./localReference.js";
|
|
13
13
|
import { MergeTreeMaintenanceType, } from "./mergeTreeDeltaCallback.js";
|
|
14
14
|
import { NodeAction, backwardExcursion, depthFirstNodeWalk, forwardExcursion, walkAllChildSegments, } from "./mergeTreeNodeWalk.js";
|
|
15
15
|
import {
|
|
@@ -24,11 +24,8 @@ import { DetachedReferencePosition, refGetTileLabels, refHasTileLabel, refTypeIn
|
|
|
24
24
|
// eslint-disable-next-line import/no-deprecated
|
|
25
25
|
import { PropertiesRollback } from "./segmentPropertiesManager.js";
|
|
26
26
|
import { endpointPosAndSide } from "./sequencePlace.js";
|
|
27
|
+
import { SortedSegmentSet } from "./sortedSegmentSet.js";
|
|
27
28
|
import { zamboniSegments } from "./zamboni.js";
|
|
28
|
-
function wasRemovedAfter(seg, seq) {
|
|
29
|
-
return (seg.removedSeq !== UnassignedSequenceNumber &&
|
|
30
|
-
(seg.removedSeq === undefined || seg.removedSeq > seq));
|
|
31
|
-
}
|
|
32
29
|
function markSegmentMoved(seg, moveInfo) {
|
|
33
30
|
seg.moveDst = moveInfo.moveDst;
|
|
34
31
|
seg.movedClientIds = [...moveInfo.movedClientIds];
|
|
@@ -176,6 +173,67 @@ export function getSlideToSegoff(segoff, slidingPreference = SlidingPreference.F
|
|
|
176
173
|
}
|
|
177
174
|
const forwardPred = (ref) => ref.slidingPreference !== SlidingPreference.BACKWARD;
|
|
178
175
|
const backwardPred = (ref) => ref.slidingPreference === SlidingPreference.BACKWARD;
|
|
176
|
+
class Obliterates {
|
|
177
|
+
constructor(mergeTree) {
|
|
178
|
+
this.mergeTree = mergeTree;
|
|
179
|
+
/**
|
|
180
|
+
* Array containing the all move operations within the
|
|
181
|
+
* collab window.
|
|
182
|
+
*
|
|
183
|
+
* The moves are stored in sequence order which accelerates clean up in setMinSeq
|
|
184
|
+
*
|
|
185
|
+
* See https://github.com/microsoft/FluidFramework/blob/main/packages/dds/merge-tree/docs/Obliterate.md#remote-perspective
|
|
186
|
+
* for additional context
|
|
187
|
+
*/
|
|
188
|
+
// eslint-disable-next-line import/no-deprecated
|
|
189
|
+
this.seqOrdered = new DoublyLinkedList();
|
|
190
|
+
/**
|
|
191
|
+
* This contains a sorted lists of all obliterate starts
|
|
192
|
+
* and is used to accelerate finding overlapping obliterates
|
|
193
|
+
* as well as determining if there are any obliterates at all.
|
|
194
|
+
*/
|
|
195
|
+
this.startOrdered = new SortedSegmentSet();
|
|
196
|
+
}
|
|
197
|
+
setMinSeq(minSeq) {
|
|
198
|
+
// eslint-disable-next-line @typescript-eslint/no-non-null-asserted-optional-chain
|
|
199
|
+
while (!this.seqOrdered.empty && this.seqOrdered.first?.data.seq <= minSeq) {
|
|
200
|
+
const ob = this.seqOrdered.shift();
|
|
201
|
+
this.startOrdered.remove(ob.data.start);
|
|
202
|
+
this.mergeTree.removeLocalReferencePosition(ob.data.start);
|
|
203
|
+
this.mergeTree.removeLocalReferencePosition(ob.data.end);
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
// eslint-disable-next-line import/no-deprecated
|
|
207
|
+
addOrUpdate(obliterateInfo) {
|
|
208
|
+
const { seq, start } = obliterateInfo;
|
|
209
|
+
if (seq !== UnassignedSequenceNumber) {
|
|
210
|
+
this.seqOrdered.push(obliterateInfo);
|
|
211
|
+
}
|
|
212
|
+
this.startOrdered.addOrUpdate(start);
|
|
213
|
+
}
|
|
214
|
+
empty() {
|
|
215
|
+
return this.startOrdered.size === 0;
|
|
216
|
+
}
|
|
217
|
+
// eslint-disable-next-line import/no-deprecated
|
|
218
|
+
findOverlapping(seg) {
|
|
219
|
+
// eslint-disable-next-line import/no-deprecated
|
|
220
|
+
const overlapping = [];
|
|
221
|
+
for (const start of this.startOrdered.items) {
|
|
222
|
+
if (start.getSegment().ordinal <= seg.ordinal) {
|
|
223
|
+
// eslint-disable-next-line import/no-deprecated
|
|
224
|
+
const ob = start.properties?.obliterate;
|
|
225
|
+
if (ob.end.getSegment().ordinal >= seg.ordinal) {
|
|
226
|
+
overlapping.push(ob);
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
else {
|
|
230
|
+
// the start is past the seg, so exit
|
|
231
|
+
break;
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
return overlapping;
|
|
235
|
+
}
|
|
236
|
+
}
|
|
179
237
|
/**
|
|
180
238
|
* @internal
|
|
181
239
|
*/
|
|
@@ -196,36 +254,7 @@ export class MergeTree {
|
|
|
196
254
|
// for now assume only markers have ids and so point directly at the Segment
|
|
197
255
|
// if we need to have pointers to non-markers, we can change to point at local refs
|
|
198
256
|
this.idToMarker = new Map();
|
|
199
|
-
|
|
200
|
-
* Array containing the sequence number of all move operations within the
|
|
201
|
-
* collab window
|
|
202
|
-
*
|
|
203
|
-
* When a segment is inserted, we must traverse to the left and right of it
|
|
204
|
-
* to determine whether the segment was inserted into an obliterated range.
|
|
205
|
-
* By keeping track of all move seqs, we can significantly reduce the search
|
|
206
|
-
* space we must traverse.
|
|
207
|
-
*
|
|
208
|
-
* Sequence numbers in `moveSeqs` are sorted to accelerate bookkeeping.
|
|
209
|
-
*
|
|
210
|
-
* See https://github.com/microsoft/FluidFramework/blob/main/packages/dds/merge-tree/docs/Obliterate.md#remote-perspective
|
|
211
|
-
* for additional context
|
|
212
|
-
*/
|
|
213
|
-
this.moveSeqs = [];
|
|
214
|
-
/**
|
|
215
|
-
* Similar to moveSeqs, but tracks local moves. These are not the move
|
|
216
|
-
* operations within the collab window, but rather local moves that have
|
|
217
|
-
* not been acked.
|
|
218
|
-
*/
|
|
219
|
-
this.localMoveSeqs = new Set();
|
|
220
|
-
/**
|
|
221
|
-
* Groups of segments moved by local moves/obliterates
|
|
222
|
-
*
|
|
223
|
-
* When a local obliterate is acked, we must also ack segments that were
|
|
224
|
-
* concurrently obliterated on insert. We check this segment group to find
|
|
225
|
-
* such segments
|
|
226
|
-
*/
|
|
227
|
-
// eslint-disable-next-line import/no-deprecated
|
|
228
|
-
this.locallyMovedSegments = new Map();
|
|
257
|
+
this.obliterates = new Obliterates(this);
|
|
229
258
|
this.splitLeafSegment = (segment, pos) => {
|
|
230
259
|
if (!(pos > 0 && segment)) {
|
|
231
260
|
return {};
|
|
@@ -341,7 +370,6 @@ export class MergeTree {
|
|
|
341
370
|
childIndex++, nodeIndex++ // Advance to next child & node
|
|
342
371
|
) {
|
|
343
372
|
// Insert the next node into the current block
|
|
344
|
-
// TODO Non null asserting, why is this not null?
|
|
345
373
|
this.addNode(block, nodes[nodeIndex]);
|
|
346
374
|
}
|
|
347
375
|
// Calculate this block's info. Previously this was inlined into the above loop as a micro-optimization,
|
|
@@ -350,8 +378,7 @@ export class MergeTree {
|
|
|
350
378
|
this.blockUpdate(block);
|
|
351
379
|
}
|
|
352
380
|
return blocks.length === 1 // If there is only one block at this layer...
|
|
353
|
-
? //
|
|
354
|
-
blocks[0] // ...then we're done. Return the root.
|
|
381
|
+
? blocks[0] // ...then we're done. Return the root.
|
|
355
382
|
: buildMergeBlock(blocks); // ...otherwise recursively build the next layer above blocks.
|
|
356
383
|
};
|
|
357
384
|
if (segments.length > 0) {
|
|
@@ -399,7 +426,6 @@ export class MergeTree {
|
|
|
399
426
|
while (parent) {
|
|
400
427
|
const children = parent.children;
|
|
401
428
|
for (let childIndex = 0; childIndex < parent.childCount; childIndex++) {
|
|
402
|
-
// TODO Non null asserting, why is this not null?
|
|
403
429
|
const child = children[childIndex];
|
|
404
430
|
if ((!!prevParent && child === prevParent) || child === node) {
|
|
405
431
|
break;
|
|
@@ -614,8 +640,7 @@ export class MergeTree {
|
|
|
614
640
|
assert(this.collabWindow.minSeq <= minSeq, 0x04f /* "minSeq of collab window > target minSeq!" */);
|
|
615
641
|
if (minSeq > this.collabWindow.minSeq) {
|
|
616
642
|
this.collabWindow.minSeq = minSeq;
|
|
617
|
-
|
|
618
|
-
this.moveSeqs = firstMoveSeqIdx === -1 ? [] : this.moveSeqs.slice(firstMoveSeqIdx);
|
|
643
|
+
this.obliterates.setMinSeq(minSeq);
|
|
619
644
|
if (MergeTree.options.zamboniSegments) {
|
|
620
645
|
zamboniSegments(this);
|
|
621
646
|
}
|
|
@@ -723,34 +748,8 @@ export class MergeTree {
|
|
|
723
748
|
const deltaSegments = [];
|
|
724
749
|
const overlappingRemoves = [];
|
|
725
750
|
pendingSegmentGroup.segments.map((pendingSegment) => {
|
|
726
|
-
const localMovedSeq = pendingSegment.localMovedSeq;
|
|
727
751
|
const overlappingRemove = !pendingSegment.ack(pendingSegmentGroup, opArgs);
|
|
728
|
-
if (opArgs.op.type === MergeTreeDeltaType.OBLITERATE && localMovedSeq !== undefined) {
|
|
729
|
-
const locallyMovedSegments = this.locallyMovedSegments.get(localMovedSeq);
|
|
730
|
-
if (locallyMovedSegments) {
|
|
731
|
-
// Disabling because a for of loop causes the type of segment to be ISegment, which does not have parent information stored
|
|
732
|
-
// eslint-disable-next-line unicorn/no-array-for-each
|
|
733
|
-
locallyMovedSegments.segments.forEach((segment) => {
|
|
734
|
-
segment.localMovedSeq = undefined;
|
|
735
|
-
if (!nodesToUpdate.includes(segment.parent)) {
|
|
736
|
-
nodesToUpdate.push(segment.parent);
|
|
737
|
-
}
|
|
738
|
-
if (segment.movedSeq === UnassignedSequenceNumber) {
|
|
739
|
-
segment.movedSeq = seq;
|
|
740
|
-
}
|
|
741
|
-
});
|
|
742
|
-
this.locallyMovedSegments.delete(localMovedSeq);
|
|
743
|
-
}
|
|
744
|
-
}
|
|
745
752
|
overwrite = overlappingRemove || overwrite;
|
|
746
|
-
if (opArgs.op.type === MergeTreeDeltaType.OBLITERATE) {
|
|
747
|
-
if (seq !== this.moveSeqs[this.moveSeqs.length - 1]) {
|
|
748
|
-
this.moveSeqs.push(seq);
|
|
749
|
-
}
|
|
750
|
-
if (localMovedSeq !== undefined) {
|
|
751
|
-
this.localMoveSeqs.delete(localMovedSeq);
|
|
752
|
-
}
|
|
753
|
-
}
|
|
754
753
|
overlappingRemoves.push(overlappingRemove);
|
|
755
754
|
if (MergeTree.options.zamboniSegments) {
|
|
756
755
|
this.addToLRUSet(pendingSegment, seq);
|
|
@@ -762,6 +761,9 @@ export class MergeTree {
|
|
|
762
761
|
segment: pendingSegment,
|
|
763
762
|
});
|
|
764
763
|
});
|
|
764
|
+
if (opArgs.op.type === MergeTreeDeltaType.OBLITERATE) {
|
|
765
|
+
this.obliterates.addOrUpdate(pendingSegmentGroup.obliterateInfo);
|
|
766
|
+
}
|
|
765
767
|
// Perform slides after all segments have been acked, so that
|
|
766
768
|
// positions after slide are final
|
|
767
769
|
if (opArgs.op.type === MergeTreeDeltaType.REMOVE ||
|
|
@@ -967,90 +969,54 @@ export class MergeTree {
|
|
|
967
969
|
this.updateRoot(splitNode);
|
|
968
970
|
saveIfLocal(newSegment);
|
|
969
971
|
insertPos += newSegment.cachedLength;
|
|
970
|
-
if (!this.options?.mergeTreeEnableObliterate) {
|
|
971
|
-
continue;
|
|
972
|
-
}
|
|
973
|
-
let moveUpperBound = Number.POSITIVE_INFINITY;
|
|
974
|
-
const smallestSeqMoveOp = this.getSmallestSeqMoveOp();
|
|
975
|
-
if (smallestSeqMoveOp === undefined) {
|
|
972
|
+
if (!this.options?.mergeTreeEnableObliterate || this.obliterates.empty()) {
|
|
976
973
|
continue;
|
|
977
974
|
}
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
let
|
|
981
|
-
|
|
982
|
-
let
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
return moveUpperBound >= smallestSeqMoveOp;
|
|
1001
|
-
};
|
|
1002
|
-
const findRightMovedSegment = (seg) => {
|
|
1003
|
-
const movedSeqs = seg.movedSeqs?.filter((movedSeq) => movedSeq >= refSeq) ?? [];
|
|
1004
|
-
const localMovedSeqs = seg.localMovedSeq ? [seg.localMovedSeq] : [];
|
|
1005
|
-
for (const movedSeq of movedSeqs) {
|
|
1006
|
-
const left = leftAckedSegments[movedSeq];
|
|
1007
|
-
if (left) {
|
|
1008
|
-
_movedSeq = movedSeq;
|
|
1009
|
-
const clientIdIdx = left.movedSeqs?.indexOf(movedSeq) ?? -1;
|
|
1010
|
-
const movedClientId = left.movedClientIds?.[clientIdIdx];
|
|
1011
|
-
assert(movedClientId !== undefined, 0x869 /* expected client id to exist */);
|
|
1012
|
-
movedClientIds = [movedClientId];
|
|
1013
|
-
return false;
|
|
975
|
+
// eslint-disable-next-line import/no-deprecated
|
|
976
|
+
let oldest;
|
|
977
|
+
let normalizedOldestSeq = 0;
|
|
978
|
+
// eslint-disable-next-line import/no-deprecated
|
|
979
|
+
let newest;
|
|
980
|
+
let normalizedNewestSeq = 0;
|
|
981
|
+
const movedClientIds = [];
|
|
982
|
+
const movedSeqs = [];
|
|
983
|
+
for (const ob of this.obliterates.findOverlapping(newSegment)) {
|
|
984
|
+
// compute a normalized seq that takes into account local seqs
|
|
985
|
+
// but is still comparable to remote seqs to keep the checks below easy
|
|
986
|
+
// REMOTE SEQUENCE NUMBERS LOCAL SEQUENCE NUMBERS
|
|
987
|
+
// [0, 1, 2, 3, ..., 100, ..., 1000, ..., (MAX - MaxLocalSeq), L1, L2, L3, L4, ..., L100, ..., L1000, ...(MAX)]
|
|
988
|
+
const normalizedObSeq = ob.seq === UnassignedSequenceNumber
|
|
989
|
+
? Number.MAX_SAFE_INTEGER - this.collabWindow.localSeq + ob.localSeq
|
|
990
|
+
: ob.seq;
|
|
991
|
+
if (normalizedObSeq > refSeq) {
|
|
992
|
+
if (oldest === undefined || normalizedOldestSeq > normalizedObSeq) {
|
|
993
|
+
normalizedOldestSeq = normalizedObSeq;
|
|
994
|
+
oldest = ob;
|
|
995
|
+
movedClientIds.unshift(ob.clientId);
|
|
996
|
+
movedSeqs.unshift(ob.seq);
|
|
1014
997
|
}
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
assert(movedClientId !== undefined, 0x86a /* expected client id to exist */);
|
|
1023
|
-
movedClientIds = [movedClientId];
|
|
1024
|
-
return false;
|
|
998
|
+
else {
|
|
999
|
+
if (newest === undefined || normalizedNewestSeq < normalizedObSeq) {
|
|
1000
|
+
normalizedNewestSeq = normalizedObSeq;
|
|
1001
|
+
newest = ob;
|
|
1002
|
+
}
|
|
1003
|
+
movedClientIds.push(ob.clientId);
|
|
1004
|
+
movedSeqs.push(ob.seq);
|
|
1025
1005
|
}
|
|
1026
1006
|
}
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
}
|
|
1030
|
-
if (!isRemoved(seg) || wasRemovedAfter(seg, moveUpperBound)) {
|
|
1031
|
-
moveUpperBound = Math.min(moveUpperBound, seg.seq ?? Number.POSITIVE_INFINITY);
|
|
1032
|
-
}
|
|
1033
|
-
// If we've reached a segment that existed before any of our in-collab-window move ops
|
|
1034
|
-
// happened, no need to continue.
|
|
1035
|
-
return moveUpperBound >= smallestSeqMoveOp;
|
|
1036
|
-
};
|
|
1037
|
-
backwardExcursion(newSegment, findLeftMovedSegment);
|
|
1038
|
-
moveUpperBound = Number.POSITIVE_INFINITY;
|
|
1039
|
-
forwardExcursion(newSegment, findRightMovedSegment);
|
|
1040
|
-
if (_localMovedSeq !== undefined || _movedSeq !== undefined) {
|
|
1041
|
-
assert(movedClientIds !== undefined, 0x86b /* movedClientIds should be set if local/moved seq is set */);
|
|
1007
|
+
}
|
|
1008
|
+
if (oldest && newest?.clientId !== clientId) {
|
|
1042
1009
|
const moveInfo = {
|
|
1043
1010
|
movedClientIds,
|
|
1044
|
-
movedSeq:
|
|
1045
|
-
movedSeqs
|
|
1046
|
-
localMovedSeq:
|
|
1047
|
-
wasMovedOnInsert:
|
|
1011
|
+
movedSeq: oldest.seq,
|
|
1012
|
+
movedSeqs,
|
|
1013
|
+
localMovedSeq: oldest.localSeq,
|
|
1014
|
+
wasMovedOnInsert: oldest.seq !== UnassignedSequenceNumber,
|
|
1048
1015
|
};
|
|
1049
1016
|
markSegmentMoved(newSegment, moveInfo);
|
|
1050
1017
|
if (moveInfo.localMovedSeq !== undefined) {
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
this.addToPendingList(newSegment, movedSegmentGroup, localSeq);
|
|
1018
|
+
assert(oldest.segmentGroup !== undefined, 0x86c /* expected segment group to exist */);
|
|
1019
|
+
this.addToPendingList(newSegment, oldest.segmentGroup);
|
|
1054
1020
|
}
|
|
1055
1021
|
if (newSegment.parent) {
|
|
1056
1022
|
this.blockUpdatePathLengths(newSegment.parent, seq, clientId);
|
|
@@ -1088,9 +1054,6 @@ export class MergeTree {
|
|
|
1088
1054
|
return true;
|
|
1089
1055
|
}
|
|
1090
1056
|
}
|
|
1091
|
-
getSmallestSeqMoveOp() {
|
|
1092
|
-
return this.moveSeqs[0] ?? (this.localMoveSeqs.size > 0 ? -1 : undefined);
|
|
1093
|
-
}
|
|
1094
1057
|
insertingWalk(block, pos, refSeq, clientId, seq, context, isLastChildBlock = true) {
|
|
1095
1058
|
let _pos;
|
|
1096
1059
|
if (pos === "start") {
|
|
@@ -1108,7 +1071,6 @@ export class MergeTree {
|
|
|
1108
1071
|
let newNode;
|
|
1109
1072
|
let fromSplit;
|
|
1110
1073
|
for (childIndex = 0; childIndex < block.childCount; childIndex++) {
|
|
1111
|
-
// TODO Non null asserting, why is this not null?
|
|
1112
1074
|
child = children[childIndex];
|
|
1113
1075
|
// ensure we walk down the far edge of the tree, even if all sub-tree is eligible for zamboni
|
|
1114
1076
|
const isLastNonLeafBlock = isLastChildBlock && !child.isLeaf() && childIndex === block.childCount - 1;
|
|
@@ -1173,9 +1135,7 @@ export class MergeTree {
|
|
|
1173
1135
|
}
|
|
1174
1136
|
if (newNode) {
|
|
1175
1137
|
for (let i = block.childCount; i > childIndex; i--) {
|
|
1176
|
-
// TODO Non null asserting, why is this not null?
|
|
1177
1138
|
block.children[i] = block.children[i - 1];
|
|
1178
|
-
// TODO Non null asserting, why is this not null?
|
|
1179
1139
|
block.children[i].index = i;
|
|
1180
1140
|
}
|
|
1181
1141
|
block.assignChild(newNode, childIndex, false);
|
|
@@ -1207,7 +1167,6 @@ export class MergeTree {
|
|
|
1207
1167
|
// Update ordinals to reflect lowered child count
|
|
1208
1168
|
this.nodeUpdateOrdinals(node);
|
|
1209
1169
|
for (let i = 0; i < halfCount; i++) {
|
|
1210
|
-
// TODO Non null asserting, why is this not null?
|
|
1211
1170
|
newNode.assignChild(node.children[halfCount + i], i, false);
|
|
1212
1171
|
node.children[halfCount + i] = undefined;
|
|
1213
1172
|
}
|
|
@@ -1217,7 +1176,6 @@ export class MergeTree {
|
|
|
1217
1176
|
}
|
|
1218
1177
|
nodeUpdateOrdinals(block) {
|
|
1219
1178
|
for (let i = 0; i < block.childCount; i++) {
|
|
1220
|
-
// TODO Non null asserting, why is this not null?
|
|
1221
1179
|
const child = block.children[i];
|
|
1222
1180
|
block.setOrdinal(child, i);
|
|
1223
1181
|
if (!child.isLeaf()) {
|
|
@@ -1294,20 +1252,30 @@ export class MergeTree {
|
|
|
1294
1252
|
const localOverlapWithRefs = [];
|
|
1295
1253
|
const movedSegments = [];
|
|
1296
1254
|
const localSeq = seq === UnassignedSequenceNumber ? ++this.collabWindow.localSeq : undefined;
|
|
1297
|
-
if (seq !== UnassignedSequenceNumber && seq !== this.moveSeqs[this.moveSeqs.length - 1]) {
|
|
1298
|
-
this.moveSeqs.push(seq);
|
|
1299
|
-
}
|
|
1300
|
-
else if (seq === UnassignedSequenceNumber && localSeq !== undefined) {
|
|
1301
|
-
this.localMoveSeqs.add(localSeq);
|
|
1302
|
-
}
|
|
1303
1255
|
// eslint-disable-next-line import/no-deprecated
|
|
1304
|
-
|
|
1256
|
+
const obliterate = {
|
|
1257
|
+
clientId,
|
|
1258
|
+
end: createDetachedLocalReferencePosition(undefined),
|
|
1259
|
+
refSeq,
|
|
1260
|
+
seq,
|
|
1261
|
+
start: createDetachedLocalReferencePosition(undefined),
|
|
1262
|
+
localSeq,
|
|
1263
|
+
segmentGroup: undefined,
|
|
1264
|
+
};
|
|
1265
|
+
const normalizedStartPos = startPos === "start" || startPos === undefined ? 0 : startPos;
|
|
1266
|
+
const normalizedEndPos = endPos === "end" || endPos === undefined ? this.getLength(refSeq, clientId) : endPos;
|
|
1267
|
+
const { segment: startSeg } = this.getContainingSegment(normalizedStartPos, refSeq, clientId);
|
|
1268
|
+
const { segment: endSeg } = this.getContainingSegment(normalizedEndPos - 1, refSeq, clientId);
|
|
1269
|
+
assert(startSeg !== undefined && endSeg !== undefined, 0xa3f /* segments cannot be undefined */);
|
|
1270
|
+
obliterate.start = this.createLocalReferencePosition(startSeg, 0, ReferenceType.StayOnRemove, {
|
|
1271
|
+
obliterate,
|
|
1272
|
+
});
|
|
1273
|
+
obliterate.end = this.createLocalReferencePosition(endSeg, endSeg.cachedLength - 1, ReferenceType.StayOnRemove, {
|
|
1274
|
+
obliterate,
|
|
1275
|
+
});
|
|
1305
1276
|
const markMoved = (segment, pos, _start, _end) => {
|
|
1277
|
+
var _a;
|
|
1306
1278
|
const existingMoveInfo = toMoveInfo(segment);
|
|
1307
|
-
if (startSide)
|
|
1308
|
-
segment.startSide = startSide;
|
|
1309
|
-
if (endSide)
|
|
1310
|
-
segment.endSide = endSide;
|
|
1311
1279
|
if (clientId !== segment.clientId &&
|
|
1312
1280
|
segment.seq !== undefined &&
|
|
1313
1281
|
seq !== UnassignedSequenceNumber &&
|
|
@@ -1347,7 +1315,8 @@ export class MergeTree {
|
|
|
1347
1315
|
if (this.collabWindow.collaborating) {
|
|
1348
1316
|
if (segment.movedSeq === UnassignedSequenceNumber &&
|
|
1349
1317
|
clientId === this.collabWindow.clientId) {
|
|
1350
|
-
segmentGroup = this.addToPendingList(segment, segmentGroup, localSeq);
|
|
1318
|
+
obliterate.segmentGroup = this.addToPendingList(segment, obliterate.segmentGroup, localSeq);
|
|
1319
|
+
(_a = obliterate.segmentGroup).obliterateInfo ?? (_a.obliterateInfo = obliterate);
|
|
1351
1320
|
}
|
|
1352
1321
|
else {
|
|
1353
1322
|
if (MergeTree.options.zamboniSegments) {
|
|
@@ -1367,6 +1336,7 @@ export class MergeTree {
|
|
|
1367
1336
|
return true;
|
|
1368
1337
|
};
|
|
1369
1338
|
this.nodeMap(refSeq, clientId, markMoved, undefined, afterMarkMoved, start, end, undefined, seq === UnassignedSequenceNumber ? undefined : seq);
|
|
1339
|
+
this.obliterates.addOrUpdate(obliterate);
|
|
1370
1340
|
this.slideAckedRemovedSegmentReferences(localOverlapWithRefs);
|
|
1371
1341
|
// opArgs == undefined => test code
|
|
1372
1342
|
if (movedSegments.length > 0) {
|
|
@@ -1375,9 +1345,6 @@ export class MergeTree {
|
|
|
1375
1345
|
deltaSegments: movedSegments,
|
|
1376
1346
|
});
|
|
1377
1347
|
}
|
|
1378
|
-
if (segmentGroup && localSeq !== undefined) {
|
|
1379
|
-
this.locallyMovedSegments.set(localSeq, segmentGroup);
|
|
1380
|
-
}
|
|
1381
1348
|
// these events are newly removed
|
|
1382
1349
|
// so we slide after eventing in case the consumer wants to make reference
|
|
1383
1350
|
// changes at remove time, like add a ref to track undo redo.
|
|
@@ -1524,7 +1491,6 @@ export class MergeTree {
|
|
|
1524
1491
|
this.markRangeRemoved(start, start + segment.cachedLength, UniversalSequenceNumber, this.collabWindow.clientId, UniversalSequenceNumber, false, { op: removeOp });
|
|
1525
1492
|
} /* op.type === MergeTreeDeltaType.ANNOTATE */
|
|
1526
1493
|
else {
|
|
1527
|
-
// TODO Non null asserting, why is this not null?
|
|
1528
1494
|
const props = pendingSegmentGroup.previousProps[i];
|
|
1529
1495
|
const annotateOp = createAnnotateRangeOp(start, start + segment.cachedLength, props);
|
|
1530
1496
|
this.annotateRange(start, start + segment.cachedLength, props, UniversalSequenceNumber, this.collabWindow.clientId, UniversalSequenceNumber, { op: annotateOp },
|
|
@@ -1571,7 +1537,7 @@ export class MergeTree {
|
|
|
1571
1537
|
if (_segment !== "start" &&
|
|
1572
1538
|
_segment !== "end" &&
|
|
1573
1539
|
isRemovedAndAckedOrMovedAndAcked(_segment) &&
|
|
1574
|
-
!refTypeIncludesFlag(refType, ReferenceType.SlideOnRemove | ReferenceType.Transient) &&
|
|
1540
|
+
!refTypeIncludesFlag(refType, ReferenceType.SlideOnRemove | ReferenceType.Transient | ReferenceType.StayOnRemove) &&
|
|
1575
1541
|
_segment.endpointType === undefined) {
|
|
1576
1542
|
throw new UsageError("Can only create SlideOnRemove or Transient local reference position on a removed or obliterated segment");
|
|
1577
1543
|
}
|
|
@@ -1644,7 +1610,6 @@ export class MergeTree {
|
|
|
1644
1610
|
}
|
|
1645
1611
|
}
|
|
1646
1612
|
for (let i = 0; i < newOrder.length; i++) {
|
|
1647
|
-
// TODO Non null asserting, why is this not null?
|
|
1648
1613
|
const seg = newOrder[i];
|
|
1649
1614
|
const { parent, index, ordinal } = currentOrder[i];
|
|
1650
1615
|
parent?.assignChild(seg, index, false);
|
|
@@ -1731,7 +1696,6 @@ export class MergeTree {
|
|
|
1731
1696
|
const rightmostTiles = createMap();
|
|
1732
1697
|
const leftmostTiles = createMap();
|
|
1733
1698
|
for (let i = 0; i < block.childCount; i++) {
|
|
1734
|
-
// TODO Non null asserting, why is this not null?
|
|
1735
1699
|
const node = block.children[i];
|
|
1736
1700
|
const nodeLength = nodeTotalLength(this, node);
|
|
1737
1701
|
if (nodeLength !== undefined) {
|