@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.
Files changed (231) hide show
  1. package/DEV.md +2 -2
  2. package/README.md +1 -1
  3. package/REFERENCEPOSITIONS.md +2 -2
  4. package/dist/MergeTreeTextHelper.d.ts +23 -0
  5. package/dist/MergeTreeTextHelper.d.ts.map +1 -0
  6. package/dist/MergeTreeTextHelper.js +136 -0
  7. package/dist/MergeTreeTextHelper.js.map +1 -0
  8. package/dist/base.d.ts +2 -26
  9. package/dist/base.d.ts.map +1 -1
  10. package/dist/base.js.map +1 -1
  11. package/dist/client.d.ts +21 -12
  12. package/dist/client.d.ts.map +1 -1
  13. package/dist/client.js +87 -27
  14. package/dist/client.js.map +1 -1
  15. package/dist/collections/heap.d.ts +28 -0
  16. package/dist/collections/heap.d.ts.map +1 -0
  17. package/dist/collections/heap.js +65 -0
  18. package/dist/collections/heap.js.map +1 -0
  19. package/dist/collections/index.d.ts +11 -0
  20. package/dist/collections/index.d.ts.map +1 -0
  21. package/dist/collections/index.js +23 -0
  22. package/dist/collections/index.js.map +1 -0
  23. package/dist/collections/intervalTree.d.ts +60 -0
  24. package/dist/collections/intervalTree.d.ts.map +1 -0
  25. package/dist/collections/intervalTree.js +99 -0
  26. package/dist/collections/intervalTree.js.map +1 -0
  27. package/dist/collections/list.d.ts +39 -0
  28. package/dist/collections/list.d.ts.map +1 -0
  29. package/dist/collections/list.js +155 -0
  30. package/dist/collections/list.js.map +1 -0
  31. package/dist/collections/rbTree.d.ts +154 -0
  32. package/dist/collections/rbTree.d.ts.map +1 -0
  33. package/dist/{collections.js → collections/rbTree.js} +10 -448
  34. package/dist/collections/rbTree.js.map +1 -0
  35. package/dist/collections/stack.d.ts +16 -0
  36. package/dist/collections/stack.d.ts.map +1 -0
  37. package/dist/collections/stack.js +30 -0
  38. package/dist/collections/stack.js.map +1 -0
  39. package/dist/collections/tst.d.ts +55 -0
  40. package/dist/collections/tst.d.ts.map +1 -0
  41. package/dist/collections/tst.js +171 -0
  42. package/dist/collections/tst.js.map +1 -0
  43. package/dist/index.d.ts +3 -1
  44. package/dist/index.d.ts.map +1 -1
  45. package/dist/index.js +4 -2
  46. package/dist/index.js.map +1 -1
  47. package/dist/localReference.d.ts +48 -99
  48. package/dist/localReference.d.ts.map +1 -1
  49. package/dist/localReference.js +132 -169
  50. package/dist/localReference.js.map +1 -1
  51. package/dist/mergeTree.d.ts +38 -299
  52. package/dist/mergeTree.d.ts.map +1 -1
  53. package/dist/mergeTree.js +214 -598
  54. package/dist/mergeTree.js.map +1 -1
  55. package/dist/mergeTreeDeltaCallback.d.ts +1 -1
  56. package/dist/mergeTreeDeltaCallback.d.ts.map +1 -1
  57. package/dist/mergeTreeDeltaCallback.js.map +1 -1
  58. package/dist/mergeTreeNodes.d.ts +269 -0
  59. package/dist/mergeTreeNodes.d.ts.map +1 -0
  60. package/dist/mergeTreeNodes.js +383 -0
  61. package/dist/mergeTreeNodes.js.map +1 -0
  62. package/dist/mergeTreeTracking.d.ts +1 -1
  63. package/dist/mergeTreeTracking.d.ts.map +1 -1
  64. package/dist/mergeTreeTracking.js.map +1 -1
  65. package/dist/opBuilder.d.ts +1 -1
  66. package/dist/opBuilder.d.ts.map +1 -1
  67. package/dist/opBuilder.js.map +1 -1
  68. package/dist/partialLengths.d.ts +130 -15
  69. package/dist/partialLengths.d.ts.map +1 -1
  70. package/dist/partialLengths.js +230 -138
  71. package/dist/partialLengths.js.map +1 -1
  72. package/dist/properties.d.ts.map +1 -1
  73. package/dist/properties.js.map +1 -1
  74. package/dist/referencePositions.d.ts +6 -26
  75. package/dist/referencePositions.d.ts.map +1 -1
  76. package/dist/referencePositions.js.map +1 -1
  77. package/dist/segmentGroupCollection.d.ts +2 -1
  78. package/dist/segmentGroupCollection.d.ts.map +1 -1
  79. package/dist/segmentGroupCollection.js +3 -0
  80. package/dist/segmentGroupCollection.js.map +1 -1
  81. package/dist/segmentPropertiesManager.d.ts +10 -1
  82. package/dist/segmentPropertiesManager.d.ts.map +1 -1
  83. package/dist/segmentPropertiesManager.js +41 -6
  84. package/dist/segmentPropertiesManager.js.map +1 -1
  85. package/dist/snapshotLoader.d.ts.map +1 -1
  86. package/dist/snapshotLoader.js.map +1 -1
  87. package/dist/snapshotV1.d.ts +1 -1
  88. package/dist/snapshotV1.d.ts.map +1 -1
  89. package/dist/snapshotV1.js.map +1 -1
  90. package/dist/snapshotlegacy.d.ts +5 -1
  91. package/dist/snapshotlegacy.d.ts.map +1 -1
  92. package/dist/snapshotlegacy.js +4 -0
  93. package/dist/snapshotlegacy.js.map +1 -1
  94. package/dist/sortedSegmentSet.d.ts +1 -1
  95. package/dist/sortedSegmentSet.d.ts.map +1 -1
  96. package/dist/sortedSegmentSet.js.map +1 -1
  97. package/dist/textSegment.d.ts +7 -7
  98. package/dist/textSegment.d.ts.map +1 -1
  99. package/dist/textSegment.js +3 -125
  100. package/dist/textSegment.js.map +1 -1
  101. package/lib/MergeTreeTextHelper.d.ts +23 -0
  102. package/lib/MergeTreeTextHelper.d.ts.map +1 -0
  103. package/lib/MergeTreeTextHelper.js +132 -0
  104. package/lib/MergeTreeTextHelper.js.map +1 -0
  105. package/lib/base.d.ts +2 -26
  106. package/lib/base.d.ts.map +1 -1
  107. package/lib/base.js.map +1 -1
  108. package/lib/client.d.ts +21 -12
  109. package/lib/client.d.ts.map +1 -1
  110. package/lib/client.js +85 -25
  111. package/lib/client.js.map +1 -1
  112. package/lib/collections/heap.d.ts +28 -0
  113. package/lib/collections/heap.d.ts.map +1 -0
  114. package/lib/collections/heap.js +61 -0
  115. package/lib/collections/heap.js.map +1 -0
  116. package/lib/collections/index.d.ts +11 -0
  117. package/lib/collections/index.d.ts.map +1 -0
  118. package/lib/collections/index.js +11 -0
  119. package/lib/collections/index.js.map +1 -0
  120. package/lib/collections/intervalTree.d.ts +60 -0
  121. package/lib/collections/intervalTree.d.ts.map +1 -0
  122. package/lib/collections/intervalTree.js +94 -0
  123. package/lib/collections/intervalTree.js.map +1 -0
  124. package/lib/collections/list.d.ts +39 -0
  125. package/lib/collections/list.d.ts.map +1 -0
  126. package/lib/collections/list.js +149 -0
  127. package/lib/collections/list.js.map +1 -0
  128. package/lib/collections/rbTree.d.ts +154 -0
  129. package/lib/collections/rbTree.d.ts.map +1 -0
  130. package/lib/{collections.js → collections/rbTree.js} +9 -439
  131. package/lib/collections/rbTree.js.map +1 -0
  132. package/lib/collections/stack.d.ts +16 -0
  133. package/lib/collections/stack.d.ts.map +1 -0
  134. package/lib/collections/stack.js +26 -0
  135. package/lib/collections/stack.js.map +1 -0
  136. package/lib/collections/tst.d.ts +55 -0
  137. package/lib/collections/tst.d.ts.map +1 -0
  138. package/lib/collections/tst.js +167 -0
  139. package/lib/collections/tst.js.map +1 -0
  140. package/lib/index.d.ts +3 -1
  141. package/lib/index.d.ts.map +1 -1
  142. package/lib/index.js +3 -1
  143. package/lib/index.js.map +1 -1
  144. package/lib/localReference.d.ts +48 -99
  145. package/lib/localReference.d.ts.map +1 -1
  146. package/lib/localReference.js +132 -170
  147. package/lib/localReference.js.map +1 -1
  148. package/lib/mergeTree.d.ts +38 -299
  149. package/lib/mergeTree.d.ts.map +1 -1
  150. package/lib/mergeTree.js +190 -563
  151. package/lib/mergeTree.js.map +1 -1
  152. package/lib/mergeTreeDeltaCallback.d.ts +1 -1
  153. package/lib/mergeTreeDeltaCallback.d.ts.map +1 -1
  154. package/lib/mergeTreeDeltaCallback.js.map +1 -1
  155. package/lib/mergeTreeNodes.d.ts +269 -0
  156. package/lib/mergeTreeNodes.d.ts.map +1 -0
  157. package/lib/mergeTreeNodes.js +369 -0
  158. package/lib/mergeTreeNodes.js.map +1 -0
  159. package/lib/mergeTreeTracking.d.ts +1 -1
  160. package/lib/mergeTreeTracking.d.ts.map +1 -1
  161. package/lib/mergeTreeTracking.js.map +1 -1
  162. package/lib/opBuilder.d.ts +1 -1
  163. package/lib/opBuilder.d.ts.map +1 -1
  164. package/lib/opBuilder.js.map +1 -1
  165. package/lib/partialLengths.d.ts +130 -15
  166. package/lib/partialLengths.d.ts.map +1 -1
  167. package/lib/partialLengths.js +227 -135
  168. package/lib/partialLengths.js.map +1 -1
  169. package/lib/properties.d.ts.map +1 -1
  170. package/lib/properties.js.map +1 -1
  171. package/lib/referencePositions.d.ts +6 -26
  172. package/lib/referencePositions.d.ts.map +1 -1
  173. package/lib/referencePositions.js.map +1 -1
  174. package/lib/segmentGroupCollection.d.ts +2 -1
  175. package/lib/segmentGroupCollection.d.ts.map +1 -1
  176. package/lib/segmentGroupCollection.js +3 -0
  177. package/lib/segmentGroupCollection.js.map +1 -1
  178. package/lib/segmentPropertiesManager.d.ts +10 -1
  179. package/lib/segmentPropertiesManager.d.ts.map +1 -1
  180. package/lib/segmentPropertiesManager.js +41 -6
  181. package/lib/segmentPropertiesManager.js.map +1 -1
  182. package/lib/snapshotLoader.d.ts.map +1 -1
  183. package/lib/snapshotLoader.js.map +1 -1
  184. package/lib/snapshotV1.d.ts +1 -1
  185. package/lib/snapshotV1.d.ts.map +1 -1
  186. package/lib/snapshotV1.js.map +1 -1
  187. package/lib/snapshotlegacy.d.ts +5 -1
  188. package/lib/snapshotlegacy.d.ts.map +1 -1
  189. package/lib/snapshotlegacy.js +4 -0
  190. package/lib/snapshotlegacy.js.map +1 -1
  191. package/lib/sortedSegmentSet.d.ts +1 -1
  192. package/lib/sortedSegmentSet.d.ts.map +1 -1
  193. package/lib/sortedSegmentSet.js.map +1 -1
  194. package/lib/textSegment.d.ts +7 -7
  195. package/lib/textSegment.d.ts.map +1 -1
  196. package/lib/textSegment.js +1 -122
  197. package/lib/textSegment.js.map +1 -1
  198. package/package.json +93 -17
  199. package/src/MergeTreeTextHelper.ts +172 -0
  200. package/src/base.ts +2 -35
  201. package/src/client.ts +114 -30
  202. package/src/collections/heap.ts +75 -0
  203. package/src/collections/index.ts +11 -0
  204. package/src/collections/intervalTree.ts +140 -0
  205. package/src/collections/list.ts +165 -0
  206. package/src/{collections.ts → collections/rbTree.ts} +79 -538
  207. package/src/collections/stack.ts +27 -0
  208. package/src/collections/tst.ts +212 -0
  209. package/src/index.ts +8 -2
  210. package/src/localReference.ts +152 -203
  211. package/src/mergeTree.ts +265 -868
  212. package/src/mergeTreeDeltaCallback.ts +1 -1
  213. package/src/mergeTreeNodes.ts +676 -0
  214. package/src/mergeTreeTracking.ts +1 -1
  215. package/src/opBuilder.ts +1 -1
  216. package/src/partialLengths.ts +295 -150
  217. package/src/properties.ts +1 -0
  218. package/src/referencePositions.ts +7 -27
  219. package/src/segmentGroupCollection.ts +5 -1
  220. package/src/segmentPropertiesManager.ts +45 -6
  221. package/src/snapshotLoader.ts +2 -1
  222. package/src/snapshotV1.ts +2 -2
  223. package/src/snapshotlegacy.ts +6 -2
  224. package/src/sortedSegmentSet.ts +1 -1
  225. package/src/textSegment.ts +10 -157
  226. package/dist/collections.d.ts +0 -197
  227. package/dist/collections.d.ts.map +0 -1
  228. package/dist/collections.js.map +0 -1
  229. package/lib/collections.d.ts +0 -197
  230. package/lib/collections.d.ts.map +0 -1
  231. package/lib/collections.js.map +0 -1
@@ -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 "./mergeTree";
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
- * Keep track of partial sums of segment lengths for all sequence numbers
36
- * in the current collaboration window (if any). Only used during active
37
- * collaboration.
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(minSeq) {
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; it is assumed that each interior node child of this block
53
- * has its partials up to date
54
- * @param collabWindow - segment window of the segment tree containing textSegmentBlock
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 combineBranch(mergeTree, block, collabWindow, recur = false) {
57
- let combinedPartialLengths = new PartialSequenceLengths(collabWindow.minSeq);
58
- PartialSequenceLengths.fromLeaves(combinedPartialLengths, block, collabWindow);
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
- }
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
- const childBlock = child;
121
+ hasInternalChild = true;
121
122
  if (recur) {
122
- childBlock.partialLengths =
123
- PartialSequenceLengths.combine(mergeTree, childBlock, collabWindow, true);
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(childBlock.partialLengths);
127
+ childPartials.push(child.partialLengths);
127
128
  }
128
129
  }
129
- let childPartialsLen = childPartials.length;
130
- if (childPartialsLen !== 0) {
131
- // Some children are interior nodes
132
- if (combinedPartialLengths.partialLengths.length > 0) {
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(combinedPartialLengths);
135
- childPartialsLen++;
136
- combinedPartialLengths = new PartialSequenceLengths(collabWindow.minSeq);
136
+ childPartials.push(leafPartialLengths);
137
137
  }
138
- const indices = new Array(childPartialsLen);
139
- const childPartialsCounts = new Array(childPartialsLen);
138
+ const childPartialsLen = childPartials.length;
139
+ const childPartialLengths = [];
140
140
  for (let i = 0; i < childPartialsLen; i++) {
141
- indices[i] = 0;
142
- childPartialsCounts[i] = childPartials[i].partialLengths.length;
143
- combinedPartialLengths.minLength += childPartials[i].minLength;
144
- combinedPartialLengths.segmentCount += childPartials[i].segmentCount;
145
- }
146
- let outerIndexOfEarliest = 0;
147
- let earliestPartialLength;
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
- }
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
- if (outerIndexOfEarliest >= 0) {
162
- // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
163
- addNext(earliestPartialLength);
164
- indices[outerIndexOfEarliest]++;
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
- // Add client entry for last partial, if any
168
- if (prevPartial) {
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
- static fromLeaves(combinedPartialLengths, block, collabWindow) {
182
- combinedPartialLengths.minLength = 0;
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
- if ((indexFirstGTE < seqPartialsLen) && (seqPartials[indexFirstGTE].seq === seq)) {
277
- seqPartials[indexFirstGTE].seglen += segmentLen;
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
- let pLen;
284
- if (removeClientOverlap) {
285
- const overlapClients = PartialSequenceLengths.getOverlapClients(removeClientOverlap, segmentLen);
286
- pLen = { seq, clientId, len: 0, seglen: segmentLen, overlapRemoveClients: overlapClients };
287
- }
288
- else {
289
- pLen = { seq, clientId, len: 0, seglen: segmentLen };
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] = pLen;
308
+ seqPartials[indexFirstGTE] = partialLengthEntry;
298
309
  }
299
310
  else {
300
- seqPartials.push(pLen);
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(mergeTree, node, seq, clientId, collabWindow) {
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