@fluidframework/merge-tree 2.3.0-288113 → 2.4.0-294316
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 -2
- 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 -5
- 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 +2 -32
- package/dist/mergeTree.d.ts.map +1 -1
- package/dist/mergeTree.js +137 -199
- package/dist/mergeTree.js.map +1 -1
- package/dist/mergeTreeNodes.d.ts +16 -10
- 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.partialLength.spec.js +8 -4
- package/dist/test/obliterate.partialLength.spec.js.map +1 -1
- package/dist/test/obliterate.spec.js +66 -5
- 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/testClient.d.ts +1 -11
- package/dist/test/testClient.d.ts.map +1 -1
- package/dist/test/testClient.js +0 -3
- package/dist/test/testClient.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/test/testUtils.d.ts +10 -0
- package/dist/test/testUtils.d.ts.map +1 -1
- package/dist/test/testUtils.js +5 -1
- package/dist/test/testUtils.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 -5
- 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 +2 -32
- package/lib/mergeTree.d.ts.map +1 -1
- package/lib/mergeTree.js +138 -200
- package/lib/mergeTree.js.map +1 -1
- package/lib/mergeTreeNodes.d.ts +16 -10
- 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.partialLength.spec.js +9 -5
- package/lib/test/obliterate.partialLength.spec.js.map +1 -1
- package/lib/test/obliterate.spec.js +67 -6
- 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/testClient.d.ts +1 -11
- package/lib/test/testClient.d.ts.map +1 -1
- package/lib/test/testClient.js +0 -3
- package/lib/test/testClient.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/test/testUtils.d.ts +10 -0
- package/lib/test/testUtils.d.ts.map +1 -1
- package/lib/test/testUtils.js +3 -0
- package/lib/test/testUtils.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 +13 -11
- package/src/index.ts +1 -0
- package/src/localReference.ts +1 -3
- package/src/mergeTree.ts +188 -258
- package/src/mergeTreeNodes.ts +22 -12
- 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 {
|
|
@@ -23,12 +23,8 @@ import { createMap, extend, extendIfUndefined } from "./properties.js";
|
|
|
23
23
|
import { DetachedReferencePosition, refGetTileLabels, refHasTileLabel, refTypeIncludesFlag, } from "./referencePositions.js";
|
|
24
24
|
// eslint-disable-next-line import/no-deprecated
|
|
25
25
|
import { PropertiesRollback } from "./segmentPropertiesManager.js";
|
|
26
|
-
import {
|
|
26
|
+
import { SortedSegmentSet } from "./sortedSegmentSet.js";
|
|
27
27
|
import { zamboniSegments } from "./zamboni.js";
|
|
28
|
-
function wasRemovedAfter(seg, seq) {
|
|
29
|
-
return (seg.removedSeq !== UnassignedSequenceNumber &&
|
|
30
|
-
(seg.removedSeq === undefined || seg.removedSeq > seq));
|
|
31
|
-
}
|
|
32
28
|
function markSegmentMoved(seg, moveInfo) {
|
|
33
29
|
seg.moveDst = moveInfo.moveDst;
|
|
34
30
|
seg.movedClientIds = [...moveInfo.movedClientIds];
|
|
@@ -176,6 +172,67 @@ export function getSlideToSegoff(segoff, slidingPreference = SlidingPreference.F
|
|
|
176
172
|
}
|
|
177
173
|
const forwardPred = (ref) => ref.slidingPreference !== SlidingPreference.BACKWARD;
|
|
178
174
|
const backwardPred = (ref) => ref.slidingPreference === SlidingPreference.BACKWARD;
|
|
175
|
+
class Obliterates {
|
|
176
|
+
constructor(mergeTree) {
|
|
177
|
+
this.mergeTree = mergeTree;
|
|
178
|
+
/**
|
|
179
|
+
* Array containing the all move operations within the
|
|
180
|
+
* collab window.
|
|
181
|
+
*
|
|
182
|
+
* The moves are stored in sequence order which accelerates clean up in setMinSeq
|
|
183
|
+
*
|
|
184
|
+
* See https://github.com/microsoft/FluidFramework/blob/main/packages/dds/merge-tree/docs/Obliterate.md#remote-perspective
|
|
185
|
+
* for additional context
|
|
186
|
+
*/
|
|
187
|
+
// eslint-disable-next-line import/no-deprecated
|
|
188
|
+
this.seqOrdered = new DoublyLinkedList();
|
|
189
|
+
/**
|
|
190
|
+
* This contains a sorted lists of all obliterate starts
|
|
191
|
+
* and is used to accelerate finding overlapping obliterates
|
|
192
|
+
* as well as determining if there are any obliterates at all.
|
|
193
|
+
*/
|
|
194
|
+
this.startOrdered = new SortedSegmentSet();
|
|
195
|
+
}
|
|
196
|
+
setMinSeq(minSeq) {
|
|
197
|
+
// eslint-disable-next-line @typescript-eslint/no-non-null-asserted-optional-chain
|
|
198
|
+
while (!this.seqOrdered.empty && this.seqOrdered.first?.data.seq <= minSeq) {
|
|
199
|
+
const ob = this.seqOrdered.shift();
|
|
200
|
+
this.startOrdered.remove(ob.data.start);
|
|
201
|
+
this.mergeTree.removeLocalReferencePosition(ob.data.start);
|
|
202
|
+
this.mergeTree.removeLocalReferencePosition(ob.data.end);
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
// eslint-disable-next-line import/no-deprecated
|
|
206
|
+
addOrUpdate(obliterateInfo) {
|
|
207
|
+
const { seq, start } = obliterateInfo;
|
|
208
|
+
if (seq !== UnassignedSequenceNumber) {
|
|
209
|
+
this.seqOrdered.push(obliterateInfo);
|
|
210
|
+
}
|
|
211
|
+
this.startOrdered.addOrUpdate(start);
|
|
212
|
+
}
|
|
213
|
+
empty() {
|
|
214
|
+
return this.startOrdered.size === 0;
|
|
215
|
+
}
|
|
216
|
+
// eslint-disable-next-line import/no-deprecated
|
|
217
|
+
findOverlapping(seg) {
|
|
218
|
+
// eslint-disable-next-line import/no-deprecated
|
|
219
|
+
const overlapping = [];
|
|
220
|
+
for (const start of this.startOrdered.items) {
|
|
221
|
+
if (start.getSegment().ordinal <= seg.ordinal) {
|
|
222
|
+
// eslint-disable-next-line import/no-deprecated
|
|
223
|
+
const ob = start.properties?.obliterate;
|
|
224
|
+
if (ob.end.getSegment().ordinal >= seg.ordinal) {
|
|
225
|
+
overlapping.push(ob);
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
else {
|
|
229
|
+
// the start is past the seg, so exit
|
|
230
|
+
break;
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
return overlapping;
|
|
234
|
+
}
|
|
235
|
+
}
|
|
179
236
|
/**
|
|
180
237
|
* @internal
|
|
181
238
|
*/
|
|
@@ -196,36 +253,7 @@ export class MergeTree {
|
|
|
196
253
|
// for now assume only markers have ids and so point directly at the Segment
|
|
197
254
|
// if we need to have pointers to non-markers, we can change to point at local refs
|
|
198
255
|
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();
|
|
256
|
+
this.obliterates = new Obliterates(this);
|
|
229
257
|
this.splitLeafSegment = (segment, pos) => {
|
|
230
258
|
if (!(pos > 0 && segment)) {
|
|
231
259
|
return {};
|
|
@@ -341,7 +369,6 @@ export class MergeTree {
|
|
|
341
369
|
childIndex++, nodeIndex++ // Advance to next child & node
|
|
342
370
|
) {
|
|
343
371
|
// Insert the next node into the current block
|
|
344
|
-
// TODO Non null asserting, why is this not null?
|
|
345
372
|
this.addNode(block, nodes[nodeIndex]);
|
|
346
373
|
}
|
|
347
374
|
// Calculate this block's info. Previously this was inlined into the above loop as a micro-optimization,
|
|
@@ -350,8 +377,7 @@ export class MergeTree {
|
|
|
350
377
|
this.blockUpdate(block);
|
|
351
378
|
}
|
|
352
379
|
return blocks.length === 1 // If there is only one block at this layer...
|
|
353
|
-
? //
|
|
354
|
-
blocks[0] // ...then we're done. Return the root.
|
|
380
|
+
? blocks[0] // ...then we're done. Return the root.
|
|
355
381
|
: buildMergeBlock(blocks); // ...otherwise recursively build the next layer above blocks.
|
|
356
382
|
};
|
|
357
383
|
if (segments.length > 0) {
|
|
@@ -399,7 +425,6 @@ export class MergeTree {
|
|
|
399
425
|
while (parent) {
|
|
400
426
|
const children = parent.children;
|
|
401
427
|
for (let childIndex = 0; childIndex < parent.childCount; childIndex++) {
|
|
402
|
-
// TODO Non null asserting, why is this not null?
|
|
403
428
|
const child = children[childIndex];
|
|
404
429
|
if ((!!prevParent && child === prevParent) || child === node) {
|
|
405
430
|
break;
|
|
@@ -614,8 +639,7 @@ export class MergeTree {
|
|
|
614
639
|
assert(this.collabWindow.minSeq <= minSeq, 0x04f /* "minSeq of collab window > target minSeq!" */);
|
|
615
640
|
if (minSeq > this.collabWindow.minSeq) {
|
|
616
641
|
this.collabWindow.minSeq = minSeq;
|
|
617
|
-
|
|
618
|
-
this.moveSeqs = firstMoveSeqIdx === -1 ? [] : this.moveSeqs.slice(firstMoveSeqIdx);
|
|
642
|
+
this.obliterates.setMinSeq(minSeq);
|
|
619
643
|
if (MergeTree.options.zamboniSegments) {
|
|
620
644
|
zamboniSegments(this);
|
|
621
645
|
}
|
|
@@ -723,34 +747,8 @@ export class MergeTree {
|
|
|
723
747
|
const deltaSegments = [];
|
|
724
748
|
const overlappingRemoves = [];
|
|
725
749
|
pendingSegmentGroup.segments.map((pendingSegment) => {
|
|
726
|
-
const localMovedSeq = pendingSegment.localMovedSeq;
|
|
727
750
|
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
751
|
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
752
|
overlappingRemoves.push(overlappingRemove);
|
|
755
753
|
if (MergeTree.options.zamboniSegments) {
|
|
756
754
|
this.addToLRUSet(pendingSegment, seq);
|
|
@@ -762,6 +760,9 @@ export class MergeTree {
|
|
|
762
760
|
segment: pendingSegment,
|
|
763
761
|
});
|
|
764
762
|
});
|
|
763
|
+
if (opArgs.op.type === MergeTreeDeltaType.OBLITERATE) {
|
|
764
|
+
this.obliterates.addOrUpdate(pendingSegmentGroup.obliterateInfo);
|
|
765
|
+
}
|
|
765
766
|
// Perform slides after all segments have been acked, so that
|
|
766
767
|
// positions after slide are final
|
|
767
768
|
if (opArgs.op.type === MergeTreeDeltaType.REMOVE ||
|
|
@@ -967,90 +968,54 @@ export class MergeTree {
|
|
|
967
968
|
this.updateRoot(splitNode);
|
|
968
969
|
saveIfLocal(newSegment);
|
|
969
970
|
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) {
|
|
971
|
+
if (!this.options?.mergeTreeEnableObliterate || this.obliterates.empty()) {
|
|
976
972
|
continue;
|
|
977
973
|
}
|
|
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;
|
|
974
|
+
// eslint-disable-next-line import/no-deprecated
|
|
975
|
+
let oldest;
|
|
976
|
+
let normalizedOldestSeq = 0;
|
|
977
|
+
// eslint-disable-next-line import/no-deprecated
|
|
978
|
+
let newest;
|
|
979
|
+
let normalizedNewestSeq = 0;
|
|
980
|
+
const movedClientIds = [];
|
|
981
|
+
const movedSeqs = [];
|
|
982
|
+
for (const ob of this.obliterates.findOverlapping(newSegment)) {
|
|
983
|
+
// compute a normalized seq that takes into account local seqs
|
|
984
|
+
// but is still comparable to remote seqs to keep the checks below easy
|
|
985
|
+
// REMOTE SEQUENCE NUMBERS LOCAL SEQUENCE NUMBERS
|
|
986
|
+
// [0, 1, 2, 3, ..., 100, ..., 1000, ..., (MAX - MaxLocalSeq), L1, L2, L3, L4, ..., L100, ..., L1000, ...(MAX)]
|
|
987
|
+
const normalizedObSeq = ob.seq === UnassignedSequenceNumber
|
|
988
|
+
? Number.MAX_SAFE_INTEGER - this.collabWindow.localSeq + ob.localSeq
|
|
989
|
+
: ob.seq;
|
|
990
|
+
if (normalizedObSeq > refSeq) {
|
|
991
|
+
if (oldest === undefined || normalizedOldestSeq > normalizedObSeq) {
|
|
992
|
+
normalizedOldestSeq = normalizedObSeq;
|
|
993
|
+
oldest = ob;
|
|
994
|
+
movedClientIds.unshift(ob.clientId);
|
|
995
|
+
movedSeqs.unshift(ob.seq);
|
|
1014
996
|
}
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
assert(movedClientId !== undefined, 0x86a /* expected client id to exist */);
|
|
1023
|
-
movedClientIds = [movedClientId];
|
|
1024
|
-
return false;
|
|
997
|
+
else {
|
|
998
|
+
if (newest === undefined || normalizedNewestSeq < normalizedObSeq) {
|
|
999
|
+
normalizedNewestSeq = normalizedObSeq;
|
|
1000
|
+
newest = ob;
|
|
1001
|
+
}
|
|
1002
|
+
movedClientIds.push(ob.clientId);
|
|
1003
|
+
movedSeqs.push(ob.seq);
|
|
1025
1004
|
}
|
|
1026
1005
|
}
|
|
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 */);
|
|
1006
|
+
}
|
|
1007
|
+
if (oldest && newest?.clientId !== clientId) {
|
|
1042
1008
|
const moveInfo = {
|
|
1043
1009
|
movedClientIds,
|
|
1044
|
-
movedSeq:
|
|
1045
|
-
movedSeqs
|
|
1046
|
-
localMovedSeq:
|
|
1047
|
-
wasMovedOnInsert:
|
|
1010
|
+
movedSeq: oldest.seq,
|
|
1011
|
+
movedSeqs,
|
|
1012
|
+
localMovedSeq: oldest.localSeq,
|
|
1013
|
+
wasMovedOnInsert: oldest.seq !== UnassignedSequenceNumber,
|
|
1048
1014
|
};
|
|
1049
1015
|
markSegmentMoved(newSegment, moveInfo);
|
|
1050
1016
|
if (moveInfo.localMovedSeq !== undefined) {
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
this.addToPendingList(newSegment, movedSegmentGroup, localSeq);
|
|
1017
|
+
assert(oldest.segmentGroup !== undefined, 0x86c /* expected segment group to exist */);
|
|
1018
|
+
this.addToPendingList(newSegment, oldest.segmentGroup);
|
|
1054
1019
|
}
|
|
1055
1020
|
if (newSegment.parent) {
|
|
1056
1021
|
this.blockUpdatePathLengths(newSegment.parent, seq, clientId);
|
|
@@ -1088,27 +1053,14 @@ export class MergeTree {
|
|
|
1088
1053
|
return true;
|
|
1089
1054
|
}
|
|
1090
1055
|
}
|
|
1091
|
-
getSmallestSeqMoveOp() {
|
|
1092
|
-
return this.moveSeqs[0] ?? (this.localMoveSeqs.size > 0 ? -1 : undefined);
|
|
1093
|
-
}
|
|
1094
1056
|
insertingWalk(block, pos, refSeq, clientId, seq, context, isLastChildBlock = true) {
|
|
1095
|
-
let _pos;
|
|
1096
|
-
if (pos === "start") {
|
|
1097
|
-
_pos = 0;
|
|
1098
|
-
}
|
|
1099
|
-
else if (pos === "end") {
|
|
1100
|
-
_pos = this.root.mergeTree?.getLength(refSeq, clientId) ?? 0;
|
|
1101
|
-
}
|
|
1102
|
-
else {
|
|
1103
|
-
_pos = pos;
|
|
1104
|
-
}
|
|
1057
|
+
let _pos = pos;
|
|
1105
1058
|
const children = block.children;
|
|
1106
1059
|
let childIndex;
|
|
1107
1060
|
let child;
|
|
1108
1061
|
let newNode;
|
|
1109
1062
|
let fromSplit;
|
|
1110
1063
|
for (childIndex = 0; childIndex < block.childCount; childIndex++) {
|
|
1111
|
-
// TODO Non null asserting, why is this not null?
|
|
1112
1064
|
child = children[childIndex];
|
|
1113
1065
|
// ensure we walk down the far edge of the tree, even if all sub-tree is eligible for zamboni
|
|
1114
1066
|
const isLastNonLeafBlock = isLastChildBlock && !child.isLeaf() && childIndex === block.childCount - 1;
|
|
@@ -1173,9 +1125,7 @@ export class MergeTree {
|
|
|
1173
1125
|
}
|
|
1174
1126
|
if (newNode) {
|
|
1175
1127
|
for (let i = block.childCount; i > childIndex; i--) {
|
|
1176
|
-
// TODO Non null asserting, why is this not null?
|
|
1177
1128
|
block.children[i] = block.children[i - 1];
|
|
1178
|
-
// TODO Non null asserting, why is this not null?
|
|
1179
1129
|
block.children[i].index = i;
|
|
1180
1130
|
}
|
|
1181
1131
|
block.assignChild(newNode, childIndex, false);
|
|
@@ -1207,7 +1157,6 @@ export class MergeTree {
|
|
|
1207
1157
|
// Update ordinals to reflect lowered child count
|
|
1208
1158
|
this.nodeUpdateOrdinals(node);
|
|
1209
1159
|
for (let i = 0; i < halfCount; i++) {
|
|
1210
|
-
// TODO Non null asserting, why is this not null?
|
|
1211
1160
|
newNode.assignChild(node.children[halfCount + i], i, false);
|
|
1212
1161
|
node.children[halfCount + i] = undefined;
|
|
1213
1162
|
}
|
|
@@ -1217,7 +1166,6 @@ export class MergeTree {
|
|
|
1217
1166
|
}
|
|
1218
1167
|
nodeUpdateOrdinals(block) {
|
|
1219
1168
|
for (let i = 0; i < block.childCount; i++) {
|
|
1220
|
-
// TODO Non null asserting, why is this not null?
|
|
1221
1169
|
const child = block.children[i];
|
|
1222
1170
|
block.setOrdinal(child, i);
|
|
1223
1171
|
if (!child.isLeaf()) {
|
|
@@ -1281,33 +1229,34 @@ export class MergeTree {
|
|
|
1281
1229
|
}
|
|
1282
1230
|
obliterateRange(start, end, refSeq, clientId, seq, overwrite = false, opArgs) {
|
|
1283
1231
|
errorIfOptionNotTrue(this.options, "mergeTreeEnableObliterate");
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
endPos !== undefined &&
|
|
1287
|
-
startSide !== undefined &&
|
|
1288
|
-
endSide !== undefined &&
|
|
1289
|
-
startPos !== "end" &&
|
|
1290
|
-
endPos !== "start", 0x9e2 /* start and end cannot be undefined because they were not passed in as undefined */);
|
|
1291
|
-
this.ensureIntervalBoundary(startPos, refSeq, clientId);
|
|
1292
|
-
this.ensureIntervalBoundary(endPos, refSeq, clientId);
|
|
1232
|
+
this.ensureIntervalBoundary(start, refSeq, clientId);
|
|
1233
|
+
this.ensureIntervalBoundary(end, refSeq, clientId);
|
|
1293
1234
|
let _overwrite = overwrite;
|
|
1294
1235
|
const localOverlapWithRefs = [];
|
|
1295
1236
|
const movedSegments = [];
|
|
1296
1237
|
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
1238
|
// eslint-disable-next-line import/no-deprecated
|
|
1304
|
-
|
|
1239
|
+
const obliterate = {
|
|
1240
|
+
clientId,
|
|
1241
|
+
end: createDetachedLocalReferencePosition(undefined),
|
|
1242
|
+
refSeq,
|
|
1243
|
+
seq,
|
|
1244
|
+
start: createDetachedLocalReferencePosition(undefined),
|
|
1245
|
+
localSeq,
|
|
1246
|
+
segmentGroup: undefined,
|
|
1247
|
+
};
|
|
1248
|
+
const { segment: startSeg } = this.getContainingSegment(start, refSeq, clientId);
|
|
1249
|
+
const { segment: endSeg } = this.getContainingSegment(end - 1, refSeq, clientId);
|
|
1250
|
+
assert(startSeg !== undefined && endSeg !== undefined, 0xa3f /* segments cannot be undefined */);
|
|
1251
|
+
obliterate.start = this.createLocalReferencePosition(startSeg, 0, ReferenceType.StayOnRemove, {
|
|
1252
|
+
obliterate,
|
|
1253
|
+
});
|
|
1254
|
+
obliterate.end = this.createLocalReferencePosition(endSeg, endSeg.cachedLength - 1, ReferenceType.StayOnRemove, {
|
|
1255
|
+
obliterate,
|
|
1256
|
+
});
|
|
1305
1257
|
const markMoved = (segment, pos, _start, _end) => {
|
|
1258
|
+
var _a;
|
|
1306
1259
|
const existingMoveInfo = toMoveInfo(segment);
|
|
1307
|
-
if (startSide)
|
|
1308
|
-
segment.startSide = startSide;
|
|
1309
|
-
if (endSide)
|
|
1310
|
-
segment.endSide = endSide;
|
|
1311
1260
|
if (clientId !== segment.clientId &&
|
|
1312
1261
|
segment.seq !== undefined &&
|
|
1313
1262
|
seq !== UnassignedSequenceNumber &&
|
|
@@ -1347,7 +1296,8 @@ export class MergeTree {
|
|
|
1347
1296
|
if (this.collabWindow.collaborating) {
|
|
1348
1297
|
if (segment.movedSeq === UnassignedSequenceNumber &&
|
|
1349
1298
|
clientId === this.collabWindow.clientId) {
|
|
1350
|
-
segmentGroup = this.addToPendingList(segment, segmentGroup, localSeq);
|
|
1299
|
+
obliterate.segmentGroup = this.addToPendingList(segment, obliterate.segmentGroup, localSeq);
|
|
1300
|
+
(_a = obliterate.segmentGroup).obliterateInfo ?? (_a.obliterateInfo = obliterate);
|
|
1351
1301
|
}
|
|
1352
1302
|
else {
|
|
1353
1303
|
if (MergeTree.options.zamboniSegments) {
|
|
@@ -1367,6 +1317,7 @@ export class MergeTree {
|
|
|
1367
1317
|
return true;
|
|
1368
1318
|
};
|
|
1369
1319
|
this.nodeMap(refSeq, clientId, markMoved, undefined, afterMarkMoved, start, end, undefined, seq === UnassignedSequenceNumber ? undefined : seq);
|
|
1320
|
+
this.obliterates.addOrUpdate(obliterate);
|
|
1370
1321
|
this.slideAckedRemovedSegmentReferences(localOverlapWithRefs);
|
|
1371
1322
|
// opArgs == undefined => test code
|
|
1372
1323
|
if (movedSegments.length > 0) {
|
|
@@ -1375,9 +1326,6 @@ export class MergeTree {
|
|
|
1375
1326
|
deltaSegments: movedSegments,
|
|
1376
1327
|
});
|
|
1377
1328
|
}
|
|
1378
|
-
if (segmentGroup && localSeq !== undefined) {
|
|
1379
|
-
this.locallyMovedSegments.set(localSeq, segmentGroup);
|
|
1380
|
-
}
|
|
1381
1329
|
// these events are newly removed
|
|
1382
1330
|
// so we slide after eventing in case the consumer wants to make reference
|
|
1383
1331
|
// changes at remove time, like add a ref to track undo redo.
|
|
@@ -1524,7 +1472,6 @@ export class MergeTree {
|
|
|
1524
1472
|
this.markRangeRemoved(start, start + segment.cachedLength, UniversalSequenceNumber, this.collabWindow.clientId, UniversalSequenceNumber, false, { op: removeOp });
|
|
1525
1473
|
} /* op.type === MergeTreeDeltaType.ANNOTATE */
|
|
1526
1474
|
else {
|
|
1527
|
-
// TODO Non null asserting, why is this not null?
|
|
1528
1475
|
const props = pendingSegmentGroup.previousProps[i];
|
|
1529
1476
|
const annotateOp = createAnnotateRangeOp(start, start + segment.cachedLength, props);
|
|
1530
1477
|
this.annotateRange(start, start + segment.cachedLength, props, UniversalSequenceNumber, this.collabWindow.clientId, UniversalSequenceNumber, { op: annotateOp },
|
|
@@ -1571,7 +1518,7 @@ export class MergeTree {
|
|
|
1571
1518
|
if (_segment !== "start" &&
|
|
1572
1519
|
_segment !== "end" &&
|
|
1573
1520
|
isRemovedAndAckedOrMovedAndAcked(_segment) &&
|
|
1574
|
-
!refTypeIncludesFlag(refType, ReferenceType.SlideOnRemove | ReferenceType.Transient) &&
|
|
1521
|
+
!refTypeIncludesFlag(refType, ReferenceType.SlideOnRemove | ReferenceType.Transient | ReferenceType.StayOnRemove) &&
|
|
1575
1522
|
_segment.endpointType === undefined) {
|
|
1576
1523
|
throw new UsageError("Can only create SlideOnRemove or Transient local reference position on a removed or obliterated segment");
|
|
1577
1524
|
}
|
|
@@ -1644,7 +1591,6 @@ export class MergeTree {
|
|
|
1644
1591
|
}
|
|
1645
1592
|
}
|
|
1646
1593
|
for (let i = 0; i < newOrder.length; i++) {
|
|
1647
|
-
// TODO Non null asserting, why is this not null?
|
|
1648
1594
|
const seg = newOrder[i];
|
|
1649
1595
|
const { parent, index, ordinal } = currentOrder[i];
|
|
1650
1596
|
parent?.assignChild(seg, index, false);
|
|
@@ -1731,7 +1677,6 @@ export class MergeTree {
|
|
|
1731
1677
|
const rightmostTiles = createMap();
|
|
1732
1678
|
const leftmostTiles = createMap();
|
|
1733
1679
|
for (let i = 0; i < block.childCount; i++) {
|
|
1734
|
-
// TODO Non null asserting, why is this not null?
|
|
1735
1680
|
const node = block.children[i];
|
|
1736
1681
|
const nodeLength = nodeTotalLength(this, node);
|
|
1737
1682
|
if (nodeLength !== undefined) {
|
|
@@ -1841,18 +1786,11 @@ export class MergeTree {
|
|
|
1841
1786
|
* ignored for the purposes of tracking when traversal should end.
|
|
1842
1787
|
*/
|
|
1843
1788
|
nodeMap(refSeq, clientId, leaf, accum, post, start = 0, end, localSeq, visibilitySeq = refSeq) {
|
|
1844
|
-
const
|
|
1845
|
-
if (
|
|
1789
|
+
const endPos = end ?? this.nodeLength(this.root, refSeq, clientId, localSeq) ?? 0;
|
|
1790
|
+
if (endPos === start) {
|
|
1846
1791
|
return;
|
|
1847
1792
|
}
|
|
1848
1793
|
let pos = 0;
|
|
1849
|
-
let { startPos, endPos } = endpointPosAndSide(start, end);
|
|
1850
|
-
startPos = startPos === "start" || startPos === undefined ? 0 : startPos;
|
|
1851
|
-
endPos =
|
|
1852
|
-
endPos === "end" || endPos === undefined
|
|
1853
|
-
? this.root.mergeTree?.getLength(refSeq, clientId) ?? 0
|
|
1854
|
-
: endPos;
|
|
1855
|
-
assert(startPos !== "end" && endPos !== "start", 0x9e3 /* start cannot be 'end' and end cannot be 'start' */);
|
|
1856
1794
|
depthFirstNodeWalk(this.root, this.root.children[0], (node) => {
|
|
1857
1795
|
if (endPos <= pos) {
|
|
1858
1796
|
return NodeAction.Exit;
|
|
@@ -1869,19 +1807,19 @@ export class MergeTree {
|
|
|
1869
1807
|
}
|
|
1870
1808
|
const nextPos = pos + lenAtRefSeq;
|
|
1871
1809
|
// start is beyond the current node, so we can skip it
|
|
1872
|
-
if (
|
|
1810
|
+
if (start >= nextPos) {
|
|
1873
1811
|
pos = nextPos;
|
|
1874
1812
|
return NodeAction.Skip;
|
|
1875
1813
|
}
|
|
1876
1814
|
if (node.isLeaf()) {
|
|
1877
|
-
if (leaf(node, pos, refSeq, clientId,
|
|
1815
|
+
if (leaf(node, pos, refSeq, clientId, start - pos, endPos - pos, accum) === false) {
|
|
1878
1816
|
return NodeAction.Exit;
|
|
1879
1817
|
}
|
|
1880
1818
|
pos = nextPos;
|
|
1881
1819
|
}
|
|
1882
1820
|
}, undefined, post === undefined
|
|
1883
1821
|
? undefined
|
|
1884
|
-
: (block) => post(block, pos, refSeq, clientId,
|
|
1822
|
+
: (block) => post(block, pos, refSeq, clientId, start - pos, endPos - pos, accum));
|
|
1885
1823
|
}
|
|
1886
1824
|
}
|
|
1887
1825
|
MergeTree.options = {
|