@fluidframework/merge-tree 1.2.6 → 2.0.0-dev.1.3.0.96595
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/.mocharc.js +12 -0
- package/README.md +2 -2
- package/dist/MergeTreeTextHelper.d.ts +23 -0
- package/dist/MergeTreeTextHelper.d.ts.map +1 -0
- package/dist/MergeTreeTextHelper.js +133 -0
- package/dist/MergeTreeTextHelper.js.map +1 -0
- package/dist/base.d.ts +2 -26
- package/dist/base.d.ts.map +1 -1
- package/dist/base.js.map +1 -1
- package/dist/client.d.ts +27 -16
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +81 -101
- package/dist/client.js.map +1 -1
- package/dist/collections/heap.d.ts +28 -0
- package/dist/collections/heap.d.ts.map +1 -0
- package/dist/collections/heap.js +65 -0
- package/dist/collections/heap.js.map +1 -0
- package/dist/collections/index.d.ts +11 -0
- package/dist/collections/index.d.ts.map +1 -0
- package/dist/collections/index.js +23 -0
- package/dist/collections/index.js.map +1 -0
- package/dist/collections/intervalTree.d.ts +60 -0
- package/dist/collections/intervalTree.d.ts.map +1 -0
- package/dist/collections/intervalTree.js +99 -0
- package/dist/collections/intervalTree.js.map +1 -0
- package/dist/collections/list.d.ts +39 -0
- package/dist/collections/list.d.ts.map +1 -0
- package/dist/collections/list.js +155 -0
- package/dist/collections/list.js.map +1 -0
- package/dist/collections/rbTree.d.ts +154 -0
- package/dist/collections/rbTree.d.ts.map +1 -0
- package/dist/{collections.js → collections/rbTree.js} +15 -478
- package/dist/collections/rbTree.js.map +1 -0
- package/dist/collections/stack.d.ts +16 -0
- package/dist/collections/stack.d.ts.map +1 -0
- package/dist/collections/stack.js +30 -0
- package/dist/collections/stack.js.map +1 -0
- package/dist/collections/tst.d.ts +55 -0
- package/dist/collections/tst.d.ts.map +1 -0
- package/dist/collections/tst.js +171 -0
- package/dist/collections/tst.js.map +1 -0
- package/dist/index.d.ts +3 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -2
- package/dist/index.js.map +1 -1
- package/dist/localReference.d.ts +48 -99
- package/dist/localReference.d.ts.map +1 -1
- package/dist/localReference.js +132 -169
- package/dist/localReference.js.map +1 -1
- package/dist/mergeTree.d.ts +71 -302
- package/dist/mergeTree.d.ts.map +1 -1
- package/dist/mergeTree.js +395 -642
- package/dist/mergeTree.js.map +1 -1
- package/dist/mergeTreeDeltaCallback.d.ts +1 -1
- package/dist/mergeTreeDeltaCallback.d.ts.map +1 -1
- package/dist/mergeTreeDeltaCallback.js.map +1 -1
- package/dist/mergeTreeNodes.d.ts +344 -0
- package/dist/mergeTreeNodes.d.ts.map +1 -0
- package/dist/mergeTreeNodes.js +383 -0
- package/dist/mergeTreeNodes.js.map +1 -0
- package/dist/mergeTreeTracking.d.ts +1 -1
- package/dist/mergeTreeTracking.d.ts.map +1 -1
- package/dist/mergeTreeTracking.js.map +1 -1
- package/dist/opBuilder.d.ts +1 -1
- package/dist/opBuilder.d.ts.map +1 -1
- package/dist/opBuilder.js.map +1 -1
- package/dist/partialLengths.d.ts +188 -18
- package/dist/partialLengths.d.ts.map +1 -1
- package/dist/partialLengths.js +495 -253
- package/dist/partialLengths.js.map +1 -1
- package/dist/properties.d.ts.map +1 -1
- package/dist/properties.js.map +1 -1
- package/dist/referencePositions.d.ts +6 -26
- package/dist/referencePositions.d.ts.map +1 -1
- package/dist/referencePositions.js +3 -20
- package/dist/referencePositions.js.map +1 -1
- package/dist/segmentGroupCollection.d.ts +3 -1
- package/dist/segmentGroupCollection.d.ts.map +1 -1
- package/dist/segmentGroupCollection.js +14 -1
- package/dist/segmentGroupCollection.js.map +1 -1
- package/dist/segmentPropertiesManager.d.ts +10 -1
- package/dist/segmentPropertiesManager.d.ts.map +1 -1
- package/dist/segmentPropertiesManager.js +42 -13
- package/dist/segmentPropertiesManager.js.map +1 -1
- package/dist/snapshotChunks.d.ts +2 -1
- package/dist/snapshotChunks.d.ts.map +1 -1
- package/dist/snapshotChunks.js.map +1 -1
- package/dist/snapshotLoader.d.ts.map +1 -1
- package/dist/snapshotLoader.js.map +1 -1
- package/dist/snapshotV1.d.ts +1 -1
- package/dist/snapshotV1.d.ts.map +1 -1
- package/dist/snapshotV1.js +1 -1
- package/dist/snapshotV1.js.map +1 -1
- package/dist/snapshotlegacy.d.ts +5 -1
- package/dist/snapshotlegacy.d.ts.map +1 -1
- package/dist/snapshotlegacy.js +4 -0
- package/dist/snapshotlegacy.js.map +1 -1
- package/dist/sortedSegmentSet.d.ts +1 -1
- package/dist/sortedSegmentSet.d.ts.map +1 -1
- package/dist/sortedSegmentSet.js.map +1 -1
- package/dist/textSegment.d.ts +7 -7
- package/dist/textSegment.d.ts.map +1 -1
- package/dist/textSegment.js +3 -125
- package/dist/textSegment.js.map +1 -1
- package/{DEV.md → docs/DEV.md} +2 -2
- package/docs/Obliterate.md +639 -0
- package/{REFERENCEPOSITIONS.md → docs/REFERENCEPOSITIONS.md} +2 -2
- package/lib/MergeTreeTextHelper.d.ts +23 -0
- package/lib/MergeTreeTextHelper.d.ts.map +1 -0
- package/lib/MergeTreeTextHelper.js +129 -0
- package/lib/MergeTreeTextHelper.js.map +1 -0
- package/lib/base.d.ts +2 -26
- package/lib/base.d.ts.map +1 -1
- package/lib/base.js.map +1 -1
- package/lib/client.d.ts +27 -16
- package/lib/client.d.ts.map +1 -1
- package/lib/client.js +79 -99
- package/lib/client.js.map +1 -1
- package/lib/collections/heap.d.ts +28 -0
- package/lib/collections/heap.d.ts.map +1 -0
- package/lib/collections/heap.js +61 -0
- package/lib/collections/heap.js.map +1 -0
- package/lib/collections/index.d.ts +11 -0
- package/lib/collections/index.d.ts.map +1 -0
- package/lib/collections/index.js +11 -0
- package/lib/collections/index.js.map +1 -0
- package/lib/collections/intervalTree.d.ts +60 -0
- package/lib/collections/intervalTree.d.ts.map +1 -0
- package/lib/collections/intervalTree.js +94 -0
- package/lib/collections/intervalTree.js.map +1 -0
- package/lib/collections/list.d.ts +39 -0
- package/lib/collections/list.d.ts.map +1 -0
- package/lib/collections/list.js +149 -0
- package/lib/collections/list.js.map +1 -0
- package/lib/collections/rbTree.d.ts +154 -0
- package/lib/collections/rbTree.d.ts.map +1 -0
- package/lib/{collections.js → collections/rbTree.js} +14 -469
- package/lib/collections/rbTree.js.map +1 -0
- package/lib/collections/stack.d.ts +16 -0
- package/lib/collections/stack.d.ts.map +1 -0
- package/lib/collections/stack.js +26 -0
- package/lib/collections/stack.js.map +1 -0
- package/lib/collections/tst.d.ts +55 -0
- package/lib/collections/tst.d.ts.map +1 -0
- package/lib/collections/tst.js +167 -0
- package/lib/collections/tst.js.map +1 -0
- package/lib/index.d.ts +3 -1
- package/lib/index.d.ts.map +1 -1
- package/lib/index.js +3 -1
- package/lib/index.js.map +1 -1
- package/lib/localReference.d.ts +48 -99
- package/lib/localReference.d.ts.map +1 -1
- package/lib/localReference.js +132 -170
- package/lib/localReference.js.map +1 -1
- package/lib/mergeTree.d.ts +71 -302
- package/lib/mergeTree.d.ts.map +1 -1
- package/lib/mergeTree.js +371 -607
- package/lib/mergeTree.js.map +1 -1
- package/lib/mergeTreeDeltaCallback.d.ts +1 -1
- package/lib/mergeTreeDeltaCallback.d.ts.map +1 -1
- package/lib/mergeTreeDeltaCallback.js.map +1 -1
- package/lib/mergeTreeNodes.d.ts +344 -0
- package/lib/mergeTreeNodes.d.ts.map +1 -0
- package/lib/mergeTreeNodes.js +369 -0
- package/lib/mergeTreeNodes.js.map +1 -0
- package/lib/mergeTreeTracking.d.ts +1 -1
- package/lib/mergeTreeTracking.d.ts.map +1 -1
- package/lib/mergeTreeTracking.js.map +1 -1
- package/lib/opBuilder.d.ts +1 -1
- package/lib/opBuilder.d.ts.map +1 -1
- package/lib/opBuilder.js.map +1 -1
- package/lib/partialLengths.d.ts +188 -18
- package/lib/partialLengths.d.ts.map +1 -1
- package/lib/partialLengths.js +491 -249
- package/lib/partialLengths.js.map +1 -1
- package/lib/properties.d.ts.map +1 -1
- package/lib/properties.js.map +1 -1
- package/lib/referencePositions.d.ts +6 -26
- package/lib/referencePositions.d.ts.map +1 -1
- package/lib/referencePositions.js +3 -20
- package/lib/referencePositions.js.map +1 -1
- package/lib/segmentGroupCollection.d.ts +3 -1
- package/lib/segmentGroupCollection.d.ts.map +1 -1
- package/lib/segmentGroupCollection.js +14 -1
- package/lib/segmentGroupCollection.js.map +1 -1
- package/lib/segmentPropertiesManager.d.ts +10 -1
- package/lib/segmentPropertiesManager.d.ts.map +1 -1
- package/lib/segmentPropertiesManager.js +42 -13
- package/lib/segmentPropertiesManager.js.map +1 -1
- package/lib/snapshotChunks.d.ts +2 -1
- package/lib/snapshotChunks.d.ts.map +1 -1
- package/lib/snapshotChunks.js.map +1 -1
- package/lib/snapshotLoader.d.ts.map +1 -1
- package/lib/snapshotLoader.js.map +1 -1
- package/lib/snapshotV1.d.ts +1 -1
- package/lib/snapshotV1.d.ts.map +1 -1
- package/lib/snapshotV1.js +1 -1
- package/lib/snapshotV1.js.map +1 -1
- package/lib/snapshotlegacy.d.ts +5 -1
- package/lib/snapshotlegacy.d.ts.map +1 -1
- package/lib/snapshotlegacy.js +4 -0
- package/lib/snapshotlegacy.js.map +1 -1
- package/lib/sortedSegmentSet.d.ts +1 -1
- package/lib/sortedSegmentSet.d.ts.map +1 -1
- package/lib/sortedSegmentSet.js.map +1 -1
- package/lib/textSegment.d.ts +7 -7
- package/lib/textSegment.d.ts.map +1 -1
- package/lib/textSegment.js +1 -122
- package/lib/textSegment.js.map +1 -1
- package/package.json +99 -20
- package/src/MergeTreeTextHelper.ts +170 -0
- package/src/base.ts +2 -35
- package/src/client.ts +91 -111
- package/src/collections/heap.ts +75 -0
- package/src/collections/index.ts +11 -0
- package/src/collections/intervalTree.ts +146 -0
- package/src/collections/list.ts +165 -0
- package/src/{collections.ts → collections/rbTree.ts} +84 -563
- package/src/collections/stack.ts +27 -0
- package/src/collections/tst.ts +212 -0
- package/src/index.ts +8 -2
- package/src/localReference.ts +152 -203
- package/src/mergeTree.ts +578 -996
- package/src/mergeTreeDeltaCallback.ts +1 -1
- package/src/mergeTreeNodes.ts +752 -0
- package/src/mergeTreeTracking.ts +1 -1
- package/src/opBuilder.ts +1 -1
- package/src/partialLengths.ts +631 -258
- package/src/properties.ts +1 -0
- package/src/referencePositions.ts +10 -44
- package/src/segmentGroupCollection.ts +17 -2
- package/src/segmentPropertiesManager.ts +46 -12
- package/src/snapshotChunks.ts +2 -1
- package/src/snapshotLoader.ts +2 -1
- package/src/snapshotV1.ts +3 -3
- package/src/snapshotlegacy.ts +6 -2
- package/src/sortedSegmentSet.ts +1 -1
- package/src/textSegment.ts +10 -157
- package/dist/collections.d.ts +0 -197
- package/dist/collections.d.ts.map +0 -1
- package/dist/collections.js.map +0 -1
- package/lib/collections.d.ts +0 -197
- package/lib/collections.d.ts.map +0 -1
- package/lib/collections.js.map +0 -1
package/lib/partialLengths.js
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
import { assert } from "@fluidframework/common-utils";
|
|
6
6
|
import { RedBlackTree } from "./collections";
|
|
7
7
|
import { UnassignedSequenceNumber } from "./constants";
|
|
8
|
-
import { compareNumbers, toRemovalInfo, } from "./
|
|
8
|
+
import { compareNumbers, toRemovalInfo, } from "./mergeTreeNodes";
|
|
9
9
|
/**
|
|
10
10
|
* Returns the partial length whose sequence number is
|
|
11
11
|
* the greatest sequence number within a that is
|
|
@@ -32,141 +32,152 @@ function latestLEQ(a, key) {
|
|
|
32
32
|
return best;
|
|
33
33
|
}
|
|
34
34
|
/**
|
|
35
|
-
*
|
|
36
|
-
*
|
|
37
|
-
*
|
|
35
|
+
* Keeps track of partial sums of segment lengths for all sequence numbers in the current collaboration window.
|
|
36
|
+
* Only used during active collaboration.
|
|
37
|
+
*
|
|
38
|
+
* This class is associated with an internal node (block) of a MergeTree. It efficiently answers queries of the form
|
|
39
|
+
* "What is the length of `block` from the perspective of some particular seq and clientId?".
|
|
40
|
+
*
|
|
41
|
+
* It also supports incremental updating of state for newly-sequenced ops that don't affect the structure of the
|
|
42
|
+
* MergeTree.
|
|
43
|
+
*
|
|
44
|
+
* To answer these queries, it pre-builds several lists which track the length of the block at a per-sequence-number
|
|
45
|
+
* level. These lists are:
|
|
46
|
+
*
|
|
47
|
+
* 1. (`partialLengths`): Stores the total length of the block.
|
|
48
|
+
* 2. (`clientSeqNumbers[clientId]`): Stores only the total lengths of segments submitted by `clientId`. [see footnote]
|
|
49
|
+
*
|
|
50
|
+
* The reason both lists are necessary is that resolving the length of the block from the perspective of
|
|
51
|
+
* (clientId, refSeq) requires including both of the following types of segments:
|
|
52
|
+
* 1. Segments sequenced before `refSeq`
|
|
53
|
+
* 2. Segments submitted by `clientId`
|
|
54
|
+
*
|
|
55
|
+
* This is possible with the above bookkeeping, using:
|
|
56
|
+
*
|
|
57
|
+
* (length of the block at the minimum sequence number)
|
|
58
|
+
* + (partialLengths total length at refSeq)
|
|
59
|
+
* + (clientSeqNumbers total length at most recent op)
|
|
60
|
+
* - (clientSeqNumbers total length at refSeq)
|
|
61
|
+
*
|
|
62
|
+
* where the subtraction avoids double-counting segments submitted by clientId sequenced within the collab window.
|
|
63
|
+
*
|
|
64
|
+
* To enable reconnect, if constructed with `computeLocalPartials === true` it also supports querying for the length of
|
|
65
|
+
* the block from the perspective of the local client at a particular `refSeq` and `localSeq`. This computation is
|
|
66
|
+
* similar to the above:
|
|
67
|
+
*
|
|
68
|
+
* (length of the block at the minimum sequence number)
|
|
69
|
+
* + (partialLengths total length at refSeq)
|
|
70
|
+
* + (unsequenced edits' total length submitted before localSeq)
|
|
71
|
+
* - (overlapping remove of the unsequenced edits' total length at refSeq)
|
|
72
|
+
*
|
|
73
|
+
* This algorithm scales roughly linearly with number of editing clients and the size of the collab window.
|
|
74
|
+
* (certain unlikely sequences of operations may introduce log factors on those variables)
|
|
75
|
+
*
|
|
76
|
+
* Note: there is some slight complication with clientSeqNumbers resulting from the possibility of different clients
|
|
77
|
+
* concurrently removing the same segment. See the field's documentation for more details.
|
|
38
78
|
*/
|
|
39
79
|
export class PartialSequenceLengths {
|
|
40
|
-
constructor(
|
|
80
|
+
constructor(
|
|
81
|
+
/**
|
|
82
|
+
* The minimumSequenceNumber as defined by the collab window used in the last call to `update`,
|
|
83
|
+
* or if no such calls have been made, the one used on construction.
|
|
84
|
+
*/
|
|
85
|
+
minSeq, computeLocalPartials) {
|
|
41
86
|
this.minSeq = minSeq;
|
|
87
|
+
/**
|
|
88
|
+
* Length of the block this PartialSequenceLength corresponds to when viewed at `minSeq`.
|
|
89
|
+
*/
|
|
42
90
|
this.minLength = 0;
|
|
91
|
+
/**
|
|
92
|
+
* Total number of segments in the subtree rooted at the block this PartialSequenceLength corresponds to.
|
|
93
|
+
*/
|
|
43
94
|
this.segmentCount = 0;
|
|
95
|
+
/**
|
|
96
|
+
* List of PartialSequenceLength objects--ordered by increasing seq--giving length information about
|
|
97
|
+
* the block associated with this PartialSequenceLengths object.
|
|
98
|
+
*
|
|
99
|
+
* `partialLengths[i].len` contains the length of this block considering only sequenced segments with
|
|
100
|
+
* `sequenceNumber <= partialLengths[i].seq`.
|
|
101
|
+
*/
|
|
44
102
|
this.partialLengths = [];
|
|
103
|
+
/**
|
|
104
|
+
* clientSeqNumbers[clientId] is a list of partial lengths for sequenced ops which either:
|
|
105
|
+
* - were submitted by `clientId`.
|
|
106
|
+
* - deleted a range containing segments that were concurrently deleted by `clientId`
|
|
107
|
+
*
|
|
108
|
+
* The second case is referred to as the "overlapping delete" case. It is necessary to avoid double-counting
|
|
109
|
+
* the removal of those segments in queries including clientId.
|
|
110
|
+
*/
|
|
45
111
|
this.clientSeqNumbers = [];
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
112
|
+
if (computeLocalPartials) {
|
|
113
|
+
this.unsequencedRecords = {
|
|
114
|
+
partialLengths: [],
|
|
115
|
+
overlappingRemoves: [],
|
|
116
|
+
cachedOverlappingByRefSeq: new Map(),
|
|
117
|
+
};
|
|
118
|
+
}
|
|
49
119
|
}
|
|
50
120
|
/**
|
|
51
121
|
* Combine the partial lengths of block's children
|
|
52
|
-
* @param block - an interior node
|
|
53
|
-
* has its partials up to date
|
|
54
|
-
* @param collabWindow - segment window of the segment tree containing
|
|
122
|
+
* @param block - an interior node. If `recur` is false, it is assumed that each interior node child of this block
|
|
123
|
+
* has its partials up to date.
|
|
124
|
+
* @param collabWindow - segment window of the segment tree containing `block`.
|
|
125
|
+
* @param recur - whether to recursively compute partial lengths for internal children of `block`.
|
|
126
|
+
* This incurs more work, but gives correct bookkeeping in the case that a descendant in the merge tree has been
|
|
127
|
+
* modified without bubbling up the resulting partial length change to this block's partials.
|
|
128
|
+
* @param computeLocalPartials - whether to compute partial length information about local unsequenced ops.
|
|
129
|
+
* This enables querying for the length of the block at a given localSeq, but incurs extra work.
|
|
130
|
+
* Local partial information doesn't support `update`.
|
|
55
131
|
*/
|
|
56
|
-
static
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
let prevPartial;
|
|
60
|
-
function cloneOverlapRemoveClients(oldTree) {
|
|
61
|
-
if (!oldTree) {
|
|
62
|
-
return undefined;
|
|
63
|
-
}
|
|
64
|
-
const newTree = new RedBlackTree(compareNumbers);
|
|
65
|
-
oldTree.map((bProp) => {
|
|
66
|
-
newTree.put(bProp.data.clientId, Object.assign({}, bProp.data));
|
|
67
|
-
return true;
|
|
68
|
-
});
|
|
69
|
-
return newTree;
|
|
70
|
-
}
|
|
71
|
-
function combineOverlapClients(a, b) {
|
|
72
|
-
const overlapRemoveClientsA = a.overlapRemoveClients;
|
|
73
|
-
if (overlapRemoveClientsA) {
|
|
74
|
-
if (b.overlapRemoveClients) {
|
|
75
|
-
b.overlapRemoveClients.map((bProp) => {
|
|
76
|
-
const aProp = overlapRemoveClientsA.get(bProp.key);
|
|
77
|
-
if (aProp) {
|
|
78
|
-
aProp.data.seglen += bProp.data.seglen;
|
|
79
|
-
}
|
|
80
|
-
else {
|
|
81
|
-
overlapRemoveClientsA.put(bProp.data.clientId, Object.assign({}, bProp.data));
|
|
82
|
-
}
|
|
83
|
-
return true;
|
|
84
|
-
});
|
|
85
|
-
}
|
|
86
|
-
}
|
|
87
|
-
else {
|
|
88
|
-
a.overlapRemoveClients = cloneOverlapRemoveClients(b.overlapRemoveClients);
|
|
89
|
-
}
|
|
90
|
-
}
|
|
91
|
-
function addNext(partialLength) {
|
|
92
|
-
const seq = partialLength.seq;
|
|
93
|
-
let pLen = 0;
|
|
94
|
-
if (prevPartial) {
|
|
95
|
-
if (prevPartial.seq === partialLength.seq) {
|
|
96
|
-
prevPartial.seglen += partialLength.seglen;
|
|
97
|
-
prevPartial.len += partialLength.seglen;
|
|
98
|
-
combineOverlapClients(prevPartial, partialLength);
|
|
99
|
-
return;
|
|
100
|
-
}
|
|
101
|
-
else {
|
|
102
|
-
pLen = prevPartial.len;
|
|
103
|
-
// Previous sequence number is finished
|
|
104
|
-
combinedPartialLengths.addClientSeqNumberFromPartial(prevPartial);
|
|
105
|
-
}
|
|
106
|
-
}
|
|
107
|
-
prevPartial = {
|
|
108
|
-
clientId: partialLength.clientId,
|
|
109
|
-
len: pLen + partialLength.seglen,
|
|
110
|
-
overlapRemoveClients: cloneOverlapRemoveClients(partialLength.overlapRemoveClients),
|
|
111
|
-
seglen: partialLength.seglen,
|
|
112
|
-
seq,
|
|
113
|
-
};
|
|
114
|
-
combinedPartialLengths.partialLengths.push(prevPartial);
|
|
115
|
-
}
|
|
132
|
+
static combine(block, collabWindow, recur = false, computeLocalPartials = false) {
|
|
133
|
+
const leafPartialLengths = PartialSequenceLengths.fromLeaves(block, collabWindow, computeLocalPartials);
|
|
134
|
+
let hasInternalChild = false;
|
|
116
135
|
const childPartials = [];
|
|
117
136
|
for (let i = 0; i < block.childCount; i++) {
|
|
118
137
|
const child = block.children[i];
|
|
119
138
|
if (!child.isLeaf()) {
|
|
120
|
-
|
|
139
|
+
hasInternalChild = true;
|
|
121
140
|
if (recur) {
|
|
122
|
-
|
|
123
|
-
PartialSequenceLengths.combine(
|
|
141
|
+
child.partialLengths =
|
|
142
|
+
PartialSequenceLengths.combine(child, collabWindow, true, computeLocalPartials);
|
|
124
143
|
}
|
|
125
144
|
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
126
|
-
childPartials.push(
|
|
145
|
+
childPartials.push(child.partialLengths);
|
|
127
146
|
}
|
|
128
147
|
}
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
148
|
+
// If there are no internal children, the PartialSequenceLengths returns from `fromLeaves` is exactly correct.
|
|
149
|
+
// Otherwise, we must additively combine all of the children partial lengths to get this block's totals.
|
|
150
|
+
const combinedPartialLengths = hasInternalChild ?
|
|
151
|
+
new PartialSequenceLengths(collabWindow.minSeq, computeLocalPartials) : leafPartialLengths;
|
|
152
|
+
if (hasInternalChild) {
|
|
153
|
+
if (leafPartialLengths.partialLengths.length > 0) {
|
|
133
154
|
// Some children were leaves; add combined partials from these segments
|
|
134
|
-
childPartials.push(
|
|
135
|
-
childPartialsLen++;
|
|
136
|
-
combinedPartialLengths = new PartialSequenceLengths(collabWindow.minSeq);
|
|
155
|
+
childPartials.push(leafPartialLengths);
|
|
137
156
|
}
|
|
138
|
-
const
|
|
139
|
-
const
|
|
157
|
+
const childPartialsLen = childPartials.length;
|
|
158
|
+
const childPartialLengths = [];
|
|
159
|
+
const childUnsequencedPartialLengths = [];
|
|
160
|
+
const childOverlapRemoves = [];
|
|
140
161
|
for (let i = 0; i < childPartialsLen; i++) {
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
combinedPartialLengths.minLength +=
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
while (outerIndexOfEarliest >= 0) {
|
|
149
|
-
outerIndexOfEarliest = -1;
|
|
150
|
-
for (let k = 0; k < childPartialsLen; k++) {
|
|
151
|
-
// Find next earliest sequence number
|
|
152
|
-
if (indices[k] < childPartialsCounts[k]) {
|
|
153
|
-
const cpLen = childPartials[k].partialLengths[indices[k]];
|
|
154
|
-
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
155
|
-
if ((outerIndexOfEarliest < 0) || (cpLen.seq < earliestPartialLength.seq)) {
|
|
156
|
-
outerIndexOfEarliest = k;
|
|
157
|
-
earliestPartialLength = cpLen;
|
|
158
|
-
}
|
|
159
|
-
}
|
|
160
|
-
}
|
|
161
|
-
if (outerIndexOfEarliest >= 0) {
|
|
162
|
-
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
163
|
-
addNext(earliestPartialLength);
|
|
164
|
-
indices[outerIndexOfEarliest]++;
|
|
162
|
+
const { segmentCount, minLength, partialLengths, unsequencedRecords } = childPartials[i];
|
|
163
|
+
combinedPartialLengths.segmentCount += segmentCount;
|
|
164
|
+
combinedPartialLengths.minLength += minLength;
|
|
165
|
+
childPartialLengths.push(partialLengths);
|
|
166
|
+
if (unsequencedRecords) {
|
|
167
|
+
childUnsequencedPartialLengths.push(unsequencedRecords.partialLengths);
|
|
168
|
+
childOverlapRemoves.push(unsequencedRecords.overlappingRemoves);
|
|
165
169
|
}
|
|
166
170
|
}
|
|
167
|
-
|
|
168
|
-
if (
|
|
169
|
-
combinedPartialLengths.
|
|
171
|
+
combinedPartialLengths.partialLengths.push(...mergePartialLengths(childPartialLengths));
|
|
172
|
+
if (computeLocalPartials) {
|
|
173
|
+
combinedPartialLengths.unsequencedRecords = {
|
|
174
|
+
partialLengths: mergePartialLengths(childUnsequencedPartialLengths),
|
|
175
|
+
overlappingRemoves: Array.from(mergeSortedListsBySeq(childOverlapRemoves)),
|
|
176
|
+
cachedOverlappingByRefSeq: new Map(),
|
|
177
|
+
};
|
|
178
|
+
}
|
|
179
|
+
for (const partial of combinedPartialLengths.partialLengths) {
|
|
180
|
+
combinedPartialLengths.addClientSeqNumberFromPartial(partial);
|
|
170
181
|
}
|
|
171
182
|
}
|
|
172
183
|
// TODO: incremental zamboni during build
|
|
@@ -174,12 +185,16 @@ export class PartialSequenceLengths {
|
|
|
174
185
|
combinedPartialLengths.zamboni(collabWindow);
|
|
175
186
|
}
|
|
176
187
|
if (PartialSequenceLengths.options.verify) {
|
|
177
|
-
|
|
188
|
+
verify(combinedPartialLengths);
|
|
178
189
|
}
|
|
179
190
|
return combinedPartialLengths;
|
|
180
191
|
}
|
|
181
|
-
|
|
182
|
-
|
|
192
|
+
/**
|
|
193
|
+
* @returns a PartialSequenceLengths structure which tracks only lengths of leaf children of the provided
|
|
194
|
+
* IMergeBlock.
|
|
195
|
+
*/
|
|
196
|
+
static fromLeaves(block, collabWindow, computeLocalPartials) {
|
|
197
|
+
const combinedPartialLengths = new PartialSequenceLengths(collabWindow.minSeq, computeLocalPartials);
|
|
183
198
|
combinedPartialLengths.segmentCount = block.childCount;
|
|
184
199
|
function seqLTE(seq, minSeq) {
|
|
185
200
|
return seq !== undefined && seq !== UnassignedSequenceNumber && seq <= minSeq;
|
|
@@ -193,19 +208,14 @@ export class PartialSequenceLengths {
|
|
|
193
208
|
combinedPartialLengths.minLength += segment.cachedLength;
|
|
194
209
|
}
|
|
195
210
|
else {
|
|
196
|
-
|
|
197
|
-
PartialSequenceLengths.insertSegment(combinedPartialLengths, segment);
|
|
198
|
-
}
|
|
211
|
+
PartialSequenceLengths.insertSegment(combinedPartialLengths, segment);
|
|
199
212
|
}
|
|
200
213
|
const removalInfo = toRemovalInfo(segment);
|
|
201
214
|
if (seqLTE(removalInfo === null || removalInfo === void 0 ? void 0 : removalInfo.removedSeq, collabWindow.minSeq)) {
|
|
202
215
|
combinedPartialLengths.minLength -= segment.cachedLength;
|
|
203
216
|
}
|
|
204
|
-
else {
|
|
205
|
-
|
|
206
|
-
&& removalInfo.removedSeq !== UnassignedSequenceNumber) {
|
|
207
|
-
PartialSequenceLengths.insertSegment(combinedPartialLengths, segment, removalInfo);
|
|
208
|
-
}
|
|
217
|
+
else if (removalInfo !== undefined) {
|
|
218
|
+
PartialSequenceLengths.insertSegment(combinedPartialLengths, segment, removalInfo);
|
|
209
219
|
}
|
|
210
220
|
}
|
|
211
221
|
}
|
|
@@ -219,9 +229,18 @@ export class PartialSequenceLengths {
|
|
|
219
229
|
prevLen = seqPartials[i].len;
|
|
220
230
|
combinedPartialLengths.addClientSeqNumberFromPartial(seqPartials[i]);
|
|
221
231
|
}
|
|
232
|
+
prevLen = 0;
|
|
233
|
+
if (combinedPartialLengths.unsequencedRecords !== undefined) {
|
|
234
|
+
const localPartials = combinedPartialLengths.unsequencedRecords.partialLengths;
|
|
235
|
+
for (const partial of localPartials) {
|
|
236
|
+
partial.len = prevLen + partial.seglen;
|
|
237
|
+
prevLen = partial.len;
|
|
238
|
+
}
|
|
239
|
+
}
|
|
222
240
|
if (PartialSequenceLengths.options.verify) {
|
|
223
|
-
|
|
241
|
+
verify(combinedPartialLengths);
|
|
224
242
|
}
|
|
243
|
+
return combinedPartialLengths;
|
|
225
244
|
}
|
|
226
245
|
static getOverlapClients(overlapClientIds, seglen) {
|
|
227
246
|
const bst = new RedBlackTree(compareNumbers);
|
|
@@ -247,57 +266,99 @@ export class PartialSequenceLengths {
|
|
|
247
266
|
PartialSequenceLengths.getOverlapClients(overlapRemoveClientIds, seglen);
|
|
248
267
|
}
|
|
249
268
|
}
|
|
269
|
+
/**
|
|
270
|
+
* Inserts length information about the insertion of `segment` into `combinedPartialLengths.partialLengths`.
|
|
271
|
+
* Does not update the clientSeqNumbers field to account for this segment.
|
|
272
|
+
* If `removalInfo` is defined, this operation updates the bookkeeping to account for the removal of this
|
|
273
|
+
* segment at the removedSeq instead.
|
|
274
|
+
* When the insertion or removal of the segment is un-acked and `combinedPartialLengths` is meant to compute
|
|
275
|
+
* such records, this does the analogous addition to the bookkeeping for the local segment in
|
|
276
|
+
* `combinedPartialLengths.unsequencedRecords`.
|
|
277
|
+
*/
|
|
250
278
|
static insertSegment(combinedPartialLengths, segment, removalInfo) {
|
|
279
|
+
var _a, _b, _c;
|
|
280
|
+
const isLocal = (removalInfo === undefined && segment.seq === UnassignedSequenceNumber)
|
|
281
|
+
|| (removalInfo !== undefined && segment.removedSeq === UnassignedSequenceNumber);
|
|
251
282
|
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
252
|
-
let
|
|
283
|
+
let seqOrLocalSeq = isLocal ? segment.localSeq : segment.seq;
|
|
253
284
|
let segmentLen = segment.cachedLength;
|
|
254
285
|
let clientId = segment.clientId;
|
|
255
286
|
let removeClientOverlap;
|
|
256
287
|
if (removalInfo) {
|
|
257
|
-
|
|
288
|
+
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
289
|
+
seqOrLocalSeq = isLocal ? removalInfo.localRemovedSeq : removalInfo.removedSeq;
|
|
258
290
|
segmentLen = -segmentLen;
|
|
259
|
-
//
|
|
260
|
-
//
|
|
261
|
-
// then apart first.
|
|
291
|
+
// The client who performed the remove is always stored
|
|
292
|
+
// in the first position of removalInfo.
|
|
262
293
|
clientId = removalInfo.removedClientIds[0];
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
: undefined;
|
|
294
|
+
const hasOverlap = removalInfo.removedClientIds.length > 1;
|
|
295
|
+
removeClientOverlap = hasOverlap ? removalInfo.removedClientIds : undefined;
|
|
266
296
|
}
|
|
267
|
-
const
|
|
268
|
-
|
|
297
|
+
const partials = isLocal ?
|
|
298
|
+
(_a = combinedPartialLengths.unsequencedRecords) === null || _a === void 0 ? void 0 : _a.partialLengths : combinedPartialLengths.partialLengths;
|
|
299
|
+
if (partials === undefined) {
|
|
300
|
+
// Local partial but its computation isn't required
|
|
301
|
+
return;
|
|
302
|
+
}
|
|
303
|
+
const partialsLen = partials.length;
|
|
269
304
|
// Find the first entry with sequence number greater or equal to seq
|
|
270
305
|
let indexFirstGTE = 0;
|
|
271
|
-
for (; indexFirstGTE <
|
|
272
|
-
if (
|
|
306
|
+
for (; indexFirstGTE < partialsLen; indexFirstGTE++) {
|
|
307
|
+
if (partials[indexFirstGTE].seq >= seqOrLocalSeq) {
|
|
273
308
|
break;
|
|
274
309
|
}
|
|
275
310
|
}
|
|
276
|
-
|
|
277
|
-
|
|
311
|
+
let partialLengthEntry;
|
|
312
|
+
if (((_b = partials[indexFirstGTE]) === null || _b === void 0 ? void 0 : _b.seq) === seqOrLocalSeq) {
|
|
313
|
+
partialLengthEntry = partials[indexFirstGTE];
|
|
314
|
+
// Existing entry at this seq--this occurs for ops that insert/delete more than one segment.
|
|
315
|
+
partialLengthEntry.seglen += segmentLen;
|
|
278
316
|
if (removeClientOverlap) {
|
|
279
|
-
PartialSequenceLengths.accumulateRemoveClientOverlap(
|
|
317
|
+
PartialSequenceLengths.accumulateRemoveClientOverlap(partials[indexFirstGTE], removeClientOverlap, segmentLen);
|
|
280
318
|
}
|
|
281
319
|
}
|
|
282
320
|
else {
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
321
|
+
partialLengthEntry = {
|
|
322
|
+
seq: seqOrLocalSeq,
|
|
323
|
+
clientId,
|
|
324
|
+
len: 0,
|
|
325
|
+
seglen: segmentLen,
|
|
326
|
+
overlapRemoveClients: removeClientOverlap
|
|
327
|
+
? PartialSequenceLengths.getOverlapClients(removeClientOverlap, segmentLen)
|
|
328
|
+
: undefined,
|
|
329
|
+
};
|
|
330
|
+
// TODO: investigate performance improvement using BST
|
|
331
|
+
insertIntoList(partials, indexFirstGTE, partialLengthEntry);
|
|
332
|
+
}
|
|
333
|
+
const { unsequencedRecords } = combinedPartialLengths;
|
|
334
|
+
if (unsequencedRecords && removeClientOverlap && segment.localRemovedSeq !== undefined) {
|
|
335
|
+
const localSeq = segment.localRemovedSeq;
|
|
336
|
+
const localPartialLengthEntry = {
|
|
337
|
+
seq: seqOrLocalSeq,
|
|
338
|
+
localSeq,
|
|
339
|
+
clientId,
|
|
340
|
+
len: 0,
|
|
341
|
+
seglen: segmentLen,
|
|
342
|
+
};
|
|
343
|
+
let localIndexFirstGTE = 0;
|
|
344
|
+
for (; localIndexFirstGTE < unsequencedRecords.overlappingRemoves.length; localIndexFirstGTE++) {
|
|
345
|
+
if (unsequencedRecords.overlappingRemoves[localIndexFirstGTE].seq >= seqOrLocalSeq) {
|
|
346
|
+
break;
|
|
347
|
+
}
|
|
290
348
|
}
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
349
|
+
insertIntoList(unsequencedRecords.overlappingRemoves, localIndexFirstGTE, localPartialLengthEntry);
|
|
350
|
+
localIndexFirstGTE = 0;
|
|
351
|
+
for (; localIndexFirstGTE < unsequencedRecords.partialLengths.length; localIndexFirstGTE++) {
|
|
352
|
+
if (unsequencedRecords.partialLengths[localIndexFirstGTE].seq >= localSeq) {
|
|
353
|
+
break;
|
|
296
354
|
}
|
|
297
|
-
|
|
355
|
+
}
|
|
356
|
+
const tweakedLocalPartialEntry = Object.assign(Object.assign({}, localPartialLengthEntry), { seq: localSeq });
|
|
357
|
+
if (((_c = unsequencedRecords.partialLengths[localIndexFirstGTE]) === null || _c === void 0 ? void 0 : _c.seq) === localSeq) {
|
|
358
|
+
unsequencedRecords.partialLengths[localIndexFirstGTE].seglen += localPartialLengthEntry.seglen;
|
|
298
359
|
}
|
|
299
360
|
else {
|
|
300
|
-
|
|
361
|
+
insertIntoList(unsequencedRecords.partialLengths, localIndexFirstGTE, tweakedLocalPartialEntry);
|
|
301
362
|
}
|
|
302
363
|
}
|
|
303
364
|
}
|
|
@@ -318,11 +379,11 @@ export class PartialSequenceLengths {
|
|
|
318
379
|
penultPartialLen = pLen;
|
|
319
380
|
}
|
|
320
381
|
}
|
|
382
|
+
const len = penultPartialLen !== undefined ? penultPartialLen.len + seqSeglen : seqSeglen;
|
|
321
383
|
if (seqPartialLen === undefined) {
|
|
322
|
-
// len will be assigned below, making this assertion true.
|
|
323
|
-
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
324
384
|
seqPartialLen = {
|
|
325
385
|
clientId,
|
|
386
|
+
len,
|
|
326
387
|
seglen: seqSeglen,
|
|
327
388
|
seq,
|
|
328
389
|
};
|
|
@@ -330,19 +391,15 @@ export class PartialSequenceLengths {
|
|
|
330
391
|
}
|
|
331
392
|
else {
|
|
332
393
|
seqPartialLen.seglen = seqSeglen;
|
|
394
|
+
seqPartialLen.len = len;
|
|
333
395
|
// Assert client id matches
|
|
334
396
|
}
|
|
335
|
-
if (penultPartialLen !== undefined) {
|
|
336
|
-
seqPartialLen.len = seqPartialLen.seglen + penultPartialLen.len;
|
|
337
|
-
}
|
|
338
|
-
else {
|
|
339
|
-
seqPartialLen.len = seqPartialLen.seglen;
|
|
340
|
-
}
|
|
341
397
|
}
|
|
342
398
|
// Assume: seq is latest sequence number; no structural change to sub-tree, but a segment
|
|
343
|
-
// with sequence number seq has been added within the sub-tree
|
|
399
|
+
// with sequence number seq has been added within the sub-tree (and `update` has been called
|
|
400
|
+
// on all descendant PartialSequenceLengths)
|
|
344
401
|
// TODO: assert client id matches
|
|
345
|
-
update(
|
|
402
|
+
update(node, seq, clientId, collabWindow) {
|
|
346
403
|
let seqSeglen = 0;
|
|
347
404
|
let segCount = 0;
|
|
348
405
|
// Compute length for seq across children
|
|
@@ -379,6 +436,7 @@ export class PartialSequenceLengths {
|
|
|
379
436
|
}
|
|
380
437
|
}
|
|
381
438
|
this.segmentCount = segCount;
|
|
439
|
+
this.unsequencedRecords = undefined;
|
|
382
440
|
PartialSequenceLengths.addSeq(this.partialLengths, seq, seqSeglen, clientId);
|
|
383
441
|
if (this.clientSeqNumbers[clientId] === undefined) {
|
|
384
442
|
this.clientSeqNumbers[clientId] = [];
|
|
@@ -388,17 +446,28 @@ export class PartialSequenceLengths {
|
|
|
388
446
|
this.zamboni(collabWindow);
|
|
389
447
|
}
|
|
390
448
|
if (PartialSequenceLengths.options.verify) {
|
|
391
|
-
|
|
449
|
+
verify(this);
|
|
392
450
|
}
|
|
393
451
|
}
|
|
394
|
-
|
|
452
|
+
/**
|
|
453
|
+
* Returns the length of this block as viewed from the perspective of `clientId` at `refSeq`.
|
|
454
|
+
* This is the total length of all segments sequenced at or before refSeq OR submitted by `clientId`.
|
|
455
|
+
* If `clientId` is the local client, `localSeq` can also be provided. In that case, it is the total
|
|
456
|
+
* length of all segments submitted at or before `refSeq` in addition to any local, unacked segments
|
|
457
|
+
* with `segment.localSeq <= localSeq`.
|
|
458
|
+
*
|
|
459
|
+
* Note: the local case (where `localSeq !== undefined`) is only supported on a PartialSequenceLength object
|
|
460
|
+
* constructed with `computeLocalPartials` set to true and not subsequently updated with `update`.
|
|
461
|
+
*/
|
|
462
|
+
getPartialLength(refSeq, clientId, localSeq) {
|
|
395
463
|
let pLen = this.minLength;
|
|
396
464
|
const seqIndex = latestLEQ(this.partialLengths, refSeq);
|
|
397
465
|
const cliLatestIndex = this.cliLatest(clientId);
|
|
398
466
|
const cliSeq = this.clientSeqNumbers[clientId];
|
|
399
467
|
if (seqIndex >= 0) {
|
|
400
|
-
// Add the partial length up to refSeq
|
|
401
468
|
pLen += this.partialLengths[seqIndex].len;
|
|
469
|
+
}
|
|
470
|
+
if (localSeq === undefined) {
|
|
402
471
|
if (cliLatestIndex >= 0) {
|
|
403
472
|
const cliLatest = cliSeq[cliLatestIndex];
|
|
404
473
|
if (cliLatest.seq > refSeq) {
|
|
@@ -406,21 +475,73 @@ export class PartialSequenceLengths {
|
|
|
406
475
|
pLen += cliLatest.len;
|
|
407
476
|
const precedingCliIndex = this.cliLatestLEQ(clientId, refSeq);
|
|
408
477
|
if (precedingCliIndex >= 0) {
|
|
478
|
+
// Subtract out double-counted lengths: segments still in the collab window but before
|
|
479
|
+
// the refSeq submitted by the client we're querying for were counted in each addition above.
|
|
409
480
|
pLen -= cliSeq[precedingCliIndex].len;
|
|
410
481
|
}
|
|
411
482
|
}
|
|
412
483
|
}
|
|
413
484
|
}
|
|
414
485
|
else {
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
486
|
+
assert(this.unsequencedRecords !== undefined, 0x39f /* Local getPartialLength invoked without computing local partials. */);
|
|
487
|
+
const unsequencedPartialLengths = this.unsequencedRecords.partialLengths;
|
|
488
|
+
// Local segments at or before localSeq should also be included
|
|
489
|
+
const localIndex = latestLEQ(unsequencedPartialLengths, localSeq);
|
|
490
|
+
if (localIndex >= 0) {
|
|
491
|
+
pLen += unsequencedPartialLengths[localIndex].len;
|
|
492
|
+
// Lastly, we must subtract out any double-counted removes, which occur if a currently un-acked local
|
|
493
|
+
// remove overlaps with a remote client's remove that occurred at sequence number <=refSeq.
|
|
494
|
+
pLen -= this.computeOverlappingLocalRemoves(refSeq, localSeq);
|
|
420
495
|
}
|
|
421
496
|
}
|
|
422
497
|
return pLen;
|
|
423
498
|
}
|
|
499
|
+
/**
|
|
500
|
+
* Computes the seglen for the double-counted removed overlap at (refSeq, localSeq). This logic is equivalent
|
|
501
|
+
* to the following:
|
|
502
|
+
*
|
|
503
|
+
* ```typescript
|
|
504
|
+
* let total = 0;
|
|
505
|
+
* for (const partialLength of this.unsequencedRecords!.overlappingRemoves) {
|
|
506
|
+
* if (partialLength.seq > refSeq) {
|
|
507
|
+
* break;
|
|
508
|
+
* }
|
|
509
|
+
*
|
|
510
|
+
* if (partialLength.localSeq <= localSeq) {
|
|
511
|
+
* total += partialLength.seglen;
|
|
512
|
+
* }
|
|
513
|
+
* }
|
|
514
|
+
*
|
|
515
|
+
* return total;
|
|
516
|
+
* ```
|
|
517
|
+
*
|
|
518
|
+
* Reconnect happens to only need to compute these lengths for two refSeq values: before and
|
|
519
|
+
* after the rebase. Since these lists potentially scale with O(collab window * number of local edits)
|
|
520
|
+
* and potentially need to be queried for each local op that gets rebased,
|
|
521
|
+
* we cache the results for a given refSeq in `this.unsequencedRecords.cachedOverlappingByRefSeq` so
|
|
522
|
+
* that they can be binary-searched the same way the usual partialLengths lists are.
|
|
523
|
+
*/
|
|
524
|
+
computeOverlappingLocalRemoves(refSeq, localSeq) {
|
|
525
|
+
if (this.unsequencedRecords === undefined) {
|
|
526
|
+
return 0;
|
|
527
|
+
}
|
|
528
|
+
let cachedOverlapPartials = this.unsequencedRecords.cachedOverlappingByRefSeq.get(refSeq);
|
|
529
|
+
if (!cachedOverlapPartials) {
|
|
530
|
+
const partials = [];
|
|
531
|
+
for (const partial of this.unsequencedRecords.overlappingRemoves) {
|
|
532
|
+
if (partial.seq > refSeq) {
|
|
533
|
+
break;
|
|
534
|
+
}
|
|
535
|
+
partials.push(Object.assign(Object.assign({}, partial), { seq: partial.localSeq, len: 0 }));
|
|
536
|
+
}
|
|
537
|
+
partials.sort((a, b) => a.seq - b.seq);
|
|
538
|
+
// This coalesces entries with the same localSeq as well as computes overall lengths.
|
|
539
|
+
cachedOverlapPartials = mergePartialLengths([partials]);
|
|
540
|
+
this.unsequencedRecords.cachedOverlappingByRefSeq.set(refSeq, cachedOverlapPartials);
|
|
541
|
+
}
|
|
542
|
+
const overlapIndex = latestLEQ(cachedOverlapPartials, localSeq);
|
|
543
|
+
return overlapIndex >= 0 ? cachedOverlapPartials[overlapIndex].len : 0;
|
|
544
|
+
}
|
|
424
545
|
toString(glc, indentCount = 0) {
|
|
425
546
|
let buf = "";
|
|
426
547
|
for (const partial of this.partialLengths) {
|
|
@@ -430,12 +551,7 @@ export class PartialSequenceLengths {
|
|
|
430
551
|
for (const clientId in this.clientSeqNumbers) {
|
|
431
552
|
if (this.clientSeqNumbers[clientId].length > 0) {
|
|
432
553
|
buf += `Client `;
|
|
433
|
-
|
|
434
|
-
buf += `${glc(+clientId)}`;
|
|
435
|
-
}
|
|
436
|
-
else {
|
|
437
|
-
buf += `${clientId}`;
|
|
438
|
-
}
|
|
554
|
+
buf += glc ? `${glc(+clientId)}` : `${clientId}`;
|
|
439
555
|
buf += "[";
|
|
440
556
|
for (const partial of this.clientSeqNumbers[clientId]) {
|
|
441
557
|
buf += `(${partial.seq},${partial.len})`;
|
|
@@ -468,6 +584,7 @@ export class PartialSequenceLengths {
|
|
|
468
584
|
return minLength;
|
|
469
585
|
}
|
|
470
586
|
this.minLength += copyDown(this.partialLengths);
|
|
587
|
+
this.minSeq = segmentWindow.minSeq;
|
|
471
588
|
// eslint-disable-next-line @typescript-eslint/no-for-in-array, guard-for-in, no-restricted-syntax
|
|
472
589
|
for (const clientId in this.clientSeqNumbers) {
|
|
473
590
|
const cliPartials = this.clientSeqNumbers[clientId];
|
|
@@ -487,105 +604,230 @@ export class PartialSequenceLengths {
|
|
|
487
604
|
}
|
|
488
605
|
cli.push({ seq, len: pLen, seglen });
|
|
489
606
|
}
|
|
490
|
-
// Assumes sequence number already coalesced
|
|
607
|
+
// Assumes sequence number already coalesced and that this is called in increasing `seq` order.
|
|
491
608
|
addClientSeqNumberFromPartial(partialLength) {
|
|
492
609
|
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
493
610
|
this.addClientSeqNumber(partialLength.clientId, partialLength.seq, partialLength.seglen);
|
|
494
611
|
if (partialLength.overlapRemoveClients) {
|
|
495
612
|
partialLength.overlapRemoveClients.map((oc) => {
|
|
496
|
-
|
|
613
|
+
// Original client entry was handled above
|
|
614
|
+
if (partialLength.clientId !== oc.data.clientId) {
|
|
615
|
+
this.addClientSeqNumber(oc.data.clientId, partialLength.seq, oc.data.seglen);
|
|
616
|
+
}
|
|
497
617
|
return true;
|
|
498
618
|
});
|
|
499
619
|
}
|
|
500
620
|
}
|
|
501
621
|
cliLatestLEQ(clientId, refSeq) {
|
|
502
622
|
const cliSeqs = this.clientSeqNumbers[clientId];
|
|
503
|
-
|
|
504
|
-
return latestLEQ(cliSeqs, refSeq);
|
|
505
|
-
}
|
|
506
|
-
else {
|
|
507
|
-
return -1;
|
|
508
|
-
}
|
|
623
|
+
return cliSeqs ? latestLEQ(cliSeqs, refSeq) : -1;
|
|
509
624
|
}
|
|
510
625
|
cliLatest(clientId) {
|
|
511
626
|
const cliSeqs = this.clientSeqNumbers[clientId];
|
|
512
|
-
|
|
513
|
-
|
|
627
|
+
return cliSeqs && (cliSeqs.length > 0) ? cliSeqs.length - 1 : -1;
|
|
628
|
+
}
|
|
629
|
+
}
|
|
630
|
+
PartialSequenceLengths.options = {
|
|
631
|
+
verify: false,
|
|
632
|
+
zamboni: true,
|
|
633
|
+
};
|
|
634
|
+
/* eslint-disable @typescript-eslint/dot-notation */
|
|
635
|
+
function verifyPartialLengths(partialSeqLengths, partialLengths, clientPartials) {
|
|
636
|
+
if (partialLengths.length === 0) {
|
|
637
|
+
return 0;
|
|
638
|
+
}
|
|
639
|
+
let lastSeqNum = 0;
|
|
640
|
+
let accumSegLen = 0;
|
|
641
|
+
let count = 0;
|
|
642
|
+
for (const partialLength of partialLengths) {
|
|
643
|
+
// Count total number of partial length
|
|
644
|
+
count++;
|
|
645
|
+
// Sequence number should be larger or equal to minseq
|
|
646
|
+
assert(partialSeqLengths.minSeq <= partialLength.seq, 0x054 /* "Sequence number less than minSeq!" */);
|
|
647
|
+
// Sequence number should be sorted
|
|
648
|
+
assert(lastSeqNum < partialLength.seq, 0x055 /* "Sequence number is not sorted!" */);
|
|
649
|
+
lastSeqNum = partialLength.seq;
|
|
650
|
+
// Len is a accumulation of all the seglen adjustments
|
|
651
|
+
accumSegLen += partialLength.seglen;
|
|
652
|
+
if (accumSegLen !== partialLength.len) {
|
|
653
|
+
assert(false, 0x056 /* "Unexpected total for accumulation of all seglen adjustments!" */);
|
|
654
|
+
}
|
|
655
|
+
if (clientPartials) {
|
|
656
|
+
// Client partials used to track local edits so we can account for them some refSeq.
|
|
657
|
+
// But the information we keep track of are since minSeq, so we keep track of more history
|
|
658
|
+
// then needed, and some of them doesn't make sense to be used for length calculations
|
|
659
|
+
// e.g. if you have this sequence, where the minSeq is #5 because of other clients
|
|
660
|
+
// seq 10: client 1: insert seg #1
|
|
661
|
+
// seq 11: client 2: delete seg #2 refseq: 10
|
|
662
|
+
// minLength is 0, we would have keep a record of seglen: -1 for clientPartialLengths for client 2
|
|
663
|
+
// So if you ask for partial length for client 2 @ seq 5, we will have return -1.
|
|
664
|
+
// However, that combination is invalid, since we should never see any ops with refseq < 10 for
|
|
665
|
+
// client 2 after seq 11.
|
|
514
666
|
}
|
|
515
667
|
else {
|
|
516
|
-
|
|
668
|
+
// Len adjustment should not make length negative
|
|
669
|
+
if (partialSeqLengths["minLength"] + partialLength.len < 0) {
|
|
670
|
+
assert(false, 0x057 /* "Negative length after length adjustment!" */);
|
|
671
|
+
}
|
|
672
|
+
}
|
|
673
|
+
if (partialLength.overlapRemoveClients) {
|
|
674
|
+
// Only the flat partialLengths can have overlapRemoveClients, the per client view shouldn't
|
|
675
|
+
assert(!clientPartials, 0x058 /* "Both overlapRemoveClients and clientPartials are set!" */);
|
|
676
|
+
// Each overlap client count as one, but the first remove to sequence was already counted.
|
|
677
|
+
// (this aligns with the logic to omit the removing client in `addClientSeqNumberFromPartial`)
|
|
678
|
+
count += partialLength.overlapRemoveClients.size() - 1;
|
|
517
679
|
}
|
|
518
680
|
}
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
681
|
+
return count;
|
|
682
|
+
}
|
|
683
|
+
function verify(partialSeqLengths) {
|
|
684
|
+
if (partialSeqLengths["clientSeqNumbers"]) {
|
|
685
|
+
let cliCount = 0;
|
|
686
|
+
for (const cliSeq of partialSeqLengths["clientSeqNumbers"]) {
|
|
687
|
+
if (cliSeq) {
|
|
688
|
+
cliCount += verifyPartialLengths(partialSeqLengths, cliSeq, true);
|
|
689
|
+
}
|
|
690
|
+
}
|
|
691
|
+
// If we have client view, we should have the flat view
|
|
692
|
+
assert(!!partialSeqLengths["partialLengths"], 0x059 /* "Client view exists but flat view does not!" */);
|
|
693
|
+
const flatCount = verifyPartialLengths(partialSeqLengths, partialSeqLengths["partialLengths"], false);
|
|
694
|
+
// The number of partial lengths on the client view and flat view should be the same
|
|
695
|
+
assert(flatCount === cliCount, 0x05a /* "Mismatch between number of partial lengths on client and flat views!" */);
|
|
696
|
+
}
|
|
697
|
+
else {
|
|
698
|
+
// If we don't have a client view, we shouldn't have the flat view either
|
|
699
|
+
assert(!partialSeqLengths["partialLengths"], 0x05b /* "Flat view exists but client view does not!" */);
|
|
700
|
+
}
|
|
701
|
+
}
|
|
702
|
+
/* eslint-enable @typescript-eslint/dot-notation */
|
|
703
|
+
/**
|
|
704
|
+
* Clones an `overlapRemoveClients` red-black tree.
|
|
705
|
+
*/
|
|
706
|
+
function cloneOverlapRemoveClients(oldTree) {
|
|
707
|
+
if (!oldTree) {
|
|
708
|
+
return undefined;
|
|
709
|
+
}
|
|
710
|
+
const newTree = new RedBlackTree(compareNumbers);
|
|
711
|
+
oldTree.map((bProp) => {
|
|
712
|
+
newTree.put(bProp.data.clientId, Object.assign({}, bProp.data));
|
|
713
|
+
return true;
|
|
714
|
+
});
|
|
715
|
+
return newTree;
|
|
716
|
+
}
|
|
717
|
+
/**
|
|
718
|
+
* Combines the `overlapRemoveClients` field of two `PartialSequenceLength` objects,
|
|
719
|
+
* modifying the first PartialSequenceLength's bookkeeping in-place.
|
|
720
|
+
*
|
|
721
|
+
* Combination is performed additively on `seglen` on a per-client basis.
|
|
722
|
+
*/
|
|
723
|
+
function combineOverlapClients(a, b) {
|
|
724
|
+
const overlapRemoveClientsA = a.overlapRemoveClients;
|
|
725
|
+
if (overlapRemoveClientsA) {
|
|
726
|
+
if (b.overlapRemoveClients) {
|
|
727
|
+
b.overlapRemoveClients.map((bProp) => {
|
|
728
|
+
const aProp = overlapRemoveClientsA.get(bProp.key);
|
|
729
|
+
if (aProp) {
|
|
730
|
+
aProp.data.seglen += bProp.data.seglen;
|
|
731
|
+
}
|
|
732
|
+
else {
|
|
733
|
+
overlapRemoveClientsA.put(bProp.data.clientId, Object.assign({}, bProp.data));
|
|
734
|
+
}
|
|
735
|
+
return true;
|
|
736
|
+
});
|
|
523
737
|
}
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
738
|
+
}
|
|
739
|
+
else {
|
|
740
|
+
a.overlapRemoveClients = cloneOverlapRemoveClients(b.overlapRemoveClients);
|
|
741
|
+
}
|
|
742
|
+
}
|
|
743
|
+
/**
|
|
744
|
+
* Given a number of seq-sorted `partialLength` lists, merges them into a combined seq-sorted `partialLength`
|
|
745
|
+
* list. This merge includes coalescing `PartialSequenceLength` entries at the same seq.
|
|
746
|
+
*
|
|
747
|
+
* Ex: merging the following two lists (some information omitted on each PartialSequenceLength):
|
|
748
|
+
* ```typescript
|
|
749
|
+
* [{ seq: 1, seglen: 5 }, { seq: 3, seglen: -1 }]
|
|
750
|
+
* [{ seq: 1, seglen: -3 }, { seq: 2: seglen: 4 }]
|
|
751
|
+
* ```
|
|
752
|
+
* would produce
|
|
753
|
+
* ```typescript
|
|
754
|
+
* [{ seq: 1, seglen: 2 }, { seq: 2, seglen: 4 }, { seq: 3, seglen: -1 }]
|
|
755
|
+
* ```
|
|
756
|
+
*/
|
|
757
|
+
function mergePartialLengths(childPartialLengths) {
|
|
758
|
+
var _a;
|
|
759
|
+
const mergedLengths = [];
|
|
760
|
+
// All child PartialSequenceLengths are now sorted temporally (i.e. by seq). Since
|
|
761
|
+
// a given MergeTree operation can affect multiple segments, there may be multiple entries
|
|
762
|
+
// for a given seq. We run through them in order, coalescing all length information for a given
|
|
763
|
+
// seq together into `combinedPartialLengths`.
|
|
764
|
+
let currentPartial;
|
|
765
|
+
for (const partialLength of mergeSortedListsBySeq(childPartialLengths)) {
|
|
766
|
+
if (!currentPartial || currentPartial.seq !== partialLength.seq) {
|
|
767
|
+
// Start a new seq entry.
|
|
768
|
+
currentPartial = Object.assign(Object.assign({}, partialLength), { len: ((_a = currentPartial === null || currentPartial === void 0 ? void 0 : currentPartial.len) !== null && _a !== void 0 ? _a : 0) + partialLength.seglen, overlapRemoveClients: cloneOverlapRemoveClients(partialLength.overlapRemoveClients) });
|
|
769
|
+
mergedLengths.push(currentPartial);
|
|
770
|
+
}
|
|
771
|
+
else {
|
|
772
|
+
// Update existing entry
|
|
773
|
+
currentPartial.seglen += partialLength.seglen;
|
|
774
|
+
currentPartial.len += partialLength.seglen;
|
|
775
|
+
combineOverlapClients(currentPartial, partialLength);
|
|
776
|
+
}
|
|
777
|
+
}
|
|
778
|
+
return mergedLengths;
|
|
779
|
+
}
|
|
780
|
+
/**
|
|
781
|
+
* Given a collection of PartialSequenceLength lists--each sorted by sequence number--returns an iterable that yields
|
|
782
|
+
* each PartialSequenceLength in sequence order.
|
|
783
|
+
*
|
|
784
|
+
* This is equivalent to flattening the input list and sorting it by sequence number. If the number of lists to merge is
|
|
785
|
+
* a constant, however, this approach is advantageous asymptotically.
|
|
786
|
+
*/
|
|
787
|
+
function mergeSortedListsBySeq(lists) {
|
|
788
|
+
class PartialSequenceLengthIterator {
|
|
789
|
+
constructor(sublists) {
|
|
790
|
+
this.sublists = sublists;
|
|
791
|
+
this.nextSmallestIndex = new Array(sublists.length);
|
|
792
|
+
for (let i = 0; i < sublists.length; i++) {
|
|
793
|
+
this.nextSmallestIndex[i] = 0;
|
|
794
|
+
}
|
|
795
|
+
}
|
|
796
|
+
next() {
|
|
797
|
+
const len = this.sublists.length;
|
|
798
|
+
let currentMin;
|
|
799
|
+
let currentMinIndex;
|
|
800
|
+
for (let i = 0; i < len; i++) {
|
|
801
|
+
const candidateIndex = this.nextSmallestIndex[i];
|
|
802
|
+
if (candidateIndex < this.sublists[i].length) {
|
|
803
|
+
const candidate = this.sublists[i][candidateIndex];
|
|
804
|
+
if (!currentMin || candidate.seq < currentMin.seq) {
|
|
805
|
+
currentMin = candidate;
|
|
806
|
+
currentMinIndex = i;
|
|
807
|
+
}
|
|
808
|
+
}
|
|
539
809
|
}
|
|
540
|
-
if (
|
|
541
|
-
//
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
// e.g. if you have this sequence, where the minSeq is #5 because of other clients
|
|
545
|
-
// seq 10: client 1: insert seg #1
|
|
546
|
-
// seq 11: client 2: delete seg #2 refseq: 10
|
|
547
|
-
// minLength is 0, we would have keep a record of seglen: -1 for clientPartialLengths for client 2
|
|
548
|
-
// So if you ask for partial length for client 2 @ seq 5, we will have return -1.
|
|
549
|
-
// However, that combination is invalid, since we should never see any ops with refseq < 10 for
|
|
550
|
-
// client 2 after seq 11.
|
|
810
|
+
if (currentMin) {
|
|
811
|
+
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
812
|
+
this.nextSmallestIndex[currentMinIndex]++;
|
|
813
|
+
return { value: currentMin, done: false };
|
|
551
814
|
}
|
|
552
815
|
else {
|
|
553
|
-
|
|
554
|
-
if (this.minLength + partialLength.len < 0) {
|
|
555
|
-
assert(false, 0x057 /* "Negative length after length adjustment!" */);
|
|
556
|
-
}
|
|
557
|
-
}
|
|
558
|
-
if (partialLength.overlapRemoveClients) {
|
|
559
|
-
// Only the flat partialLengths can have overlapRemoveClients, the per client view shouldn't
|
|
560
|
-
assert(!clientPartials, 0x058 /* "Both overlapRemoveClients and clientPartials are set!" */);
|
|
561
|
-
// Each overlap client count as one
|
|
562
|
-
count += partialLength.overlapRemoveClients.size();
|
|
816
|
+
return { value: undefined, done: true };
|
|
563
817
|
}
|
|
564
818
|
}
|
|
565
|
-
return count;
|
|
566
819
|
}
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
}
|
|
574
|
-
}
|
|
575
|
-
// If we have client view, we should have the flat view
|
|
576
|
-
assert(!!this.partialLengths, 0x059 /* "Client view exists but flat view does not!" */);
|
|
577
|
-
const flatCount = this.verifyPartialLengths(this.partialLengths, false);
|
|
578
|
-
// The number of partial lengths on the client view and flat view should be the same
|
|
579
|
-
assert(flatCount === cliCount, 0x05a /* "Mismatch between number of partial lengths on client and flat views!" */);
|
|
580
|
-
}
|
|
581
|
-
else {
|
|
582
|
-
// If we don't have a client view, we shouldn't have the flat view either
|
|
583
|
-
assert(!this.partialLengths, 0x05b /* "Flat view exists but client view does not!" */);
|
|
820
|
+
return { [Symbol.iterator]: () => new PartialSequenceLengthIterator(lists) };
|
|
821
|
+
}
|
|
822
|
+
function insertIntoList(list, index, elem) {
|
|
823
|
+
if (index < list.length) {
|
|
824
|
+
for (let k = list.length; k > index; k--) {
|
|
825
|
+
list[k] = list[k - 1];
|
|
584
826
|
}
|
|
827
|
+
list[index] = elem;
|
|
828
|
+
}
|
|
829
|
+
else {
|
|
830
|
+
list.push(elem);
|
|
585
831
|
}
|
|
586
832
|
}
|
|
587
|
-
PartialSequenceLengths.options = {
|
|
588
|
-
verify: false,
|
|
589
|
-
zamboni: true,
|
|
590
|
-
};
|
|
591
833
|
//# sourceMappingURL=partialLengths.js.map
|