@fluidframework/merge-tree 1.2.1 → 2.0.0-internal.1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/DEV.md +2 -2
- package/README.md +1 -1
- package/REFERENCEPOSITIONS.md +2 -2
- package/dist/MergeTreeTextHelper.d.ts +23 -0
- package/dist/MergeTreeTextHelper.d.ts.map +1 -0
- package/dist/MergeTreeTextHelper.js +136 -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 +21 -12
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +87 -27
- 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} +10 -448
- 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 +38 -299
- package/dist/mergeTree.d.ts.map +1 -1
- package/dist/mergeTree.js +214 -598
- 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 +269 -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 +130 -15
- package/dist/partialLengths.d.ts.map +1 -1
- package/dist/partialLengths.js +230 -138
- 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.map +1 -1
- package/dist/segmentGroupCollection.d.ts +2 -1
- package/dist/segmentGroupCollection.d.ts.map +1 -1
- package/dist/segmentGroupCollection.js +3 -0
- 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 +41 -6
- package/dist/segmentPropertiesManager.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.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/lib/MergeTreeTextHelper.d.ts +23 -0
- package/lib/MergeTreeTextHelper.d.ts.map +1 -0
- package/lib/MergeTreeTextHelper.js +132 -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 +21 -12
- package/lib/client.d.ts.map +1 -1
- package/lib/client.js +85 -25
- 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} +9 -439
- 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 +38 -299
- package/lib/mergeTree.d.ts.map +1 -1
- package/lib/mergeTree.js +190 -563
- 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 +269 -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 +130 -15
- package/lib/partialLengths.d.ts.map +1 -1
- package/lib/partialLengths.js +227 -135
- 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.map +1 -1
- package/lib/segmentGroupCollection.d.ts +2 -1
- package/lib/segmentGroupCollection.d.ts.map +1 -1
- package/lib/segmentGroupCollection.js +3 -0
- 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 +41 -6
- package/lib/segmentPropertiesManager.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.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 +93 -17
- package/src/MergeTreeTextHelper.ts +172 -0
- package/src/base.ts +2 -35
- package/src/client.ts +114 -30
- package/src/collections/heap.ts +75 -0
- package/src/collections/index.ts +11 -0
- package/src/collections/intervalTree.ts +140 -0
- package/src/collections/list.ts +165 -0
- package/src/{collections.ts → collections/rbTree.ts} +79 -538
- 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 +265 -868
- package/src/mergeTreeDeltaCallback.ts +1 -1
- package/src/mergeTreeNodes.ts +676 -0
- package/src/mergeTreeTracking.ts +1 -1
- package/src/opBuilder.ts +1 -1
- package/src/partialLengths.ts +295 -150
- package/src/properties.ts +1 -0
- package/src/referencePositions.ts +7 -27
- package/src/segmentGroupCollection.ts +5 -1
- package/src/segmentPropertiesManager.ts +45 -6
- package/src/snapshotLoader.ts +2 -1
- package/src/snapshotV1.ts +2 -2
- 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,137 @@ 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
|
+
* This algorithm scales roughly linearly with number of editing clients and the size of the collab window.
|
|
65
|
+
* (certain unlikely sequences of operations may introduce log factors on those variables)
|
|
66
|
+
*
|
|
67
|
+
* Note: there is some slight complication with clientSeqNumbers resulting from the possibility of different clients
|
|
68
|
+
* concurrently removing the same segment. See the field's documentation for more details.
|
|
38
69
|
*/
|
|
39
70
|
export class PartialSequenceLengths {
|
|
40
|
-
constructor(
|
|
71
|
+
constructor(
|
|
72
|
+
/**
|
|
73
|
+
* The minimumSequenceNumber as defined by the collab window used in the last call to `update`,
|
|
74
|
+
* or if no such calls have been made, the one used on construction.
|
|
75
|
+
*/
|
|
76
|
+
minSeq) {
|
|
41
77
|
this.minSeq = minSeq;
|
|
78
|
+
/**
|
|
79
|
+
* Length of the block this PartialSequenceLength corresponds to when viewed at `minSeq`.
|
|
80
|
+
*/
|
|
42
81
|
this.minLength = 0;
|
|
82
|
+
/**
|
|
83
|
+
* Total number of segments in the subtree rooted at the block this PartialSequenceLength corresponds to.
|
|
84
|
+
*/
|
|
43
85
|
this.segmentCount = 0;
|
|
86
|
+
/**
|
|
87
|
+
* List of PartialSequenceLength objects--ordered by increasing seq--giving length information about
|
|
88
|
+
* the block associated with this PartialSequenceLengths object.
|
|
89
|
+
*
|
|
90
|
+
* `partialLengths[i].len` contains the length of this block considering only sequenced segments with
|
|
91
|
+
* `sequenceNumber <= partialLengths[i].seq`.
|
|
92
|
+
*/
|
|
44
93
|
this.partialLengths = [];
|
|
94
|
+
/**
|
|
95
|
+
* clientSeqNumbers[clientId] is a list of partial lengths for sequenced ops which either:
|
|
96
|
+
* - were submitted by `clientId`.
|
|
97
|
+
* - deleted a range containing segments that were concurrently deleted by `clientId`
|
|
98
|
+
*
|
|
99
|
+
* The second case is referred to as the "overlapping delete" case. It is necessary to avoid double-counting
|
|
100
|
+
* the removal of those segments in queries including clientId.
|
|
101
|
+
*/
|
|
45
102
|
this.clientSeqNumbers = [];
|
|
46
103
|
}
|
|
47
|
-
static combine(mergeTree, block, collabWindow, recur = false) {
|
|
48
|
-
return PartialSequenceLengths.combineBranch(mergeTree, block, collabWindow, recur);
|
|
49
|
-
}
|
|
50
104
|
/**
|
|
51
105
|
* 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
|
|
106
|
+
* @param block - an interior node. If `recur` is false, it is assumed that each interior node child of this block
|
|
107
|
+
* has its partials up to date.
|
|
108
|
+
* @param collabWindow - segment window of the segment tree containing `block`.
|
|
109
|
+
* @param recur - whether to recursively compute partial lengths for internal children of `block`.
|
|
110
|
+
* This incurs more work, but gives correct bookkeeping in the case that a descendant in the merge tree has been
|
|
111
|
+
* modified without bubbling up the resulting partial length change to this block's partials.
|
|
55
112
|
*/
|
|
56
|
-
static
|
|
57
|
-
|
|
58
|
-
PartialSequenceLengths.fromLeaves(
|
|
59
|
-
let
|
|
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
|
-
}
|
|
113
|
+
static combine(block, collabWindow, recur = false) {
|
|
114
|
+
var _a;
|
|
115
|
+
const leafPartialLengths = PartialSequenceLengths.fromLeaves(block, collabWindow);
|
|
116
|
+
let hasInternalChild = false;
|
|
116
117
|
const childPartials = [];
|
|
117
118
|
for (let i = 0; i < block.childCount; i++) {
|
|
118
119
|
const child = block.children[i];
|
|
119
120
|
if (!child.isLeaf()) {
|
|
120
|
-
|
|
121
|
+
hasInternalChild = true;
|
|
121
122
|
if (recur) {
|
|
122
|
-
|
|
123
|
-
PartialSequenceLengths.combine(
|
|
123
|
+
child.partialLengths =
|
|
124
|
+
PartialSequenceLengths.combine(child, collabWindow, true);
|
|
124
125
|
}
|
|
125
126
|
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
126
|
-
childPartials.push(
|
|
127
|
+
childPartials.push(child.partialLengths);
|
|
127
128
|
}
|
|
128
129
|
}
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
130
|
+
// If there are no internal children, the PartialSequenceLengths returns from `fromLeaves` is exactly correct.
|
|
131
|
+
// Otherwise, we must additively combine all of the children partial lengths to get this block's totals.
|
|
132
|
+
const combinedPartialLengths = hasInternalChild ? new PartialSequenceLengths(collabWindow.minSeq) : leafPartialLengths;
|
|
133
|
+
if (hasInternalChild) {
|
|
134
|
+
if (leafPartialLengths.partialLengths.length > 0) {
|
|
133
135
|
// Some children were leaves; add combined partials from these segments
|
|
134
|
-
childPartials.push(
|
|
135
|
-
childPartialsLen++;
|
|
136
|
-
combinedPartialLengths = new PartialSequenceLengths(collabWindow.minSeq);
|
|
136
|
+
childPartials.push(leafPartialLengths);
|
|
137
137
|
}
|
|
138
|
-
const
|
|
139
|
-
const
|
|
138
|
+
const childPartialsLen = childPartials.length;
|
|
139
|
+
const childPartialLengths = [];
|
|
140
140
|
for (let i = 0; i < childPartialsLen; i++) {
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
combinedPartialLengths.minLength +=
|
|
144
|
-
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
outerIndexOfEarliest = k;
|
|
157
|
-
earliestPartialLength = cpLen;
|
|
158
|
-
}
|
|
159
|
-
}
|
|
141
|
+
const { segmentCount, minLength, partialLengths } = childPartials[i];
|
|
142
|
+
combinedPartialLengths.segmentCount += segmentCount;
|
|
143
|
+
combinedPartialLengths.minLength += minLength;
|
|
144
|
+
childPartialLengths.push(partialLengths);
|
|
145
|
+
}
|
|
146
|
+
// All child PartialSequenceLengths are now sorted temporally (i.e. by seq). Since
|
|
147
|
+
// a given MergeTree operation can affect multiple segments, there may be multiple entries
|
|
148
|
+
// for a given seq. We run through them in order, coalescing all length information for a given
|
|
149
|
+
// seq together into `combinedPartialLengths`.
|
|
150
|
+
let currentPartial;
|
|
151
|
+
for (const partialLength of mergeSortedListsBySeq(childPartialLengths)) {
|
|
152
|
+
if (!currentPartial || currentPartial.seq !== partialLength.seq) {
|
|
153
|
+
// Start a new seq entry.
|
|
154
|
+
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) });
|
|
155
|
+
combinedPartialLengths.partialLengths.push(currentPartial);
|
|
160
156
|
}
|
|
161
|
-
|
|
162
|
-
//
|
|
163
|
-
|
|
164
|
-
|
|
157
|
+
else {
|
|
158
|
+
// Update existing entry
|
|
159
|
+
currentPartial.seglen += partialLength.seglen;
|
|
160
|
+
currentPartial.len += partialLength.seglen;
|
|
161
|
+
combineOverlapClients(currentPartial, partialLength);
|
|
165
162
|
}
|
|
166
163
|
}
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
combinedPartialLengths.addClientSeqNumberFromPartial(prevPartial);
|
|
164
|
+
for (const partial of combinedPartialLengths.partialLengths) {
|
|
165
|
+
combinedPartialLengths.addClientSeqNumberFromPartial(partial);
|
|
170
166
|
}
|
|
171
167
|
}
|
|
172
168
|
// TODO: incremental zamboni during build
|
|
@@ -178,8 +174,12 @@ export class PartialSequenceLengths {
|
|
|
178
174
|
}
|
|
179
175
|
return combinedPartialLengths;
|
|
180
176
|
}
|
|
181
|
-
|
|
182
|
-
|
|
177
|
+
/**
|
|
178
|
+
* @returns a PartialSequenceLengths structure which tracks only lengths of leaf children of the provided
|
|
179
|
+
* IMergeBlock.
|
|
180
|
+
*/
|
|
181
|
+
static fromLeaves(block, collabWindow) {
|
|
182
|
+
const combinedPartialLengths = new PartialSequenceLengths(collabWindow.minSeq);
|
|
183
183
|
combinedPartialLengths.segmentCount = block.childCount;
|
|
184
184
|
function seqLTE(seq, minSeq) {
|
|
185
185
|
return seq !== undefined && seq !== UnassignedSequenceNumber && seq <= minSeq;
|
|
@@ -222,6 +222,7 @@ export class PartialSequenceLengths {
|
|
|
222
222
|
if (PartialSequenceLengths.options.verify) {
|
|
223
223
|
combinedPartialLengths.verify();
|
|
224
224
|
}
|
|
225
|
+
return combinedPartialLengths;
|
|
225
226
|
}
|
|
226
227
|
static getOverlapClients(overlapClientIds, seglen) {
|
|
227
228
|
const bst = new RedBlackTree(compareNumbers);
|
|
@@ -247,7 +248,14 @@ export class PartialSequenceLengths {
|
|
|
247
248
|
PartialSequenceLengths.getOverlapClients(overlapRemoveClientIds, seglen);
|
|
248
249
|
}
|
|
249
250
|
}
|
|
251
|
+
/**
|
|
252
|
+
* Inserts length information about the insertion of `segment` into `combinedPartialLengths.partialLengths`.
|
|
253
|
+
* Does not update the clientSeqNumbers field to account for this segment.
|
|
254
|
+
* If `removalInfo` is defined, this operation updates the bookkeeping to account for the removal of this
|
|
255
|
+
* segment at the removedSeq instead.
|
|
256
|
+
*/
|
|
250
257
|
static insertSegment(combinedPartialLengths, segment, removalInfo) {
|
|
258
|
+
var _a;
|
|
251
259
|
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
252
260
|
let seq = segment.seq;
|
|
253
261
|
let segmentLen = segment.cachedLength;
|
|
@@ -273,31 +281,34 @@ export class PartialSequenceLengths {
|
|
|
273
281
|
break;
|
|
274
282
|
}
|
|
275
283
|
}
|
|
276
|
-
|
|
277
|
-
|
|
284
|
+
let partialLengthEntry = ((_a = seqPartials[indexFirstGTE]) === null || _a === void 0 ? void 0 : _a.seq) === seq ? seqPartials[indexFirstGTE] : undefined;
|
|
285
|
+
if (partialLengthEntry !== undefined) {
|
|
286
|
+
// Existing entry at this seq--this occurs for ops that insert/delete more than one segment.
|
|
287
|
+
partialLengthEntry.seglen += segmentLen;
|
|
278
288
|
if (removeClientOverlap) {
|
|
279
289
|
PartialSequenceLengths.accumulateRemoveClientOverlap(seqPartials[indexFirstGTE], removeClientOverlap, segmentLen);
|
|
280
290
|
}
|
|
281
291
|
}
|
|
282
292
|
else {
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
293
|
+
partialLengthEntry = {
|
|
294
|
+
seq,
|
|
295
|
+
clientId,
|
|
296
|
+
len: 0,
|
|
297
|
+
seglen: segmentLen,
|
|
298
|
+
overlapRemoveClients: removeClientOverlap
|
|
299
|
+
? PartialSequenceLengths.getOverlapClients(removeClientOverlap, segmentLen)
|
|
300
|
+
: undefined,
|
|
301
|
+
};
|
|
291
302
|
if (indexFirstGTE < seqPartialsLen) {
|
|
292
303
|
// Shift entries with greater sequence numbers
|
|
293
304
|
// TODO: investigate performance improvement using BST
|
|
294
305
|
for (let k = seqPartialsLen; k > indexFirstGTE; k--) {
|
|
295
306
|
seqPartials[k] = seqPartials[k - 1];
|
|
296
307
|
}
|
|
297
|
-
seqPartials[indexFirstGTE] =
|
|
308
|
+
seqPartials[indexFirstGTE] = partialLengthEntry;
|
|
298
309
|
}
|
|
299
310
|
else {
|
|
300
|
-
seqPartials.push(
|
|
311
|
+
seqPartials.push(partialLengthEntry);
|
|
301
312
|
}
|
|
302
313
|
}
|
|
303
314
|
}
|
|
@@ -318,11 +329,11 @@ export class PartialSequenceLengths {
|
|
|
318
329
|
penultPartialLen = pLen;
|
|
319
330
|
}
|
|
320
331
|
}
|
|
332
|
+
const len = penultPartialLen !== undefined ? penultPartialLen.len + seqSeglen : seqSeglen;
|
|
321
333
|
if (seqPartialLen === undefined) {
|
|
322
|
-
// len will be assigned below, making this assertion true.
|
|
323
|
-
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
324
334
|
seqPartialLen = {
|
|
325
335
|
clientId,
|
|
336
|
+
len,
|
|
326
337
|
seglen: seqSeglen,
|
|
327
338
|
seq,
|
|
328
339
|
};
|
|
@@ -330,19 +341,15 @@ export class PartialSequenceLengths {
|
|
|
330
341
|
}
|
|
331
342
|
else {
|
|
332
343
|
seqPartialLen.seglen = seqSeglen;
|
|
344
|
+
seqPartialLen.len = len;
|
|
333
345
|
// Assert client id matches
|
|
334
346
|
}
|
|
335
|
-
if (penultPartialLen !== undefined) {
|
|
336
|
-
seqPartialLen.len = seqPartialLen.seglen + penultPartialLen.len;
|
|
337
|
-
}
|
|
338
|
-
else {
|
|
339
|
-
seqPartialLen.len = seqPartialLen.seglen;
|
|
340
|
-
}
|
|
341
347
|
}
|
|
342
348
|
// 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
|
|
349
|
+
// with sequence number seq has been added within the sub-tree (and `update` has been called
|
|
350
|
+
// on all descendant PartialSequenceLengths)
|
|
344
351
|
// TODO: assert client id matches
|
|
345
|
-
update(
|
|
352
|
+
update(node, seq, clientId, collabWindow) {
|
|
346
353
|
let seqSeglen = 0;
|
|
347
354
|
let segCount = 0;
|
|
348
355
|
// Compute length for seq across children
|
|
@@ -406,6 +413,8 @@ export class PartialSequenceLengths {
|
|
|
406
413
|
pLen += cliLatest.len;
|
|
407
414
|
const precedingCliIndex = this.cliLatestLEQ(clientId, refSeq);
|
|
408
415
|
if (precedingCliIndex >= 0) {
|
|
416
|
+
// Subtract out double-counted lengths: segments still in the collab window but before
|
|
417
|
+
// the refSeq submitted by the client we're querying for were counted in each addition above.
|
|
409
418
|
pLen -= cliSeq[precedingCliIndex].len;
|
|
410
419
|
}
|
|
411
420
|
}
|
|
@@ -468,6 +477,7 @@ export class PartialSequenceLengths {
|
|
|
468
477
|
return minLength;
|
|
469
478
|
}
|
|
470
479
|
this.minLength += copyDown(this.partialLengths);
|
|
480
|
+
this.minSeq = segmentWindow.minSeq;
|
|
471
481
|
// eslint-disable-next-line @typescript-eslint/no-for-in-array, guard-for-in, no-restricted-syntax
|
|
472
482
|
for (const clientId in this.clientSeqNumbers) {
|
|
473
483
|
const cliPartials = this.clientSeqNumbers[clientId];
|
|
@@ -487,7 +497,7 @@ export class PartialSequenceLengths {
|
|
|
487
497
|
}
|
|
488
498
|
cli.push({ seq, len: pLen, seglen });
|
|
489
499
|
}
|
|
490
|
-
// Assumes sequence number already coalesced
|
|
500
|
+
// Assumes sequence number already coalesced and that this is called in increasing `seq` order.
|
|
491
501
|
addClientSeqNumberFromPartial(partialLength) {
|
|
492
502
|
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
493
503
|
this.addClientSeqNumber(partialLength.clientId, partialLength.seq, partialLength.seglen);
|
|
@@ -588,4 +598,86 @@ PartialSequenceLengths.options = {
|
|
|
588
598
|
verify: false,
|
|
589
599
|
zamboni: true,
|
|
590
600
|
};
|
|
601
|
+
/**
|
|
602
|
+
* Clones an `overlapRemoveClients` red-black tree.
|
|
603
|
+
*/
|
|
604
|
+
function cloneOverlapRemoveClients(oldTree) {
|
|
605
|
+
if (!oldTree) {
|
|
606
|
+
return undefined;
|
|
607
|
+
}
|
|
608
|
+
const newTree = new RedBlackTree(compareNumbers);
|
|
609
|
+
oldTree.map((bProp) => {
|
|
610
|
+
newTree.put(bProp.data.clientId, Object.assign({}, bProp.data));
|
|
611
|
+
return true;
|
|
612
|
+
});
|
|
613
|
+
return newTree;
|
|
614
|
+
}
|
|
615
|
+
/**
|
|
616
|
+
* Combines the `overlapRemoveClients` field of two `PartialSequenceLength` objects,
|
|
617
|
+
* modifying the first PartialSequenceLength's bookkeeping in-place.
|
|
618
|
+
*
|
|
619
|
+
* Combination is performed additively on `seglen` on a per-client basis.
|
|
620
|
+
*/
|
|
621
|
+
function combineOverlapClients(a, b) {
|
|
622
|
+
const overlapRemoveClientsA = a.overlapRemoveClients;
|
|
623
|
+
if (overlapRemoveClientsA) {
|
|
624
|
+
if (b.overlapRemoveClients) {
|
|
625
|
+
b.overlapRemoveClients.map((bProp) => {
|
|
626
|
+
const aProp = overlapRemoveClientsA.get(bProp.key);
|
|
627
|
+
if (aProp) {
|
|
628
|
+
aProp.data.seglen += bProp.data.seglen;
|
|
629
|
+
}
|
|
630
|
+
else {
|
|
631
|
+
overlapRemoveClientsA.put(bProp.data.clientId, Object.assign({}, bProp.data));
|
|
632
|
+
}
|
|
633
|
+
return true;
|
|
634
|
+
});
|
|
635
|
+
}
|
|
636
|
+
}
|
|
637
|
+
else {
|
|
638
|
+
a.overlapRemoveClients = cloneOverlapRemoveClients(b.overlapRemoveClients);
|
|
639
|
+
}
|
|
640
|
+
}
|
|
641
|
+
/**
|
|
642
|
+
* Given a collection of PartialSequenceLength lists--each sorted by sequence number--returns an iterable that yields
|
|
643
|
+
* each PartialSequenceLength in sequence order.
|
|
644
|
+
*
|
|
645
|
+
* This is equivalent to flattening the input list and sorting it by sequence number. If the number of lists to merge is
|
|
646
|
+
* a constant, however, this approach is advantageous asymptotically.
|
|
647
|
+
*/
|
|
648
|
+
function mergeSortedListsBySeq(lists) {
|
|
649
|
+
class PartialSequenceLengthIterator {
|
|
650
|
+
constructor(sublists) {
|
|
651
|
+
this.sublists = sublists;
|
|
652
|
+
this.nextSmallestIndex = new Array(sublists.length);
|
|
653
|
+
for (let i = 0; i < sublists.length; i++) {
|
|
654
|
+
this.nextSmallestIndex[i] = 0;
|
|
655
|
+
}
|
|
656
|
+
}
|
|
657
|
+
next() {
|
|
658
|
+
const len = this.sublists.length;
|
|
659
|
+
let currentMin;
|
|
660
|
+
let currentMinIndex;
|
|
661
|
+
for (let i = 0; i < len; i++) {
|
|
662
|
+
const candidateIndex = this.nextSmallestIndex[i];
|
|
663
|
+
if (candidateIndex < this.sublists[i].length) {
|
|
664
|
+
const candidate = this.sublists[i][candidateIndex];
|
|
665
|
+
if (!currentMin || candidate.seq < currentMin.seq) {
|
|
666
|
+
currentMin = candidate;
|
|
667
|
+
currentMinIndex = i;
|
|
668
|
+
}
|
|
669
|
+
}
|
|
670
|
+
}
|
|
671
|
+
if (currentMin) {
|
|
672
|
+
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
673
|
+
this.nextSmallestIndex[currentMinIndex]++;
|
|
674
|
+
return { value: currentMin, done: false };
|
|
675
|
+
}
|
|
676
|
+
else {
|
|
677
|
+
return { value: undefined, done: true };
|
|
678
|
+
}
|
|
679
|
+
}
|
|
680
|
+
}
|
|
681
|
+
return { [Symbol.iterator]: () => new PartialSequenceLengthIterator(lists) };
|
|
682
|
+
}
|
|
591
683
|
//# sourceMappingURL=partialLengths.js.map
|