@@ -136,14 +136,19 @@ interface UnsequencedPartialLengthInfo {
136
136
* Like PerClientAdjustments, except we store one set of PartialSequenceLengthsSet for each refSeq. The "seq" keys in these sets
137
137
* are all local seqs.
138
138
*
139
- * TODO: Explain the lazy computation bit and how it works.
139
+ * These entries are aggregated by {@link PartialSequenceLengths.computeOverallRefSeqAdjustment} when a local perspective for a
140
+ * given refSeq is requested.
141
+ *
142
+ * In general, adjustments in this map are added to avoid double-counting an operation performed by both the local client and some
143
+ * remote client, and an adjustment at (refSeq = A, clientSeq = B) takes effect for all perspectives (refSeq = C, clientSeq = D) where
144
+ * A \<= C and B \<= D.
140
145
*/
141
146
perRefSeqAdjustments : Map < number , PartialSequenceLengthsSet > ;
142
147
143
148
/**
144
149
* Cache keyed on refSeq which stores length information for the total overlap of removed segments at
145
150
* that refSeq.
146
- * This information is derivable from the entries of `overlappingRemoves `.
151
+ * This information is derivable from the entries of `perRefSeqAdjustments `.
147
152
*
148
153
* Like the `partialLengths` field, `seq` on each entry is actually the local seq.
149
154
* See `computeOverlappingLocalRemoves` for more information.
@@ -171,13 +176,13 @@ export interface PartialSequenceLengthsOptions {
171
176
* "What is the length of `block` from the perspective of some particular seq and clientId?".
172
177
*
173
178
* It also supports incremental updating of state for newly-sequenced ops that don't affect the structure of the
174
- * MergeTree.
179
+ * MergeTree (in most cases--see AB#31003 or comments on { @link PartialSequenceLengths.update}) .
175
180
*
176
181
* To answer these queries, it pre-builds several lists which track the length of the block at a per-sequence-number
177
182
* level. These lists are:
178
183
*
179
184
* 1. (`partialLengths`): Stores the total length of the block.
180
- * 2. (`clientSeqNumbers [clientId]`): Stores only the total lengths of segments submitted by `clientId`. [see footnote]
185
+ * 2. (`perClientAdjustments [clientId]`): Stores adjustments to the base length which account for all changes submitted by `clientId`. [see footnote]
181
186
*
182
187
* The reason both lists are necessary is that resolving the length of the block from the perspective of
183
188
* (clientId, refSeq) requires including both of the following types of segments:
@@ -200,13 +205,19 @@ export interface PartialSequenceLengthsOptions {
200
205
* (length of the block at the minimum sequence number)
201
206
* + (partialLengths total length at refSeq)
202
207
* + (unsequenced edits' total length submitted before localSeq)
203
- * - (overlapping remove of the unsequenced edits' total length at refSeq)
208
+ * + (adjustments for changes double-counted by happening at or before both localSeq and refSeq)
204
209
*
205
210
* This algorithm scales roughly linearly with number of editing clients and the size of the collab window.
206
211
* (certain unlikely sequences of operations may introduce log factors on those variables)
207
212
*
208
- * Note: there is some slight complication with clientSeqNumbers resulting from the possibility of different clients
209
- * concurrently removing the same segment. See the field's documentation for more details.
213
+ * @privateRemarks
214
+ * If you are looking to understand this class in more detail, a suggested order of internalization is:
215
+ *
216
+ * 1. The above description and how it relates to the implementation of `getPartialLength` (which implements the above high-level description
217
+ * 2. `PartialSequenceLengthsSet`, which allows binary searching for overall length deltas at a given sequence number and handles updates.
218
+ * 3. The `fromLeaves` method, which is the base case for the [potential] recursion in `combine`
219
+ * 4. The logic in `combine` to aggregate smaller block entries into larger ones
220
+ * 5. The incremental code path of `update`
210
221
*/
211
222
export class PartialSequenceLengths {
212
223
public static options : PartialSequenceLengthsOptions = {
@@ -275,6 +286,18 @@ export class PartialSequenceLengths {
275
286
* Contains information required to answer queries for the length of this segment from the perspective of
276
287
* the local client but not including all local segments (i.e., `localSeq !== collabWindow.localSeq`).
277
288
* This field is only computed if requested in the constructor (i.e. `computeLocalPartials === true`).
289
+ *
290
+ * Note that the usage pattern for this list is a bit different from perClientAdjustments: when dealing with perspectives of remote clients,
291
+ * we generally want to know what their view of the block was accounting for all changes made by that client as well as all \<= some refSeq.
292
+ *
293
+ * However, when dealing with perspectives relevant to the local client, we are still interested in changes made \<= some refSeq, but instead
294
+ * of caring about all changes made by the local client, we additionally want the subset of them that were made \<= some localSeq.
295
+ *
296
+ * The PartialSequenceLengthsSets stored in this field therefore track localSeqs rather than seqs (it's still named seq for ease of implementation).
297
+ * Furthermore, when computing the length of the block at a given refSeq/localSeq perspective,
298
+ * rather than add something like `perClientAdjustments[clientId].latestLeq(latestSeq) - perClientAdjustments[clientId].latestLeq(refSeq)` [to
299
+ * get the tail end of adjustments necessary for a remote client client], we instead add `unsequencedRecords.partialLengths.latestLeq(localSeq)`
300
+ * [to get the head end of adjustments necessary for the local client].
278
301
*/
279
302
private unsequencedRecords : UnsequencedPartialLengthInfo | undefined ;
280
303
@@ -290,7 +313,6 @@ export class PartialSequenceLengths {
290
313
this . unsequencedRecords = {
291
314
partialLengths : new PartialSequenceLengthsSet ( ) ,
292
315
perRefSeqAdjustments : new Map ( ) ,
293
- // overlappingRemoves: [],
294
316
cachedAdjustmentByRefSeq : new Map ( ) ,
295
317
} ;
296
318
}
@@ -407,7 +429,11 @@ export class PartialSequenceLengths {
407
429
}
408
430
409
431
for ( const partial of perClientAdjustments [ clientId ] . items ) {
410
- combinedPartialLengths . addClientSeqNumber ( clientId , partial . seq , partial . seglen ) ;
432
+ combinedPartialLengths . addClientAdjustment (
433
+ clientId ,
434
+ partial . seq ,
435
+ partial . seglen ,
436
+ ) ;
411
437
}
412
438
}
413
439
}
@@ -467,6 +493,11 @@ export class PartialSequenceLengths {
467
493
return combinedPartialLengths ;
468
494
}
469
495
496
+ /**
497
+ * Assuming this segment was moved on insertion, inserts length information about that operation
498
+ * into the appropriate per-client adjustments (the overall view needs no such adjustment since
499
+ * from an observing client's perspective, the segment never exists).
500
+ */
470
501
private static accountForMoveOnInsert (
471
502
combinedPartialLengths : PartialSequenceLengths ,
472
503
segment : ISegmentPrivate ,
@@ -525,27 +556,14 @@ export class PartialSequenceLengths {
525
556
if ( ! wasRemovedByInsertingClient && ! wasMovedByInsertingClient ) {
526
557
const moveSeq = moveInfo ?. movedSeq ;
527
558
assert ( moveSeq !== undefined , "ObliterateOnInsertion implies moveSeq is defined" ) ;
528
- combinedPartialLengths . addClientSeqNumber ( clientId , moveSeq , segment . cachedLength ) ;
559
+ combinedPartialLengths . addClientAdjustment ( clientId , moveSeq , segment . cachedLength ) ;
529
560
}
530
561
}
531
562
}
532
563
533
564
/**
534
565
* Inserts length information about the insertion of `segment` into
535
- * `combinedPartialLengths.partialLengths`.
536
- *
537
- * Does not update the clientSeqNumbers field to account for this segment.
538
- *
539
- * If `removalInfo` or `moveInfo` are defined, this operation updates the
540
- * bookkeeping to account for the (re)moval of this segment at the (re)movedSeq
541
- * instead.
542
- *
543
- * When the insertion or (re)moval of the segment is un-acked and
544
- * `combinedPartialLengths` is meant to compute such records, this does the
545
- * analogous addition to the bookkeeping for the local segment in
546
- * `combinedPartialLengths.unsequencedRecords`.
547
- *
548
- * TODO: Update this comment
566
+ * `combinedPartialLengths.partialLengths` and the appropriate per-client adjustments.
549
567
*/
550
568
private static accountForInsertion (
551
569
combinedPartialLengths : PartialSequenceLengths ,
@@ -586,10 +604,14 @@ export class PartialSequenceLengths {
586
604
len : 0 ,
587
605
seglen : segmentLen ,
588
606
} ) ;
589
- combinedPartialLengths . addClientSeqNumber ( clientId , seqOrLocalSeq , segmentLen ) ;
607
+ combinedPartialLengths . addClientAdjustment ( clientId , seqOrLocalSeq , segmentLen ) ;
590
608
}
591
609
}
592
610
611
+ /**
612
+ * Inserts length information about the removal or obliteration of `segment` into
613
+ * `combinedPartialLengths.partialLengths` and the appropriate per-client adjustments.
614
+ */
593
615
private static accountForRemoval (
594
616
combinedPartialLengths : PartialSequenceLengths ,
595
617
segment : ISegmentPrivate ,
@@ -732,15 +754,15 @@ export class PartialSequenceLengths {
732
754
} else {
733
755
// Note that all clients that have a remove or obliterate operation on this segment
734
756
// use the seq of the winning move/obliterate in their per-client adjustments!
735
- combinedPartialLengths . addClientSeqNumber ( id , seqOrLocalSeq , lenDelta ) ;
757
+ combinedPartialLengths . addClientAdjustment ( id , seqOrLocalSeq , lenDelta ) ;
736
758
737
759
// Also ensure that all these clients have seen the segment as inserted before being removed
738
760
// This is technically not necessary for removes (we never ask for the length of this block with
739
761
// respect to a refSeq which this entry would affect), but it's simpler to just add it here.
740
762
// We already add this entry as part of the accountForInsertion codepath for the client that
741
763
// actually did insert the segment, hence not doing so [again] here.
742
764
if ( segment . seq > collabWindow . minSeq && id !== segment . clientId ) {
743
- combinedPartialLengths . addClientSeqNumber ( id , segment . seq , segment . cachedLength ) ;
765
+ combinedPartialLengths . addClientAdjustment ( id , segment . seq , segment . cachedLength ) ;
744
766
}
745
767
}
746
768
}
@@ -801,11 +823,11 @@ export class PartialSequenceLengths {
801
823
moveInfo . movedSeq < segment . seq &&
802
824
moveInfo . wasMovedOnInsert
803
825
) {
804
- this . addClientSeqNumber ( clientId , moveInfo . movedSeq , segment . cachedLength ) ;
826
+ this . addClientAdjustment ( clientId , moveInfo . movedSeq , segment . cachedLength ) ;
805
827
failIncrementalPropagation = true ;
806
828
} else {
807
829
seqSeglen += segment . cachedLength ;
808
- this . addClientSeqNumber ( clientId , seq , segment . cachedLength ) ;
830
+ this . addClientAdjustment ( clientId , seq , segment . cachedLength ) ;
809
831
}
810
832
}
811
833
@@ -816,9 +838,9 @@ export class PartialSequenceLengths {
816
838
if ( segment . seq !== UnassignedSequenceNumber && seq === earlierDeletion ) {
817
839
seqSeglen -= segment . cachedLength ;
818
840
if ( clientId !== collabWindow . clientId ) {
819
- this . addClientSeqNumber ( clientId , seq , - segment . cachedLength ) ;
841
+ this . addClientAdjustment ( clientId , seq , - segment . cachedLength ) ;
820
842
if ( segment . seq > collabWindow . minSeq && segment . clientId !== clientId ) {
821
- this . addClientSeqNumber ( clientId , segment . seq , segment . cachedLength ) ;
843
+ this . addClientAdjustment ( clientId , segment . seq , segment . cachedLength ) ;
822
844
failIncrementalPropagation = true ;
823
845
}
824
846
}
@@ -849,7 +871,7 @@ export class PartialSequenceLengths {
849
871
branchPartialLengths . perClientAdjustments . forEach ( ( clientAdjustments , id ) => {
850
872
const leqBranchPartial = clientAdjustments . latestLeq ( seq ) ;
851
873
if ( leqBranchPartial && leqBranchPartial . seq === seq ) {
852
- this . addClientSeqNumber ( id , seq , leqBranchPartial . seglen ) ;
874
+ this . addClientAdjustment ( id , seq , leqBranchPartial . seglen ) ;
853
875
}
854
876
} ) ;
855
877
}
@@ -914,24 +936,7 @@ export class PartialSequenceLengths {
914
936
}
915
937
916
938
/**
917
- * TODO: Adjust this comment
918
- * Computes the seglen for the double-counted removed overlap at (refSeq, localSeq). This logic is equivalent
919
- * to the following:
920
- *
921
- * ```typescript
922
- * let total = 0;
923
- * for (const partialLength of this.unsequencedRecords!.overlappingRemoves) {
924
- * if (partialLength.seq > refSeq) {
925
- * break;
926
- * }
927
- *
928
- * if (partialLength.localSeq <= localSeq) {
929
- * total += partialLength.seglen;
930
- * }
931
- * }
932
- *
933
- * return total;
934
- * ```
939
+ * Computes the seglen for the double-counted removed overlap at (refSeq, localSeq).
935
940
*
936
941
* Reconnect happens to only need to compute these lengths for two refSeq values: before and
937
942
* after the rebase. Since these lists potentially scale with O(collab window * number of local edits)
@@ -952,17 +957,17 @@ export class PartialSequenceLengths {
952
957
adjustments ,
953
958
] of this . unsequencedRecords . perRefSeqAdjustments . entries ( ) ) {
954
959
if ( seq > refSeq ) {
955
- // TODO: Prior code path got away with an early exit here by sorting the entries by seq .
956
- // You could consider doing the same to restore some perf .
960
+ // TODO: Prior code path got away with an early exit here by sorting the entries by refSeq .
961
+ // We could do the same here if we wanted .
957
962
// Old codepath basically flattened the 2d array into a 1d array with both dimensions listed.
958
963
continue ;
959
964
}
960
965
961
966
for ( const partial of adjustments . items ) {
967
+ // This coalesces entries with the same localSeq as well as computes overall lengths.
962
968
partials . addOrUpdate ( partial ) ;
963
969
}
964
970
}
965
- // This coalesces entries with the same localSeq as well as computes overall lengths.
966
971
cachedAdjustment = partials ;
967
972
this . unsequencedRecords . cachedAdjustmentByRefSeq . set ( refSeq , cachedAdjustment ) ;
968
973
}
@@ -1007,8 +1012,7 @@ export class PartialSequenceLengths {
1007
1012
}
1008
1013
}
1009
1014
1010
- // TODO: Rename this
1011
- private addClientSeqNumber ( clientId : number , seq : number , seglen : number ) : void {
1015
+ private addClientAdjustment ( clientId : number , seq : number , seglen : number ) : void {
1012
1016
this . perClientAdjustments [ clientId ] ??= new PartialSequenceLengthsSet ( ) ;
1013
1017
const cli = this . perClientAdjustments [ clientId ] ;
1014
1018
cli . addOrUpdate ( { seq, len : 0 , seglen } ) ;
0 commit comments