@fluidframework/merge-tree 0.53.0 → 0.54.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/client.d.ts +1 -1
- package/dist/mergeTree.d.ts +27 -33
- package/dist/mergeTree.d.ts.map +1 -1
- package/dist/mergeTree.js +61 -60
- package/dist/mergeTree.js.map +1 -1
- package/dist/partialLengths.js +2 -2
- package/dist/partialLengths.js.map +1 -1
- package/dist/textSegment.d.ts.map +1 -1
- package/dist/textSegment.js +11 -2
- package/dist/textSegment.js.map +1 -1
- package/lib/client.d.ts +1 -1
- package/lib/mergeTree.d.ts +27 -33
- package/lib/mergeTree.d.ts.map +1 -1
- package/lib/mergeTree.js +61 -60
- package/lib/mergeTree.js.map +1 -1
- package/lib/partialLengths.js +2 -2
- package/lib/partialLengths.js.map +1 -1
- package/lib/textSegment.d.ts.map +1 -1
- package/lib/textSegment.js +11 -2
- package/lib/textSegment.js.map +1 -1
- package/package.json +7 -7
- package/src/mergeTree.ts +240 -166
- package/src/partialLengths.ts +2 -2
- package/src/textSegment.ts +12 -2
package/src/mergeTree.ts
CHANGED
|
@@ -4,7 +4,9 @@
|
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
/* eslint-disable @typescript-eslint/no-non-null-assertion */
|
|
7
|
-
/* eslint-disable @typescript-eslint/consistent-type-assertions
|
|
7
|
+
/* eslint-disable @typescript-eslint/consistent-type-assertions */
|
|
8
|
+
/* eslint-disable @typescript-eslint/no-shadow */
|
|
9
|
+
/* eslint-disable no-bitwise */
|
|
8
10
|
|
|
9
11
|
import { assert, Trace } from "@fluidframework/common-utils";
|
|
10
12
|
import { IIntegerRange } from "./base";
|
|
@@ -165,14 +167,28 @@ export interface ISegmentChanges {
|
|
|
165
167
|
|
|
166
168
|
export interface BlockAction<TClientData> {
|
|
167
169
|
// eslint-disable-next-line @typescript-eslint/prefer-function-type
|
|
168
|
-
(
|
|
169
|
-
|
|
170
|
+
(
|
|
171
|
+
block: IMergeBlock,
|
|
172
|
+
pos: number,
|
|
173
|
+
refSeq: number,
|
|
174
|
+
clientId: number,
|
|
175
|
+
start: number | undefined,
|
|
176
|
+
end: number | undefined,
|
|
177
|
+
accum: TClientData,
|
|
178
|
+
): boolean;
|
|
170
179
|
}
|
|
171
180
|
|
|
172
181
|
export interface NodeAction<TClientData> {
|
|
173
182
|
// eslint-disable-next-line @typescript-eslint/prefer-function-type
|
|
174
|
-
(
|
|
175
|
-
|
|
183
|
+
(
|
|
184
|
+
node: IMergeNode,
|
|
185
|
+
pos: number,
|
|
186
|
+
refSeq: number,
|
|
187
|
+
clientId: number,
|
|
188
|
+
start: number | undefined,
|
|
189
|
+
end: number | undefined,
|
|
190
|
+
clientData: TClientData,
|
|
191
|
+
): boolean;
|
|
176
192
|
}
|
|
177
193
|
|
|
178
194
|
export interface IncrementalSegmentAction<TContext> {
|
|
@@ -374,20 +390,20 @@ export function ordinalToArray(ord: string) {
|
|
|
374
390
|
// `MaxNodesInBlock`. (i.e., `MaxNodesInBlock` contains 1 extra slot for temporary storage to
|
|
375
391
|
// facilitate splits.)
|
|
376
392
|
export const MaxNodesInBlock = 8;
|
|
393
|
+
const traceOrdinals = false;
|
|
377
394
|
|
|
378
395
|
export class MergeBlock extends MergeNode implements IMergeBlock {
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
constructor(public childCount: number) {
|
|
396
|
+
public children: IMergeNode[];
|
|
397
|
+
public constructor(public childCount: number) {
|
|
382
398
|
super();
|
|
383
399
|
this.children = new Array<IMergeNode>(MaxNodesInBlock);
|
|
384
400
|
}
|
|
385
401
|
|
|
386
|
-
hierBlock(): HierMergeBlock | undefined {
|
|
402
|
+
public hierBlock(): HierMergeBlock | undefined {
|
|
387
403
|
return undefined;
|
|
388
404
|
}
|
|
389
405
|
|
|
390
|
-
setOrdinal(child: IMergeNode, index: number) {
|
|
406
|
+
public setOrdinal(child: IMergeNode, index: number) {
|
|
391
407
|
let childCount = this.childCount;
|
|
392
408
|
if (childCount === 8) {
|
|
393
409
|
childCount = 7;
|
|
@@ -403,18 +419,23 @@ export class MergeBlock extends MergeNode implements IMergeBlock {
|
|
|
403
419
|
localOrdinal = prevOrdCode + ordinalWidth;
|
|
404
420
|
}
|
|
405
421
|
child.ordinal = this.ordinal + String.fromCharCode(localOrdinal);
|
|
406
|
-
if (
|
|
422
|
+
if (traceOrdinals) {
|
|
423
|
+
// eslint-disable-next-line max-len
|
|
407
424
|
console.log(`so: prnt chld prev ${ordinalToArray(this.ordinal)} ${ordinalToArray(child.ordinal)} ${(index > 0) ? ordinalToArray(this.children[index - 1].ordinal) : "NA"}`);
|
|
408
425
|
}
|
|
409
426
|
assert(child.ordinal.length === (this.ordinal.length + 1), 0x041 /* "Unexpected child ordinal length!" */);
|
|
410
427
|
if (index > 0) {
|
|
411
|
-
assert(
|
|
428
|
+
assert(
|
|
429
|
+
child.ordinal > this.children[index - 1].ordinal,
|
|
430
|
+
0x042, /* "Child ordinal <= previous sibling ordinal!" */
|
|
431
|
+
);
|
|
432
|
+
// eslint-disable-next-line max-len
|
|
412
433
|
// console.log(`${ordinalToArray(this.ordinal)} ${ordinalToArray(child.ordinal)} ${ordinalToArray(this.children[index - 1].ordinal)}`);
|
|
413
434
|
// console.log(`ord width ${ordinalWidth}`);
|
|
414
435
|
}
|
|
415
436
|
}
|
|
416
437
|
|
|
417
|
-
assignChild(child: IMergeNode, index: number, updateOrdinal = true) {
|
|
438
|
+
public assignChild(child: IMergeNode, index: number, updateOrdinal = true) {
|
|
418
439
|
child.parent = this;
|
|
419
440
|
child.index = index;
|
|
420
441
|
if (updateOrdinal) {
|
|
@@ -425,9 +446,9 @@ export class MergeBlock extends MergeNode implements IMergeBlock {
|
|
|
425
446
|
}
|
|
426
447
|
|
|
427
448
|
class HierMergeBlock extends MergeBlock implements IMergeBlock {
|
|
428
|
-
rightmostTiles: MapLike<ReferencePosition>;
|
|
429
|
-
leftmostTiles: MapLike<ReferencePosition>;
|
|
430
|
-
rangeStacks: MapLike<Stack<ReferencePosition>>;
|
|
449
|
+
public rightmostTiles: MapLike<ReferencePosition>;
|
|
450
|
+
public leftmostTiles: MapLike<ReferencePosition>;
|
|
451
|
+
public rangeStacks: MapLike<Stack<ReferencePosition>>;
|
|
431
452
|
|
|
432
453
|
constructor(childCount: number) {
|
|
433
454
|
super(childCount);
|
|
@@ -436,16 +457,16 @@ class HierMergeBlock extends MergeBlock implements IMergeBlock {
|
|
|
436
457
|
this.rangeStacks = createMap<Stack<ReferencePosition>>();
|
|
437
458
|
}
|
|
438
459
|
|
|
439
|
-
addNodeReferences(mergeTree: MergeTree, node: IMergeNode) {
|
|
460
|
+
public addNodeReferences(mergeTree: MergeTree, node: IMergeNode) {
|
|
440
461
|
addNodeReferences(mergeTree, node, this.rightmostTiles, this.leftmostTiles,
|
|
441
462
|
this.rangeStacks);
|
|
442
463
|
}
|
|
443
464
|
|
|
444
|
-
hierBlock() {
|
|
465
|
+
public hierBlock() {
|
|
445
466
|
return this;
|
|
446
467
|
}
|
|
447
468
|
|
|
448
|
-
hierToString(indentCount: number) {
|
|
469
|
+
public hierToString(indentCount: number) {
|
|
449
470
|
let strbuf = "";
|
|
450
471
|
// eslint-disable-next-line guard-for-in, no-restricted-syntax
|
|
451
472
|
for (const key in this.rangeStacks) {
|
|
@@ -469,42 +490,45 @@ function nodeTotalLength(mergeTree: MergeTree, node: IMergeNode) {
|
|
|
469
490
|
}
|
|
470
491
|
|
|
471
492
|
export abstract class BaseSegment extends MergeNode implements ISegment {
|
|
472
|
-
constructor() {
|
|
473
|
-
super();
|
|
474
|
-
}
|
|
475
493
|
public clientId: number = LocalClientId;
|
|
476
494
|
public seq: number = UniversalSequenceNumber;
|
|
477
|
-
removedSeq?: number;
|
|
478
|
-
removedClientId?: number;
|
|
479
|
-
removedClientOverlap?: number[];
|
|
480
|
-
readonly segmentGroups: SegmentGroupCollection = new SegmentGroupCollection(this);
|
|
481
|
-
readonly trackingCollection: TrackingGroupCollection = new TrackingGroupCollection(this);
|
|
482
|
-
propertyManager?: PropertiesManager;
|
|
483
|
-
properties?: PropertySet;
|
|
484
|
-
localRefs?: LocalReferenceCollection;
|
|
485
|
-
abstract readonly type: string;
|
|
486
|
-
localSeq?: number;
|
|
487
|
-
localRemovedSeq?: number;
|
|
488
|
-
|
|
489
|
-
addProperties(newProps: PropertySet, op?: ICombiningOp, seq?: number, collabWindow?: CollaborationWindow) {
|
|
495
|
+
public removedSeq?: number;
|
|
496
|
+
public removedClientId?: number;
|
|
497
|
+
public removedClientOverlap?: number[];
|
|
498
|
+
public readonly segmentGroups: SegmentGroupCollection = new SegmentGroupCollection(this);
|
|
499
|
+
public readonly trackingCollection: TrackingGroupCollection = new TrackingGroupCollection(this);
|
|
500
|
+
public propertyManager?: PropertiesManager;
|
|
501
|
+
public properties?: PropertySet;
|
|
502
|
+
public localRefs?: LocalReferenceCollection;
|
|
503
|
+
public abstract readonly type: string;
|
|
504
|
+
public localSeq?: number;
|
|
505
|
+
public localRemovedSeq?: number;
|
|
506
|
+
|
|
507
|
+
public addProperties(newProps: PropertySet, op?: ICombiningOp, seq?: number, collabWindow?: CollaborationWindow) {
|
|
490
508
|
if (!this.propertyManager) {
|
|
491
509
|
this.propertyManager = new PropertiesManager();
|
|
492
510
|
}
|
|
493
511
|
if (!this.properties) {
|
|
494
512
|
this.properties = createMap<any>();
|
|
495
513
|
}
|
|
496
|
-
return this.propertyManager.addProperties(
|
|
514
|
+
return this.propertyManager.addProperties(
|
|
515
|
+
this.properties,
|
|
516
|
+
newProps,
|
|
517
|
+
op,
|
|
518
|
+
seq,
|
|
519
|
+
collabWindow && collabWindow.collaborating,
|
|
520
|
+
);
|
|
497
521
|
}
|
|
498
522
|
|
|
499
|
-
hasProperty(key: string): boolean {
|
|
523
|
+
public hasProperty(key: string): boolean {
|
|
500
524
|
return !!this.properties && (this.properties[key] !== undefined);
|
|
501
525
|
}
|
|
502
526
|
|
|
503
|
-
isLeaf() {
|
|
527
|
+
public isLeaf() {
|
|
504
528
|
return true;
|
|
505
529
|
}
|
|
506
530
|
|
|
507
|
-
cloneInto(b: ISegment) {
|
|
531
|
+
protected cloneInto(b: ISegment) {
|
|
508
532
|
b.clientId = this.clientId;
|
|
509
533
|
// TODO: deep clone properties
|
|
510
534
|
b.properties = clone(this.properties);
|
|
@@ -514,17 +538,17 @@ export abstract class BaseSegment extends MergeNode implements ISegment {
|
|
|
514
538
|
b.seq = this.seq;
|
|
515
539
|
}
|
|
516
540
|
|
|
517
|
-
canAppend(segment: ISegment): boolean {
|
|
541
|
+
public canAppend(segment: ISegment): boolean {
|
|
518
542
|
return false;
|
|
519
543
|
}
|
|
520
544
|
|
|
521
|
-
addSerializedProps(jseg: IJSONSegment) {
|
|
545
|
+
protected addSerializedProps(jseg: IJSONSegment) {
|
|
522
546
|
if (this.properties) {
|
|
523
547
|
jseg.props = this.properties;
|
|
524
548
|
}
|
|
525
549
|
}
|
|
526
550
|
|
|
527
|
-
abstract toJSONObject(): any;
|
|
551
|
+
public abstract toJSONObject(): any;
|
|
528
552
|
|
|
529
553
|
public ack(segmentGroup: SegmentGroup, opArgs: IMergeTreeDeltaOpArgs, mergeTree: MergeTree): boolean {
|
|
530
554
|
const currentSegmentGroup = this.segmentGroups.dequeue();
|
|
@@ -542,7 +566,8 @@ export abstract class BaseSegment extends MergeNode implements ISegment {
|
|
|
542
566
|
return true;
|
|
543
567
|
|
|
544
568
|
case MergeTreeDeltaType.REMOVE:
|
|
545
|
-
|
|
569
|
+
// eslint-disable-next-line @typescript-eslint/no-this-alias
|
|
570
|
+
const removalInfo: IRemovalInfo = this;
|
|
546
571
|
assert(!!removalInfo, 0x046 /* "On remove ack, missing removal info!" */);
|
|
547
572
|
assert(!!removalInfo.removedSeq, 0x047 /* "On remove ack, missing removed sequence number!" */);
|
|
548
573
|
this.localRemovedSeq = undefined;
|
|
@@ -597,13 +622,17 @@ export abstract class BaseSegment extends MergeNode implements ISegment {
|
|
|
597
622
|
if (this.propertyManager) {
|
|
598
623
|
if (this.properties) {
|
|
599
624
|
other.propertyManager = new PropertiesManager();
|
|
600
|
-
other.properties = this.propertyManager.copyTo(
|
|
625
|
+
other.properties = this.propertyManager.copyTo(
|
|
626
|
+
this.properties,
|
|
627
|
+
other.properties,
|
|
628
|
+
other.propertyManager,
|
|
629
|
+
);
|
|
601
630
|
}
|
|
602
631
|
}
|
|
603
632
|
}
|
|
604
633
|
|
|
605
|
-
abstract clone(): ISegment;
|
|
606
|
-
abstract append(segment: ISegment): void;
|
|
634
|
+
public abstract clone(): ISegment;
|
|
635
|
+
public abstract append(segment: ISegment): void;
|
|
607
636
|
protected abstract createSplitSegmentAt(pos: number): BaseSegment | undefined;
|
|
608
637
|
}
|
|
609
638
|
|
|
@@ -612,11 +641,13 @@ export const reservedRangeLabelsKey = "referenceRangeLabels";
|
|
|
612
641
|
export const reservedMarkerIdKey = "markerId";
|
|
613
642
|
export const reservedMarkerSimpleTypeKey = "markerSimpleType";
|
|
614
643
|
|
|
615
|
-
export const refGetTileLabels = (refPos: ReferencePosition) =>
|
|
616
|
-
refPos.
|
|
644
|
+
export const refGetTileLabels = (refPos: ReferencePosition) =>
|
|
645
|
+
(refPos.refType & ReferenceType.Tile)
|
|
646
|
+
&& refPos.properties ? refPos.properties[reservedTileLabelsKey] as string[] : undefined;
|
|
617
647
|
|
|
618
|
-
export const refGetRangeLabels = (refPos: ReferencePosition) =>
|
|
619
|
-
refPos.
|
|
648
|
+
export const refGetRangeLabels = (refPos: ReferencePosition) =>
|
|
649
|
+
(refPos.refType & (ReferenceType.NestBegin | ReferenceType.NestEnd))
|
|
650
|
+
&& refPos.properties ? refPos.properties[reservedRangeLabelsKey] as string[] : undefined;
|
|
620
651
|
|
|
621
652
|
export function refHasTileLabel(refPos: ReferencePosition, label: string) {
|
|
622
653
|
const tileLabels = refPos.getTileLabels();
|
|
@@ -1025,63 +1056,51 @@ export type LocalReferenceMapper = (id: string) => LocalReference;
|
|
|
1025
1056
|
|
|
1026
1057
|
// Represents a sequence of text segments
|
|
1027
1058
|
export class MergeTree {
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
// The bigger it is, the more expensive it is to break segment into sub-segments (on edits)
|
|
1031
|
-
// The smaller it is, the more segments we have in snapshots (and in memory) - it's more expensive to load snapshots.
|
|
1032
|
-
// Small number also makes ReplayTool produce false positives ("same" snapshots have slightly different binary representations).
|
|
1033
|
-
// More measurements needs to be done, but it's very likely the right spot is somewhere between 1K-2K mark.
|
|
1034
|
-
// That said, we also break segments on newline and there are very few segments that are longer than 256 because of it.
|
|
1035
|
-
// must be an even number
|
|
1036
|
-
static TextSegmentGranularity = 256;
|
|
1037
|
-
|
|
1038
|
-
static zamboniSegmentsMaxCount = 2;
|
|
1039
|
-
static options = {
|
|
1059
|
+
private static readonly zamboniSegmentsMaxCount = 2;
|
|
1060
|
+
public static readonly options = {
|
|
1040
1061
|
incrementalUpdate: true,
|
|
1041
1062
|
insertAfterRemovedSegs: true,
|
|
1042
1063
|
measureOrdinalTime: true,
|
|
1043
1064
|
measureWindowTime: true,
|
|
1044
1065
|
zamboniSegments: true,
|
|
1045
1066
|
};
|
|
1046
|
-
static traceAppend = false;
|
|
1047
|
-
static traceZRemove = false;
|
|
1048
|
-
static traceOrdinals = false;
|
|
1049
|
-
static traceGatherText = false;
|
|
1050
|
-
static diagInsertTie = false;
|
|
1051
|
-
static
|
|
1052
|
-
static
|
|
1053
|
-
static
|
|
1054
|
-
static
|
|
1055
|
-
static
|
|
1056
|
-
static theUnfinishedNode = <IMergeBlock>{ childCount: -1 };
|
|
1067
|
+
private static readonly traceAppend = false;
|
|
1068
|
+
private static readonly traceZRemove = false;
|
|
1069
|
+
private static readonly traceOrdinals = false;
|
|
1070
|
+
public static readonly traceGatherText = false;
|
|
1071
|
+
private static readonly diagInsertTie = false;
|
|
1072
|
+
public static readonly diagOverlappingRemove = false;
|
|
1073
|
+
private static readonly traceTraversal = false;
|
|
1074
|
+
private static readonly traceIncrTraversal = false;
|
|
1075
|
+
private static readonly initBlockUpdateActions: BlockUpdateActions;
|
|
1076
|
+
private static readonly theUnfinishedNode = <IMergeBlock>{ childCount: -1 };
|
|
1057
1077
|
// WARNING:
|
|
1058
1078
|
// Setting blockUpdateMarkers to false will result in eventual consistency issues
|
|
1059
1079
|
// for property updates on markers when loading from snapshots
|
|
1060
|
-
static readonly blockUpdateMarkers = true;
|
|
1080
|
+
private static readonly blockUpdateMarkers = true;
|
|
1061
1081
|
|
|
1062
|
-
windowTime = 0;
|
|
1063
|
-
packTime = 0;
|
|
1064
|
-
ordTime = 0;
|
|
1065
|
-
maxOrdTime = 0;
|
|
1082
|
+
private windowTime = 0;
|
|
1083
|
+
private packTime = 0;
|
|
1084
|
+
private ordTime = 0;
|
|
1085
|
+
private maxOrdTime = 0;
|
|
1066
1086
|
|
|
1067
1087
|
root: IMergeBlock;
|
|
1068
|
-
blockUpdateActions: BlockUpdateActions;
|
|
1069
|
-
collabWindow = new CollaborationWindow();
|
|
1070
|
-
pendingSegments: List<SegmentGroup> | undefined;
|
|
1071
|
-
segmentsToScour: Heap<LRUSegment> | undefined;
|
|
1072
|
-
// TODO:
|
|
1088
|
+
private readonly blockUpdateActions: BlockUpdateActions = MergeTree.initBlockUpdateActions;
|
|
1089
|
+
public readonly collabWindow = new CollaborationWindow();
|
|
1090
|
+
public pendingSegments: List<SegmentGroup> | undefined;
|
|
1091
|
+
private segmentsToScour: Heap<LRUSegment> | undefined;
|
|
1092
|
+
// TODO: add remove on segment remove
|
|
1073
1093
|
// for now assume only markers have ids and so point directly at the Segment
|
|
1074
1094
|
// if we need to have pointers to non-markers, we can change to point at local refs
|
|
1075
|
-
idToSegment =
|
|
1076
|
-
minSeqListeners: Heap<MinListener> | undefined;
|
|
1095
|
+
private readonly idToSegment = new Map<string, ISegment>();
|
|
1096
|
+
private minSeqListeners: Heap<MinListener> | undefined;
|
|
1077
1097
|
// For diagnostics
|
|
1078
|
-
getLongClientId?: (id: number) => string;
|
|
1079
|
-
mergeTreeDeltaCallback?: MergeTreeDeltaCallback;
|
|
1080
|
-
mergeTreeMaintenanceCallback?: MergeTreeMaintenanceCallback;
|
|
1098
|
+
public getLongClientId?: (id: number) => string;
|
|
1099
|
+
public mergeTreeDeltaCallback?: MergeTreeDeltaCallback;
|
|
1100
|
+
public mergeTreeMaintenanceCallback?: MergeTreeMaintenanceCallback;
|
|
1081
1101
|
|
|
1082
1102
|
// TODO: make and use interface describing options
|
|
1083
|
-
constructor(public options?: PropertySet) {
|
|
1084
|
-
this.blockUpdateActions = MergeTree.initBlockUpdateActions;
|
|
1103
|
+
public constructor(public options?: PropertySet) {
|
|
1085
1104
|
this.root = this.makeBlock(0);
|
|
1086
1105
|
}
|
|
1087
1106
|
|
|
@@ -1096,13 +1115,13 @@ export class MergeTree {
|
|
|
1096
1115
|
return block;
|
|
1097
1116
|
}
|
|
1098
1117
|
|
|
1099
|
-
clone() {
|
|
1118
|
+
public clone() {
|
|
1100
1119
|
const b = new MergeTree(this.options);
|
|
1101
1120
|
// For now assume that b will not collaborate
|
|
1102
1121
|
b.root = b.blockClone(this.root);
|
|
1103
1122
|
}
|
|
1104
1123
|
|
|
1105
|
-
blockClone(block: IMergeBlock, segments?: ISegment[]) {
|
|
1124
|
+
public blockClone(block: IMergeBlock, segments?: ISegment[]) {
|
|
1106
1125
|
const bBlock = this.makeBlock(block.childCount);
|
|
1107
1126
|
for (let i = 0; i < block.childCount; i++) {
|
|
1108
1127
|
const child = block.children[i];
|
|
@@ -1126,8 +1145,8 @@ export class MergeTree {
|
|
|
1126
1145
|
return b;
|
|
1127
1146
|
}
|
|
1128
1147
|
|
|
1129
|
-
localNetLength(segment: ISegment) {
|
|
1130
|
-
const removalInfo =
|
|
1148
|
+
public localNetLength(segment: ISegment) {
|
|
1149
|
+
const removalInfo: IRemovalInfo = segment;
|
|
1131
1150
|
if (removalInfo.removedSeq !== undefined) {
|
|
1132
1151
|
return 0;
|
|
1133
1152
|
} else {
|
|
@@ -1136,8 +1155,8 @@ export class MergeTree {
|
|
|
1136
1155
|
}
|
|
1137
1156
|
|
|
1138
1157
|
// TODO: remove id when segment removed
|
|
1139
|
-
mapIdToSegment(id: string, segment: ISegment) {
|
|
1140
|
-
this.idToSegment
|
|
1158
|
+
public mapIdToSegment(id: string, segment: ISegment) {
|
|
1159
|
+
this.idToSegment.set(id, segment);
|
|
1141
1160
|
}
|
|
1142
1161
|
|
|
1143
1162
|
private addNode(block: IMergeBlock, node: IMergeNode) {
|
|
@@ -1146,7 +1165,8 @@ export class MergeTree {
|
|
|
1146
1165
|
return index;
|
|
1147
1166
|
}
|
|
1148
1167
|
|
|
1149
|
-
|
|
1168
|
+
/* eslint-disable max-len */
|
|
1169
|
+
public reloadFromSegments(segments: ISegment[]) {
|
|
1150
1170
|
// This code assumes that a later call to `startCollaboration()` will initialize partial lengths.
|
|
1151
1171
|
assert(!this.collabWindow.collaborating, 0x049 /* "Trying to reload from segments while collaborating!" */);
|
|
1152
1172
|
|
|
@@ -1202,9 +1222,10 @@ export class MergeTree {
|
|
|
1202
1222
|
console.log(`reload time ${elapsedMicroseconds(clockStart)}`);
|
|
1203
1223
|
}
|
|
1204
1224
|
}
|
|
1225
|
+
/* eslint-enable max-len */
|
|
1205
1226
|
|
|
1206
1227
|
// For now assume min starts at zero
|
|
1207
|
-
startCollaboration(localClientId: number, minSeq: number, currentSeq: number) {
|
|
1228
|
+
public startCollaboration(localClientId: number, minSeq: number, currentSeq: number) {
|
|
1208
1229
|
this.collabWindow.clientId = localClientId;
|
|
1209
1230
|
this.collabWindow.minSeq = minSeq;
|
|
1210
1231
|
this.collabWindow.collaborating = true;
|
|
@@ -1252,11 +1273,11 @@ export class MergeTree {
|
|
|
1252
1273
|
holdNodes.push(segment);
|
|
1253
1274
|
} else {
|
|
1254
1275
|
if (MergeTree.traceZRemove) {
|
|
1255
|
-
// eslint-disable-next-line @typescript-eslint/dot-notation
|
|
1276
|
+
// eslint-disable-next-line @typescript-eslint/dot-notation, max-len
|
|
1256
1277
|
console.log(`${this.getLongClientId!(this.collabWindow.clientId)}: Zremove ${segment["text"]}; cli ${this.getLongClientId!(segment.clientId)}`);
|
|
1257
1278
|
}
|
|
1258
1279
|
|
|
1259
|
-
// Notify maintenance event observers that the segment is being unlinked from the MergeTree
|
|
1280
|
+
// Notify maintenance event observers that the segment is being unlinked from the MergeTree
|
|
1260
1281
|
if (this.mergeTreeMaintenanceCallback) {
|
|
1261
1282
|
this.mergeTreeMaintenanceCallback({
|
|
1262
1283
|
operation: MergeTreeMaintenanceType.UNLINK,
|
|
@@ -1278,7 +1299,7 @@ export class MergeTree {
|
|
|
1278
1299
|
|
|
1279
1300
|
if (canAppend) {
|
|
1280
1301
|
if (MergeTree.traceAppend) {
|
|
1281
|
-
// eslint-disable-next-line @typescript-eslint/dot-notation
|
|
1302
|
+
// eslint-disable-next-line @typescript-eslint/dot-notation, max-len
|
|
1282
1303
|
console.log(`${this.getLongClientId!(this.collabWindow.clientId)}: append ${prevSegment!["text"]} + ${segment["text"]}; cli ${this.getLongClientId!(prevSegment!.clientId)} + cli ${this.getLongClientId!(segment.clientId)}`);
|
|
1283
1304
|
}
|
|
1284
1305
|
prevSegment!.append(segment);
|
|
@@ -1427,13 +1448,20 @@ export class MergeTree {
|
|
|
1427
1448
|
}
|
|
1428
1449
|
}
|
|
1429
1450
|
|
|
1430
|
-
getCollabWindow() {
|
|
1451
|
+
public getCollabWindow() {
|
|
1431
1452
|
return this.collabWindow;
|
|
1432
1453
|
}
|
|
1433
1454
|
|
|
1434
|
-
getStats() {
|
|
1455
|
+
public getStats() {
|
|
1435
1456
|
const nodeGetStats = (block: IMergeBlock): MergeTreeStats => {
|
|
1436
|
-
const stats: MergeTreeStats = {
|
|
1457
|
+
const stats: MergeTreeStats = {
|
|
1458
|
+
maxHeight: 0,
|
|
1459
|
+
nodeCount: 0,
|
|
1460
|
+
leafCount: 0,
|
|
1461
|
+
removedLeafCount: 0,
|
|
1462
|
+
liveCount: 0,
|
|
1463
|
+
histo: [],
|
|
1464
|
+
};
|
|
1437
1465
|
for (let k = 0; k < MaxNodesInBlock; k++) {
|
|
1438
1466
|
stats.histo[k] = 0;
|
|
1439
1467
|
}
|
|
@@ -1476,11 +1504,11 @@ export class MergeTree {
|
|
|
1476
1504
|
return rootStats;
|
|
1477
1505
|
}
|
|
1478
1506
|
|
|
1479
|
-
findHistorialPosition(pos: number, fromSeq: number, toSeq: number, clientId: number) {
|
|
1507
|
+
public findHistorialPosition(pos: number, fromSeq: number, toSeq: number, clientId: number) {
|
|
1480
1508
|
return this.findHistorialPositionFromClient(pos, fromSeq, toSeq, clientId);
|
|
1481
1509
|
}
|
|
1482
1510
|
|
|
1483
|
-
findHistorialPositionFromClient(pos: number, fromSeq: number, toSeq: number, clientId: number) {
|
|
1511
|
+
private findHistorialPositionFromClient(pos: number, fromSeq: number, toSeq: number, clientId: number) {
|
|
1484
1512
|
assert(fromSeq < toSeq, 0x04a /* "Invalid range for historical position search!" */);
|
|
1485
1513
|
if (pos < this.getLength(fromSeq, clientId)) {
|
|
1486
1514
|
assert(toSeq <= this.collabWindow.currentSeq,
|
|
@@ -1498,7 +1526,13 @@ export class MergeTree {
|
|
|
1498
1526
|
}
|
|
1499
1527
|
}
|
|
1500
1528
|
|
|
1501
|
-
findHistorialRangeFromClient(
|
|
1529
|
+
public findHistorialRangeFromClient(
|
|
1530
|
+
rangeStart: number,
|
|
1531
|
+
rangeEnd: number,
|
|
1532
|
+
fromSeq: number,
|
|
1533
|
+
toSeq: number,
|
|
1534
|
+
clientId: number,
|
|
1535
|
+
) {
|
|
1502
1536
|
const ranges: IIntegerRange[] = [];
|
|
1503
1537
|
const recordRange = (
|
|
1504
1538
|
segment: ISegment,
|
|
@@ -1525,11 +1559,11 @@ export class MergeTree {
|
|
|
1525
1559
|
return ranges;
|
|
1526
1560
|
}
|
|
1527
1561
|
|
|
1528
|
-
findHistorialRange(rangeStart: number, rangeEnd: number, fromSeq: number, toSeq: number, clientId: number) {
|
|
1562
|
+
public findHistorialRange(rangeStart: number, rangeEnd: number, fromSeq: number, toSeq: number, clientId: number) {
|
|
1529
1563
|
return this.findHistorialRangeFromClient(rangeStart, rangeEnd, fromSeq, toSeq, clientId);
|
|
1530
1564
|
}
|
|
1531
1565
|
|
|
1532
|
-
getLength(refSeq: number, clientId: number) {
|
|
1566
|
+
public getLength(refSeq: number, clientId: number) {
|
|
1533
1567
|
return this.blockLength(this.root, refSeq, clientId);
|
|
1534
1568
|
}
|
|
1535
1569
|
|
|
@@ -1538,7 +1572,7 @@ export class MergeTree {
|
|
|
1538
1572
|
*/
|
|
1539
1573
|
public get length() { return this.root.cachedLength; }
|
|
1540
1574
|
|
|
1541
|
-
getPosition(node: MergeNode, refSeq: number, clientId: number) {
|
|
1575
|
+
public getPosition(node: MergeNode, refSeq: number, clientId: number) {
|
|
1542
1576
|
let totalOffset = 0;
|
|
1543
1577
|
let parent = node.parent;
|
|
1544
1578
|
let prevParent: IMergeBlock | undefined;
|
|
@@ -1557,26 +1591,7 @@ export class MergeTree {
|
|
|
1557
1591
|
return totalOffset;
|
|
1558
1592
|
}
|
|
1559
1593
|
|
|
1560
|
-
|
|
1561
|
-
let _end = end;
|
|
1562
|
-
const gatherSegment = (
|
|
1563
|
-
segment: ISegment, pos: number, refSeq: number, clientId: number, start: number,
|
|
1564
|
-
end: number, accumSegments: SegmentAccumulator) => {
|
|
1565
|
-
accumSegments.segments.push(segment.clone());
|
|
1566
|
-
return true;
|
|
1567
|
-
};
|
|
1568
|
-
|
|
1569
|
-
if (_end === undefined) {
|
|
1570
|
-
_end = this.blockLength(this.root, refSeq, clientId);
|
|
1571
|
-
}
|
|
1572
|
-
const accum: SegmentAccumulator = {
|
|
1573
|
-
segments: [],
|
|
1574
|
-
};
|
|
1575
|
-
this.mapRange<SegmentAccumulator>({ leaf: gatherSegment }, refSeq, clientId, accum, start, _end);
|
|
1576
|
-
return accum.segments;
|
|
1577
|
-
}
|
|
1578
|
-
|
|
1579
|
-
getContainingSegment<T extends ISegment>(pos: number, refSeq: number, clientId: number) {
|
|
1594
|
+
public getContainingSegment<T extends ISegment>(pos: number, refSeq: number, clientId: number) {
|
|
1580
1595
|
let segment: T | undefined;
|
|
1581
1596
|
let offset: number | undefined;
|
|
1582
1597
|
|
|
@@ -1597,10 +1612,6 @@ export class MergeTree {
|
|
|
1597
1612
|
}
|
|
1598
1613
|
}
|
|
1599
1614
|
|
|
1600
|
-
getRemovalInfo(segment: ISegment) {
|
|
1601
|
-
return segment as IRemovalInfo;
|
|
1602
|
-
}
|
|
1603
|
-
|
|
1604
1615
|
private nodeLength(node: IMergeNode, refSeq: number, clientId: number) {
|
|
1605
1616
|
if ((!this.collabWindow.collaborating) || (this.collabWindow.clientId === clientId)) {
|
|
1606
1617
|
// Local client sees all segments, even when collaborating
|
|
@@ -1615,7 +1626,7 @@ export class MergeTree {
|
|
|
1615
1626
|
return node.partialLengths!.getPartialLength(refSeq, clientId);
|
|
1616
1627
|
} else {
|
|
1617
1628
|
const segment = node;
|
|
1618
|
-
const removalInfo =
|
|
1629
|
+
const removalInfo: IRemovalInfo = segment;
|
|
1619
1630
|
|
|
1620
1631
|
if(removalInfo.removedSeq !== undefined
|
|
1621
1632
|
&& removalInfo.removedSeq !== UnassignedSequenceNumber
|
|
@@ -1629,8 +1640,11 @@ export class MergeTree {
|
|
|
1629
1640
|
((segment.seq !== UnassignedSequenceNumber) && (segment.seq! <= refSeq)))) {
|
|
1630
1641
|
// Segment happened by reference sequence number or segment from requesting client
|
|
1631
1642
|
if (removalInfo.removedSeq !== undefined) {
|
|
1632
|
-
if (
|
|
1633
|
-
|
|
1643
|
+
if (
|
|
1644
|
+
removalInfo.removedClientId === clientId
|
|
1645
|
+
|| (removalInfo.removedClientOverlap
|
|
1646
|
+
&& removalInfo.removedClientOverlap.includes(clientId))
|
|
1647
|
+
) {
|
|
1634
1648
|
return 0;
|
|
1635
1649
|
} else {
|
|
1636
1650
|
return segment.cachedLength;
|
|
@@ -1653,7 +1667,7 @@ export class MergeTree {
|
|
|
1653
1667
|
}
|
|
1654
1668
|
}
|
|
1655
1669
|
|
|
1656
|
-
addMinSeqListener(minRequired: number, onMinGE: (minSeq: number) => void) {
|
|
1670
|
+
public addMinSeqListener(minRequired: number, onMinGE: (minSeq: number) => void) {
|
|
1657
1671
|
if (!this.minSeqListeners) {
|
|
1658
1672
|
this.minSeqListeners = new Heap<MinListener>([],
|
|
1659
1673
|
minListenerComparer);
|
|
@@ -1671,8 +1685,11 @@ export class MergeTree {
|
|
|
1671
1685
|
}
|
|
1672
1686
|
}
|
|
1673
1687
|
|
|
1674
|
-
setMinSeq(minSeq: number) {
|
|
1675
|
-
assert(
|
|
1688
|
+
public setMinSeq(minSeq: number) {
|
|
1689
|
+
assert(
|
|
1690
|
+
minSeq <= this.collabWindow.currentSeq,
|
|
1691
|
+
0x04e, /* "Trying to set minSeq above currentSeq of collab window!" */
|
|
1692
|
+
);
|
|
1676
1693
|
|
|
1677
1694
|
// Only move forward
|
|
1678
1695
|
assert(this.collabWindow.minSeq <= minSeq, 0x04f /* "minSeq of collab window > target minSeq!" */);
|
|
@@ -1698,7 +1715,7 @@ export class MergeTree {
|
|
|
1698
1715
|
return LocalReference.DetachedPosition;
|
|
1699
1716
|
}
|
|
1700
1717
|
|
|
1701
|
-
getStackContext(startPos: number, clientId: number, rangeLabels: string[]) {
|
|
1718
|
+
public getStackContext(startPos: number, clientId: number, rangeLabels: string[]) {
|
|
1702
1719
|
const searchInfo: IMarkerSearchRangeInfo = {
|
|
1703
1720
|
mergeTree: this,
|
|
1704
1721
|
stacks: createMap<Stack<Marker>>(),
|
|
@@ -1711,7 +1728,7 @@ export class MergeTree {
|
|
|
1711
1728
|
}
|
|
1712
1729
|
|
|
1713
1730
|
// TODO: filter function
|
|
1714
|
-
findTile(startPos: number, clientId: number, tileLabel: string, posPrecedesTile = true) {
|
|
1731
|
+
public findTile(startPos: number, clientId: number, tileLabel: string, posPrecedesTile = true) {
|
|
1715
1732
|
const searchInfo: IReferenceSearchInfo = {
|
|
1716
1733
|
mergeTree: this,
|
|
1717
1734
|
posPrecedesTile,
|
|
@@ -1758,7 +1775,10 @@ export class MergeTree {
|
|
|
1758
1775
|
for (let childIndex = 0; childIndex < block.childCount; childIndex++) {
|
|
1759
1776
|
const child = children[childIndex];
|
|
1760
1777
|
const len = this.nodeLength(child, refSeq, clientId) ?? 0;
|
|
1761
|
-
if (
|
|
1778
|
+
if (
|
|
1779
|
+
(!contains && _pos < len)
|
|
1780
|
+
|| (contains && contains(child, _pos, refSeq, clientId, undefined, undefined, clientData))
|
|
1781
|
+
) {
|
|
1762
1782
|
// Found entry containing pos
|
|
1763
1783
|
if (!child.isLeaf()) {
|
|
1764
1784
|
return this.searchBlock(child, _pos, _segpos, refSeq, clientId, actions, clientData);
|
|
@@ -1842,7 +1862,7 @@ export class MergeTree {
|
|
|
1842
1862
|
* Assign sequence number to existing segment; update partial lengths to reflect the change
|
|
1843
1863
|
* @param seq - sequence number given by server to pending segment
|
|
1844
1864
|
*/
|
|
1845
|
-
ackPendingSegment(opArgs: IMergeTreeDeltaOpArgs, verboseOps = false) {
|
|
1865
|
+
public ackPendingSegment(opArgs: IMergeTreeDeltaOpArgs, verboseOps = false) {
|
|
1846
1866
|
const seq = opArgs.sequencedMessage!.sequenceNumber;
|
|
1847
1867
|
const pendingSegmentGroup = this.pendingSegments!.dequeue();
|
|
1848
1868
|
const nodesToUpdate: IMergeBlock[] = [];
|
|
@@ -1897,8 +1917,8 @@ export class MergeTree {
|
|
|
1897
1917
|
}
|
|
1898
1918
|
|
|
1899
1919
|
// TODO: error checking
|
|
1900
|
-
getMarkerFromId(id: string) {
|
|
1901
|
-
return this.idToSegment
|
|
1920
|
+
public getMarkerFromId(id: string) {
|
|
1921
|
+
return this.idToSegment.get(id);
|
|
1902
1922
|
}
|
|
1903
1923
|
|
|
1904
1924
|
/**
|
|
@@ -1908,7 +1928,7 @@ export class MergeTree {
|
|
|
1908
1928
|
* @param refseq - The reference sequence number at which to compute the position.
|
|
1909
1929
|
* @param clientId - The client id with which to compute the position.
|
|
1910
1930
|
*/
|
|
1911
|
-
posFromRelativePos(
|
|
1931
|
+
public posFromRelativePos(
|
|
1912
1932
|
relativePos: IRelativePosition,
|
|
1913
1933
|
refseq = this.collabWindow.currentSeq,
|
|
1914
1934
|
clientId = this.collabWindow.clientId) {
|
|
@@ -1972,7 +1992,11 @@ export class MergeTree {
|
|
|
1972
1992
|
}
|
|
1973
1993
|
}
|
|
1974
1994
|
|
|
1975
|
-
public insertAtReferencePosition(
|
|
1995
|
+
public insertAtReferencePosition(
|
|
1996
|
+
referencePosition: ReferencePosition,
|
|
1997
|
+
insertSegment: ISegment,
|
|
1998
|
+
opArgs: IMergeTreeDeltaOpArgs,
|
|
1999
|
+
): void {
|
|
1976
2000
|
if (insertSegment.cachedLength === 0) {
|
|
1977
2001
|
return;
|
|
1978
2002
|
}
|
|
@@ -2124,11 +2148,19 @@ export class MergeTree {
|
|
|
2124
2148
|
block.assignChild(child, childIndex, false);
|
|
2125
2149
|
}
|
|
2126
2150
|
|
|
2127
|
-
private blockInsert<T extends ISegment>(
|
|
2151
|
+
private blockInsert<T extends ISegment>(
|
|
2152
|
+
pos: number,
|
|
2153
|
+
refSeq: number,
|
|
2154
|
+
clientId: number,
|
|
2155
|
+
seq: number,
|
|
2156
|
+
localSeq: number | undefined,
|
|
2157
|
+
newSegments: T[],
|
|
2158
|
+
) {
|
|
2128
2159
|
let segIsLocal = false;
|
|
2129
2160
|
const checkSegmentIsLocal = (segment: ISegment, pos: number, refSeq: number, clientId: number) => {
|
|
2130
2161
|
if (segment.seq === UnassignedSequenceNumber) {
|
|
2131
2162
|
if (MergeTree.diagInsertTie) {
|
|
2163
|
+
// eslint-disable-next-line max-len
|
|
2132
2164
|
console.log(`@cli ${glc(this, this.collabWindow.clientId)}: promoting continue due to seq ${segment.seq} text ${segment.toString()} ref ${refSeq}`);
|
|
2133
2165
|
}
|
|
2134
2166
|
segIsLocal = true;
|
|
@@ -2141,6 +2173,7 @@ export class MergeTree {
|
|
|
2141
2173
|
segIsLocal = false;
|
|
2142
2174
|
this.rightExcursion(node, checkSegmentIsLocal);
|
|
2143
2175
|
if (MergeTree.diagInsertTie && segIsLocal) {
|
|
2176
|
+
// eslint-disable-next-line max-len
|
|
2144
2177
|
console.log(`@cli ${glc(this, this.collabWindow.clientId)}: attempting continue with seq ${seq} ref ${refSeq} `);
|
|
2145
2178
|
}
|
|
2146
2179
|
return segIsLocal;
|
|
@@ -2364,6 +2397,7 @@ export class MergeTree {
|
|
|
2364
2397
|
return undefined;
|
|
2365
2398
|
} else if (splitNode === MergeTree.theUnfinishedNode) {
|
|
2366
2399
|
if (MergeTree.traceTraversal) {
|
|
2400
|
+
// eslint-disable-next-line max-len
|
|
2367
2401
|
console.log(`@cli ${glc(this, this.collabWindow.clientId)} unfinished bus pos ${_pos} len ${len}`);
|
|
2368
2402
|
}
|
|
2369
2403
|
_pos -= len; // Act as if shifted segment
|
|
@@ -2404,6 +2438,7 @@ export class MergeTree {
|
|
|
2404
2438
|
}
|
|
2405
2439
|
if (MergeTree.traceTraversal) {
|
|
2406
2440
|
if ((!found) && (_pos > 0)) {
|
|
2441
|
+
// eslint-disable-next-line max-len
|
|
2407
2442
|
console.log(`inserting walk fell through pos ${_pos} len: ${this.blockLength(this.root, refSeq, clientId)}`);
|
|
2408
2443
|
}
|
|
2409
2444
|
}
|
|
@@ -2483,6 +2518,7 @@ export class MergeTree {
|
|
|
2483
2518
|
if (i > 0) {
|
|
2484
2519
|
if (child.ordinal <= block.children[i - 1].ordinal) {
|
|
2485
2520
|
console.log("node sib integrity issue");
|
|
2521
|
+
// eslint-disable-next-line max-len
|
|
2486
2522
|
console.log(`??: prnt chld prev ${ordinalToArray(block.ordinal)} ${ordinalToArray(child.ordinal)} ${(i > 0) ? ordinalToArray(block.children[i - 1].ordinal) : "NA"}`);
|
|
2487
2523
|
}
|
|
2488
2524
|
}
|
|
@@ -2541,7 +2577,7 @@ export class MergeTree {
|
|
|
2541
2577
|
* @param seq - The sequence number of the annotate operation
|
|
2542
2578
|
* @param opArgs - The op args for the annotate op. this is passed to the merge tree callback if there is one
|
|
2543
2579
|
*/
|
|
2544
|
-
annotateRange(
|
|
2580
|
+
public annotateRange(
|
|
2545
2581
|
start: number, end: number, props: PropertySet, combiningOp: ICombiningOp | undefined, refSeq: number,
|
|
2546
2582
|
clientId: number, seq: number, opArgs: IMergeTreeDeltaOpArgs) {
|
|
2547
2583
|
this.ensureIntervalBoundary(start, refSeq, clientId);
|
|
@@ -2583,7 +2619,15 @@ export class MergeTree {
|
|
|
2583
2619
|
}
|
|
2584
2620
|
}
|
|
2585
2621
|
|
|
2586
|
-
markRangeRemoved(
|
|
2622
|
+
public markRangeRemoved(
|
|
2623
|
+
start: number,
|
|
2624
|
+
end: number,
|
|
2625
|
+
refSeq: number,
|
|
2626
|
+
clientId: number,
|
|
2627
|
+
seq: number,
|
|
2628
|
+
overwrite = false,
|
|
2629
|
+
opArgs: IMergeTreeDeltaOpArgs,
|
|
2630
|
+
) {
|
|
2587
2631
|
let _overwrite = overwrite;
|
|
2588
2632
|
this.ensureIntervalBoundary(start, refSeq, clientId);
|
|
2589
2633
|
this.ensureIntervalBoundary(end, refSeq, clientId);
|
|
@@ -2592,9 +2636,10 @@ export class MergeTree {
|
|
|
2592
2636
|
const savedLocalRefs: LocalReferenceCollection[] = [];
|
|
2593
2637
|
const localSeq = seq === UnassignedSequenceNumber ? ++this.collabWindow.localSeq : undefined;
|
|
2594
2638
|
const markRemoved = (segment: ISegment, pos: number, start: number, end: number) => {
|
|
2595
|
-
const removalInfo =
|
|
2639
|
+
const removalInfo: IRemovalInfo = segment;
|
|
2596
2640
|
if (removalInfo.removedSeq !== undefined) {
|
|
2597
2641
|
if (MergeTree.diagOverlappingRemove) {
|
|
2642
|
+
// eslint-disable-next-line max-len
|
|
2598
2643
|
console.log(`yump @seq ${seq} cli ${glc(this, this.collabWindow.clientId)}: overlaps deleted segment ${removalInfo.removedSeq} text '${segment.toString()}'`);
|
|
2599
2644
|
}
|
|
2600
2645
|
_overwrite = true;
|
|
@@ -2622,8 +2667,8 @@ export class MergeTree {
|
|
|
2622
2667
|
// Save segment so can assign removed sequence number when acked by server
|
|
2623
2668
|
if (this.collabWindow.collaborating) {
|
|
2624
2669
|
// Use removal information
|
|
2625
|
-
const removalInfo =
|
|
2626
|
-
if (
|
|
2670
|
+
const removalInfo: IRemovalInfo = segment;
|
|
2671
|
+
if (removalInfo.removedSeq === UnassignedSequenceNumber && clientId === this.collabWindow.clientId) {
|
|
2627
2672
|
segmentGroup = this.addToPendingList(segment, segmentGroup, localSeq);
|
|
2628
2673
|
} else {
|
|
2629
2674
|
if (MergeTree.options.zamboniSegments) {
|
|
@@ -2701,7 +2746,7 @@ export class MergeTree {
|
|
|
2701
2746
|
}
|
|
2702
2747
|
}
|
|
2703
2748
|
|
|
2704
|
-
removeLocalReference(segment: ISegment, lref: LocalReference) {
|
|
2749
|
+
public removeLocalReference(segment: ISegment, lref: LocalReference) {
|
|
2705
2750
|
if (segment.localRefs) {
|
|
2706
2751
|
const removedRef = segment.localRefs.removeLocalRef(lref);
|
|
2707
2752
|
if (removedRef) {
|
|
@@ -2711,7 +2756,7 @@ export class MergeTree {
|
|
|
2711
2756
|
}
|
|
2712
2757
|
}
|
|
2713
2758
|
|
|
2714
|
-
addLocalReference(lref: LocalReference) {
|
|
2759
|
+
public addLocalReference(lref: LocalReference) {
|
|
2715
2760
|
const segment = lref.segment!;
|
|
2716
2761
|
let localRefs = segment.localRefs;
|
|
2717
2762
|
if (!localRefs) {
|
|
@@ -2744,7 +2789,12 @@ export class MergeTree {
|
|
|
2744
2789
|
block.cachedLength = len;
|
|
2745
2790
|
}
|
|
2746
2791
|
|
|
2747
|
-
private blockUpdatePathLengths(
|
|
2792
|
+
private blockUpdatePathLengths(
|
|
2793
|
+
startBlock: IMergeBlock | undefined,
|
|
2794
|
+
seq: number,
|
|
2795
|
+
clientId: number,
|
|
2796
|
+
newStructure = false,
|
|
2797
|
+
) {
|
|
2748
2798
|
let block: IMergeBlock | undefined = startBlock;
|
|
2749
2799
|
while (block !== undefined) {
|
|
2750
2800
|
if (newStructure) {
|
|
@@ -2758,8 +2808,16 @@ export class MergeTree {
|
|
|
2758
2808
|
|
|
2759
2809
|
private blockUpdateLength(node: IMergeBlock, seq: number, clientId: number) {
|
|
2760
2810
|
this.blockUpdate(node);
|
|
2761
|
-
if (
|
|
2762
|
-
|
|
2811
|
+
if (
|
|
2812
|
+
this.collabWindow.collaborating
|
|
2813
|
+
&& seq !== UnassignedSequenceNumber
|
|
2814
|
+
&& seq !== TreeMaintenanceSequenceNumber
|
|
2815
|
+
) {
|
|
2816
|
+
if (
|
|
2817
|
+
node.partialLengths !== undefined
|
|
2818
|
+
&& MergeTree.options.incrementalUpdate
|
|
2819
|
+
&& clientId !== NonCollabClient
|
|
2820
|
+
) {
|
|
2763
2821
|
node.partialLengths.update(this, node, seq, clientId, this.collabWindow);
|
|
2764
2822
|
} else {
|
|
2765
2823
|
node.partialLengths = PartialSequenceLengths.combine(this, node, this.collabWindow);
|
|
@@ -2767,12 +2825,25 @@ export class MergeTree {
|
|
|
2767
2825
|
}
|
|
2768
2826
|
}
|
|
2769
2827
|
|
|
2770
|
-
map<TClientData>(
|
|
2828
|
+
public map<TClientData>(
|
|
2829
|
+
actions: SegmentActions<TClientData>,
|
|
2830
|
+
refSeq: number,
|
|
2831
|
+
clientId: number,
|
|
2832
|
+
accum: TClientData,
|
|
2833
|
+
) {
|
|
2771
2834
|
// TODO: optimize to avoid comparisons
|
|
2772
2835
|
this.nodeMap(this.root, actions, 0, refSeq, clientId, accum);
|
|
2773
2836
|
}
|
|
2774
2837
|
|
|
2775
|
-
mapRange<TClientData>(
|
|
2838
|
+
public mapRange<TClientData>(
|
|
2839
|
+
actions: SegmentActions<TClientData>,
|
|
2840
|
+
refSeq: number,
|
|
2841
|
+
clientId: number,
|
|
2842
|
+
accum: TClientData,
|
|
2843
|
+
start?: number,
|
|
2844
|
+
end?: number,
|
|
2845
|
+
splitRange: boolean = false,
|
|
2846
|
+
) {
|
|
2776
2847
|
if (splitRange) {
|
|
2777
2848
|
if (start) {
|
|
2778
2849
|
this.ensureIntervalBoundary(start, refSeq, clientId);
|
|
@@ -2784,9 +2855,10 @@ export class MergeTree {
|
|
|
2784
2855
|
this.nodeMap(this.root, actions, 0, refSeq, clientId, accum, start, end);
|
|
2785
2856
|
}
|
|
2786
2857
|
|
|
2787
|
-
nodeToString(block: IMergeBlock, strbuf: string, indentCount = 0) {
|
|
2858
|
+
public nodeToString(block: IMergeBlock, strbuf: string, indentCount = 0) {
|
|
2788
2859
|
let _strbuf = strbuf;
|
|
2789
2860
|
_strbuf += internedSpaces(indentCount);
|
|
2861
|
+
// eslint-disable-next-line max-len
|
|
2790
2862
|
_strbuf += `Node (len ${block.cachedLength}) p len (${block.parent ? block.parent.cachedLength : 0}) ord ${ordinalToArray(block.ordinal)} with ${block.childCount} segs:\n`;
|
|
2791
2863
|
if (MergeTree.blockUpdateMarkers) {
|
|
2792
2864
|
_strbuf += internedSpaces(indentCount);
|
|
@@ -2804,8 +2876,9 @@ export class MergeTree {
|
|
|
2804
2876
|
} else {
|
|
2805
2877
|
const segment = child;
|
|
2806
2878
|
_strbuf += internedSpaces(indentCount + 4);
|
|
2879
|
+
// eslint-disable-next-line max-len
|
|
2807
2880
|
_strbuf += `cli: ${glc(this, segment.clientId)} seq: ${segment.seq} ord: ${ordinalToArray(segment.ordinal)}`;
|
|
2808
|
-
const removalInfo =
|
|
2881
|
+
const removalInfo: IRemovalInfo = segment;
|
|
2809
2882
|
if (removalInfo.removedSeq !== undefined) {
|
|
2810
2883
|
_strbuf += ` rcli: ${glc(this, removalInfo.removedClientId!)} rseq: ${removalInfo.removedSeq}`;
|
|
2811
2884
|
}
|
|
@@ -2818,7 +2891,7 @@ export class MergeTree {
|
|
|
2818
2891
|
return _strbuf;
|
|
2819
2892
|
}
|
|
2820
2893
|
|
|
2821
|
-
toString() {
|
|
2894
|
+
public toString() {
|
|
2822
2895
|
return this.nodeToString(this.root, "", 0);
|
|
2823
2896
|
}
|
|
2824
2897
|
|
|
@@ -2846,7 +2919,7 @@ export class MergeTree {
|
|
|
2846
2919
|
const len = this.nodeLength(child, state.refSeq, state.clientId) ?? 0;
|
|
2847
2920
|
if (MergeTree.traceIncrTraversal) {
|
|
2848
2921
|
if (child.isLeaf()) {
|
|
2849
|
-
// eslint-disable-next-line @typescript-eslint/dot-notation
|
|
2922
|
+
// eslint-disable-next-line @typescript-eslint/dot-notation, max-len
|
|
2850
2923
|
console.log(`considering (r ${state.refSeq} c ${glc(this, state.clientId)}) seg with text ${child["text"]} len ${len} seq ${child.seq} rseq ${child.removedSeq} cli ${glc(this, child.clientId)}`);
|
|
2851
2924
|
}
|
|
2852
2925
|
}
|
|
@@ -2913,6 +2986,7 @@ export class MergeTree {
|
|
|
2913
2986
|
segInfo += ` rcli: ${glc(this, segment.removedClientId!)} rseq: ${segment.removedSeq}`;
|
|
2914
2987
|
}
|
|
2915
2988
|
}
|
|
2989
|
+
// eslint-disable-next-line max-len
|
|
2916
2990
|
console.log(`@tcli ${glc(this, this.collabWindow.clientId)}: map len: ${len} start: ${_start} end: ${_end} ${segInfo}`);
|
|
2917
2991
|
}
|
|
2918
2992
|
if (go && (_end > 0) && (len > 0) && (_start < len)) {
|
|
@@ -2949,7 +3023,7 @@ export class MergeTree {
|
|
|
2949
3023
|
|
|
2950
3024
|
// Invokes the leaf action for all segments. Note that *all* segments are visited
|
|
2951
3025
|
// regardless of if they would be visible to the current `clientId` and `refSeq`.
|
|
2952
|
-
walkAllSegments<TClientData>(
|
|
3026
|
+
public walkAllSegments<TClientData>(
|
|
2953
3027
|
block: IMergeBlock,
|
|
2954
3028
|
action: (segment: ISegment, accum?: TClientData) => boolean,
|
|
2955
3029
|
accum?: TClientData,
|