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