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