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