@fluidframework/merge-tree 2.3.1 → 2.4.0-297027
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/api-report/merge-tree.legacy.alpha.api.md +26 -7
- package/dist/attributionPolicy.d.ts.map +1 -1
- package/dist/attributionPolicy.js +10 -3
- package/dist/attributionPolicy.js.map +1 -1
- package/dist/client.d.ts +14 -4
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +97 -10
- package/dist/client.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js.map +1 -1
- package/dist/legacy.d.ts +1 -0
- package/dist/mergeTree.d.ts +15 -2
- package/dist/mergeTree.d.ts.map +1 -1
- package/dist/mergeTree.js +68 -48
- package/dist/mergeTree.js.map +1 -1
- package/dist/mergeTreeNodes.d.ts +6 -9
- package/dist/mergeTreeNodes.d.ts.map +1 -1
- package/dist/mergeTreeNodes.js +2 -1
- package/dist/mergeTreeNodes.js.map +1 -1
- package/dist/opBuilder.d.ts +15 -1
- package/dist/opBuilder.d.ts.map +1 -1
- package/dist/opBuilder.js +28 -1
- package/dist/opBuilder.js.map +1 -1
- package/dist/ops.d.ts +27 -1
- package/dist/ops.d.ts.map +1 -1
- package/dist/ops.js +1 -0
- package/dist/ops.js.map +1 -1
- package/dist/sequencePlace.d.ts +4 -0
- package/dist/sequencePlace.d.ts.map +1 -1
- package/dist/sequencePlace.js +17 -1
- package/dist/sequencePlace.js.map +1 -1
- package/dist/test/obliterate.concurrent.spec.js +18 -0
- package/dist/test/obliterate.concurrent.spec.js.map +1 -1
- package/dist/test/obliterate.partialLength.spec.js +8 -4
- package/dist/test/obliterate.partialLength.spec.js.map +1 -1
- package/dist/test/obliterate.rangeExpansion.spec.js +109 -53
- package/dist/test/obliterate.rangeExpansion.spec.js.map +1 -1
- package/dist/test/obliterate.spec.js +14 -8
- package/dist/test/obliterate.spec.js.map +1 -1
- package/dist/test/reconnectHelper.d.ts +8 -6
- package/dist/test/reconnectHelper.d.ts.map +1 -1
- package/dist/test/reconnectHelper.js +14 -13
- package/dist/test/reconnectHelper.js.map +1 -1
- package/dist/test/testClient.d.ts +1 -11
- package/dist/test/testClient.d.ts.map +1 -1
- package/dist/test/testClient.js +0 -3
- package/dist/test/testClient.js.map +1 -1
- package/dist/test/testClientLogger.d.ts.map +1 -1
- package/dist/test/testClientLogger.js +19 -8
- package/dist/test/testClientLogger.js.map +1 -1
- package/dist/test/testUtils.d.ts +10 -0
- package/dist/test/testUtils.d.ts.map +1 -1
- package/dist/test/testUtils.js +5 -1
- package/dist/test/testUtils.js.map +1 -1
- package/lib/attributionPolicy.d.ts.map +1 -1
- package/lib/attributionPolicy.js +10 -3
- package/lib/attributionPolicy.js.map +1 -1
- package/lib/client.d.ts +14 -4
- package/lib/client.d.ts.map +1 -1
- package/lib/client.js +98 -11
- package/lib/client.js.map +1 -1
- package/lib/index.d.ts +1 -1
- package/lib/index.d.ts.map +1 -1
- package/lib/index.js.map +1 -1
- package/lib/legacy.d.ts +1 -0
- package/lib/mergeTree.d.ts +15 -2
- package/lib/mergeTree.d.ts.map +1 -1
- package/lib/mergeTree.js +69 -49
- package/lib/mergeTree.js.map +1 -1
- package/lib/mergeTreeNodes.d.ts +6 -9
- package/lib/mergeTreeNodes.d.ts.map +1 -1
- package/lib/mergeTreeNodes.js +2 -1
- package/lib/mergeTreeNodes.js.map +1 -1
- package/lib/opBuilder.d.ts +15 -1
- package/lib/opBuilder.d.ts.map +1 -1
- package/lib/opBuilder.js +26 -0
- package/lib/opBuilder.js.map +1 -1
- package/lib/ops.d.ts +27 -1
- package/lib/ops.d.ts.map +1 -1
- package/lib/ops.js +1 -0
- package/lib/ops.js.map +1 -1
- package/lib/sequencePlace.d.ts +4 -0
- package/lib/sequencePlace.d.ts.map +1 -1
- package/lib/sequencePlace.js +15 -0
- package/lib/sequencePlace.js.map +1 -1
- package/lib/test/obliterate.concurrent.spec.js +18 -0
- package/lib/test/obliterate.concurrent.spec.js.map +1 -1
- package/lib/test/obliterate.partialLength.spec.js +9 -5
- package/lib/test/obliterate.partialLength.spec.js.map +1 -1
- package/lib/test/obliterate.rangeExpansion.spec.js +109 -53
- package/lib/test/obliterate.rangeExpansion.spec.js.map +1 -1
- package/lib/test/obliterate.spec.js +15 -9
- package/lib/test/obliterate.spec.js.map +1 -1
- package/lib/test/reconnectHelper.d.ts +8 -6
- package/lib/test/reconnectHelper.d.ts.map +1 -1
- package/lib/test/reconnectHelper.js +15 -14
- package/lib/test/reconnectHelper.js.map +1 -1
- package/lib/test/testClient.d.ts +1 -11
- package/lib/test/testClient.d.ts.map +1 -1
- package/lib/test/testClient.js +0 -3
- package/lib/test/testClient.js.map +1 -1
- package/lib/test/testClientLogger.d.ts.map +1 -1
- package/lib/test/testClientLogger.js +19 -8
- package/lib/test/testClientLogger.js.map +1 -1
- package/lib/test/testUtils.d.ts +10 -0
- package/lib/test/testUtils.d.ts.map +1 -1
- package/lib/test/testUtils.js +3 -0
- package/lib/test/testUtils.js.map +1 -1
- package/package.json +30 -17
- package/src/attributionPolicy.ts +5 -0
- package/src/client.ts +138 -20
- package/src/index.ts +1 -0
- package/src/mergeTree.ts +116 -77
- package/src/mergeTreeNodes.ts +9 -10
- package/src/opBuilder.ts +32 -0
- package/src/ops.ts +23 -1
- package/src/sequencePlace.ts +16 -0
package/src/client.ts
CHANGED
|
@@ -56,6 +56,7 @@ import {
|
|
|
56
56
|
createGroupOp,
|
|
57
57
|
createInsertSegmentOp,
|
|
58
58
|
createObliterateRangeOp,
|
|
59
|
+
createObliterateRangeOpSided,
|
|
59
60
|
createRemoveRangeOp,
|
|
60
61
|
} from "./opBuilder.js";
|
|
61
62
|
import {
|
|
@@ -72,9 +73,11 @@ import {
|
|
|
72
73
|
IRelativePosition,
|
|
73
74
|
MergeTreeDeltaType,
|
|
74
75
|
ReferenceType,
|
|
76
|
+
type IMergeTreeObliterateSidedMsg,
|
|
75
77
|
} from "./ops.js";
|
|
76
78
|
import { PropertySet } from "./properties.js";
|
|
77
79
|
import { DetachedReferencePosition, ReferencePosition } from "./referencePositions.js";
|
|
80
|
+
import { Side, type InteriorSequencePlace } from "./sequencePlace.js";
|
|
78
81
|
import { SnapshotLoader } from "./snapshotLoader.js";
|
|
79
82
|
import { SnapshotV1 } from "./snapshotV1.js";
|
|
80
83
|
import { SnapshotLegacy } from "./snapshotlegacy.js";
|
|
@@ -99,6 +102,7 @@ export interface IIntegerRange {
|
|
|
99
102
|
* they need for rebasing their ops on reconnection.
|
|
100
103
|
* @legacy
|
|
101
104
|
* @alpha
|
|
105
|
+
* @deprecated This functionality was not meant to be exported and will be removed in a future release
|
|
102
106
|
*/
|
|
103
107
|
export interface IClientEvents {
|
|
104
108
|
(event: "normalize", listener: (target: IEventThisPlaceHolder) => void): void;
|
|
@@ -252,12 +256,26 @@ export class Client extends TypedEventEmitter<IClientEvents> {
|
|
|
252
256
|
* Obliterates the range. This is similar to removing the range, but also
|
|
253
257
|
* includes any concurrently inserted content.
|
|
254
258
|
*
|
|
255
|
-
* @param start - The
|
|
256
|
-
* @param end - The
|
|
259
|
+
* @param start - The start of the range to obliterate. Inclusive is side is Before (default).
|
|
260
|
+
* @param end - The end of the range to obliterate. Exclusive is side is After
|
|
261
|
+
* (default is to be after the last included character, but number index is exclusive).
|
|
257
262
|
*/
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
263
|
+
public obliterateRangeLocal(
|
|
264
|
+
start: number | InteriorSequencePlace,
|
|
265
|
+
end: number | InteriorSequencePlace,
|
|
266
|
+
// eslint-disable-next-line import/no-deprecated
|
|
267
|
+
): IMergeTreeObliterateMsg | IMergeTreeObliterateSidedMsg {
|
|
268
|
+
// eslint-disable-next-line import/no-deprecated
|
|
269
|
+
let obliterateOp: IMergeTreeObliterateMsg | IMergeTreeObliterateSidedMsg;
|
|
270
|
+
if (this._mergeTree.options?.mergeTreeEnableSidedObliterate) {
|
|
271
|
+
obliterateOp = createObliterateRangeOpSided(start, end);
|
|
272
|
+
} else {
|
|
273
|
+
assert(
|
|
274
|
+
typeof start === "number" && typeof end === "number",
|
|
275
|
+
"Start and end must be numbers if mergeTreeEnableSidedObliterate is not enabled.",
|
|
276
|
+
);
|
|
277
|
+
obliterateOp = createObliterateRangeOp(start, end);
|
|
278
|
+
}
|
|
261
279
|
this.applyObliterateRangeOp({ op: obliterateOp });
|
|
262
280
|
return obliterateOp;
|
|
263
281
|
}
|
|
@@ -469,22 +487,39 @@ export class Client extends TypedEventEmitter<IClientEvents> {
|
|
|
469
487
|
|
|
470
488
|
private applyObliterateRangeOp(opArgs: IMergeTreeDeltaOpArgs): void {
|
|
471
489
|
assert(
|
|
472
|
-
opArgs.op.type === MergeTreeDeltaType.OBLITERATE
|
|
490
|
+
opArgs.op.type === MergeTreeDeltaType.OBLITERATE ||
|
|
491
|
+
opArgs.op.type === MergeTreeDeltaType.OBLITERATE_SIDED,
|
|
473
492
|
0x866 /* Unexpected op type on range obliterate! */,
|
|
474
493
|
);
|
|
475
494
|
const op = opArgs.op;
|
|
476
495
|
const clientArgs = this.getClientSequenceArgs(opArgs);
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
496
|
+
if (this._mergeTree.options?.mergeTreeEnableSidedObliterate) {
|
|
497
|
+
const { start, end } = this.getValidSidedRange(op, clientArgs);
|
|
498
|
+
this._mergeTree.obliterateRange(
|
|
499
|
+
start,
|
|
500
|
+
end,
|
|
501
|
+
clientArgs.referenceSequenceNumber,
|
|
502
|
+
clientArgs.clientId,
|
|
503
|
+
clientArgs.sequenceNumber,
|
|
504
|
+
false,
|
|
505
|
+
opArgs,
|
|
506
|
+
);
|
|
507
|
+
} else {
|
|
508
|
+
assert(
|
|
509
|
+
op.type === MergeTreeDeltaType.OBLITERATE,
|
|
510
|
+
"Unexpected sided obliterate while mergeTreeEnableSidedObliterate is disabled",
|
|
511
|
+
);
|
|
512
|
+
const range = this.getValidOpRange(op, clientArgs);
|
|
513
|
+
this._mergeTree.obliterateRange(
|
|
514
|
+
range.start,
|
|
515
|
+
range.end,
|
|
516
|
+
clientArgs.referenceSequenceNumber,
|
|
517
|
+
clientArgs.clientId,
|
|
518
|
+
clientArgs.sequenceNumber,
|
|
519
|
+
false,
|
|
520
|
+
opArgs,
|
|
521
|
+
);
|
|
522
|
+
}
|
|
488
523
|
}
|
|
489
524
|
|
|
490
525
|
/**
|
|
@@ -562,6 +597,82 @@ export class Client extends TypedEventEmitter<IClientEvents> {
|
|
|
562
597
|
);
|
|
563
598
|
}
|
|
564
599
|
|
|
600
|
+
/**
|
|
601
|
+
* Returns a valid range for the op, or throws if the range is invalid
|
|
602
|
+
* @param op - The op to generate the range for
|
|
603
|
+
* @param clientArgs - The client args for the op
|
|
604
|
+
* @throws LoggingError if the range is invalid
|
|
605
|
+
*/
|
|
606
|
+
private getValidSidedRange(
|
|
607
|
+
// eslint-disable-next-line import/no-deprecated
|
|
608
|
+
op: IMergeTreeObliterateSidedMsg | IMergeTreeObliterateMsg,
|
|
609
|
+
clientArgs: IMergeTreeClientSequenceArgs,
|
|
610
|
+
): {
|
|
611
|
+
start: InteriorSequencePlace;
|
|
612
|
+
end: InteriorSequencePlace;
|
|
613
|
+
} {
|
|
614
|
+
const invalidPositions: string[] = [];
|
|
615
|
+
let start: InteriorSequencePlace | undefined;
|
|
616
|
+
let end: InteriorSequencePlace | undefined;
|
|
617
|
+
if (op.pos1 === undefined) {
|
|
618
|
+
invalidPositions.push("start");
|
|
619
|
+
} else {
|
|
620
|
+
start =
|
|
621
|
+
typeof op.pos1 === "object"
|
|
622
|
+
? { pos: op.pos1.pos, side: op.pos1.before ? Side.Before : Side.After }
|
|
623
|
+
: { pos: op.pos1, side: Side.Before };
|
|
624
|
+
}
|
|
625
|
+
if (op.pos2 === undefined) {
|
|
626
|
+
invalidPositions.push("end");
|
|
627
|
+
} else {
|
|
628
|
+
end =
|
|
629
|
+
typeof op.pos2 === "object"
|
|
630
|
+
? { pos: op.pos2.pos, side: op.pos2.before ? Side.Before : Side.After }
|
|
631
|
+
: { pos: op.pos2 - 1, side: Side.After };
|
|
632
|
+
}
|
|
633
|
+
|
|
634
|
+
// Validate if local op
|
|
635
|
+
if (clientArgs.clientId === this.getClientId()) {
|
|
636
|
+
const length = this._mergeTree.getLength(
|
|
637
|
+
this.getCollabWindow().currentSeq,
|
|
638
|
+
this.getClientId(),
|
|
639
|
+
);
|
|
640
|
+
if (start !== undefined && (start.pos >= length || start.pos < 0)) {
|
|
641
|
+
// start out of bounds
|
|
642
|
+
invalidPositions.push("start");
|
|
643
|
+
}
|
|
644
|
+
if (end !== undefined && (end.pos >= length || end.pos < 0)) {
|
|
645
|
+
invalidPositions.push("end");
|
|
646
|
+
}
|
|
647
|
+
if (
|
|
648
|
+
start !== undefined &&
|
|
649
|
+
end !== undefined &&
|
|
650
|
+
(start.pos > end.pos ||
|
|
651
|
+
(start.pos === end.pos && start.side !== end.side && start.side === Side.After))
|
|
652
|
+
) {
|
|
653
|
+
// end is before start
|
|
654
|
+
invalidPositions.push("inverted");
|
|
655
|
+
}
|
|
656
|
+
if (invalidPositions.length > 0) {
|
|
657
|
+
throw new LoggingError("InvalidRange", {
|
|
658
|
+
usageError: true,
|
|
659
|
+
invalidPositions: invalidPositions.toString(),
|
|
660
|
+
length,
|
|
661
|
+
opType: op.type,
|
|
662
|
+
opPos1Relative: op.relativePos1 !== undefined,
|
|
663
|
+
opPos2Relative: op.relativePos2 !== undefined,
|
|
664
|
+
opPos1: JSON.stringify(op.pos1),
|
|
665
|
+
opPos2: JSON.stringify(op.pos2),
|
|
666
|
+
start: JSON.stringify(start),
|
|
667
|
+
end: JSON.stringify(end),
|
|
668
|
+
});
|
|
669
|
+
}
|
|
670
|
+
}
|
|
671
|
+
|
|
672
|
+
assert(start !== undefined && end !== undefined, "Missing start or end of range");
|
|
673
|
+
return { start, end };
|
|
674
|
+
}
|
|
675
|
+
|
|
565
676
|
/**
|
|
566
677
|
* Returns a valid range for the op, or undefined
|
|
567
678
|
* @param op - The op to generate the range for
|
|
@@ -611,7 +722,6 @@ export class Client extends TypedEventEmitter<IClientEvents> {
|
|
|
611
722
|
invalidPositions.push("start");
|
|
612
723
|
}
|
|
613
724
|
// Validate end if not insert, or insert has end
|
|
614
|
-
//
|
|
615
725
|
if (
|
|
616
726
|
(op.type !== MergeTreeDeltaType.INSERT || end !== undefined) &&
|
|
617
727
|
(end === undefined || end <= start!)
|
|
@@ -889,7 +999,12 @@ export class Client extends TypedEventEmitter<IClientEvents> {
|
|
|
889
999
|
|
|
890
1000
|
const first = opList[0];
|
|
891
1001
|
|
|
892
|
-
if (
|
|
1002
|
+
if (
|
|
1003
|
+
!!first &&
|
|
1004
|
+
first.pos2 !== undefined &&
|
|
1005
|
+
first.type !== MergeTreeDeltaType.OBLITERATE_SIDED &&
|
|
1006
|
+
newOp.type !== MergeTreeDeltaType.OBLITERATE_SIDED
|
|
1007
|
+
) {
|
|
893
1008
|
first.pos2 += newOp.pos2! - newOp.pos1!;
|
|
894
1009
|
} else {
|
|
895
1010
|
opList.push(newOp);
|
|
@@ -936,7 +1051,8 @@ export class Client extends TypedEventEmitter<IClientEvents> {
|
|
|
936
1051
|
this.applyAnnotateRangeOp(opArgs);
|
|
937
1052
|
break;
|
|
938
1053
|
}
|
|
939
|
-
case MergeTreeDeltaType.OBLITERATE:
|
|
1054
|
+
case MergeTreeDeltaType.OBLITERATE:
|
|
1055
|
+
case MergeTreeDeltaType.OBLITERATE_SIDED: {
|
|
940
1056
|
this.applyObliterateRangeOp(opArgs);
|
|
941
1057
|
break;
|
|
942
1058
|
}
|
|
@@ -970,6 +1086,7 @@ export class Client extends TypedEventEmitter<IClientEvents> {
|
|
|
970
1086
|
this.applyAnnotateRangeOp({ op });
|
|
971
1087
|
break;
|
|
972
1088
|
}
|
|
1089
|
+
case MergeTreeDeltaType.OBLITERATE_SIDED:
|
|
973
1090
|
case MergeTreeDeltaType.OBLITERATE: {
|
|
974
1091
|
this.applyObliterateRangeOp({ op });
|
|
975
1092
|
break;
|
|
@@ -1202,6 +1319,7 @@ export class Client extends TypedEventEmitter<IClientEvents> {
|
|
|
1202
1319
|
this.applyRemoveRangeOp(opArgs);
|
|
1203
1320
|
break;
|
|
1204
1321
|
}
|
|
1322
|
+
case MergeTreeDeltaType.OBLITERATE_SIDED:
|
|
1205
1323
|
case MergeTreeDeltaType.OBLITERATE: {
|
|
1206
1324
|
this.applyObliterateRangeOp(opArgs);
|
|
1207
1325
|
break;
|
package/src/index.ts
CHANGED
package/src/mergeTree.ts
CHANGED
|
@@ -90,7 +90,7 @@ import {
|
|
|
90
90
|
} from "./referencePositions.js";
|
|
91
91
|
// eslint-disable-next-line import/no-deprecated
|
|
92
92
|
import { PropertiesRollback } from "./segmentPropertiesManager.js";
|
|
93
|
-
import {
|
|
93
|
+
import { Side, type InteriorSequencePlace } from "./sequencePlace.js";
|
|
94
94
|
import { SortedSegmentSet } from "./sortedSegmentSet.js";
|
|
95
95
|
import { zamboniSegments } from "./zamboni.js";
|
|
96
96
|
|
|
@@ -198,6 +198,17 @@ export interface IMergeTreeOptions {
|
|
|
198
198
|
* @defaultValue `false`
|
|
199
199
|
*/
|
|
200
200
|
mergeTreeEnableObliterateReconnect?: boolean;
|
|
201
|
+
|
|
202
|
+
/**
|
|
203
|
+
* Enables support for obliterate endpoint expansion.
|
|
204
|
+
* When enabled, obliterate operations can have sidedness specified for their endpoints.
|
|
205
|
+
* If an endpoint is externally anchored
|
|
206
|
+
* (aka the start is after a given position, or the end is before a given position),
|
|
207
|
+
* then concurrent inserts adjacent to the exclusive endpoint of an obliterated range will be included in the obliteration
|
|
208
|
+
*
|
|
209
|
+
* @defaultValue `false`
|
|
210
|
+
*/
|
|
211
|
+
mergeTreeEnableSidedObliterate?: boolean;
|
|
201
212
|
}
|
|
202
213
|
export function errorIfOptionNotTrue(
|
|
203
214
|
options: IMergeTreeOptions | undefined,
|
|
@@ -211,6 +222,7 @@ export function errorIfOptionNotTrue(
|
|
|
211
222
|
/**
|
|
212
223
|
* @legacy
|
|
213
224
|
* @alpha
|
|
225
|
+
* @deprecated This functionality was not meant to be exported and will be removed in a future release
|
|
214
226
|
*/
|
|
215
227
|
export interface IMergeTreeAttributionOptions {
|
|
216
228
|
/**
|
|
@@ -238,6 +250,7 @@ export interface IMergeTreeAttributionOptions {
|
|
|
238
250
|
* @sealed
|
|
239
251
|
* @legacy
|
|
240
252
|
* @alpha
|
|
253
|
+
* @deprecated This functionality was not meant to be exported and will be removed in a future release
|
|
241
254
|
*/
|
|
242
255
|
export interface AttributionPolicy {
|
|
243
256
|
/**
|
|
@@ -1235,7 +1248,10 @@ export class MergeTree {
|
|
|
1235
1248
|
});
|
|
1236
1249
|
});
|
|
1237
1250
|
|
|
1238
|
-
if (
|
|
1251
|
+
if (
|
|
1252
|
+
opArgs.op.type === MergeTreeDeltaType.OBLITERATE ||
|
|
1253
|
+
opArgs.op.type === MergeTreeDeltaType.OBLITERATE_SIDED
|
|
1254
|
+
) {
|
|
1239
1255
|
this.obliterates.addOrUpdate(pendingSegmentGroup.obliterateInfo!);
|
|
1240
1256
|
}
|
|
1241
1257
|
|
|
@@ -1243,7 +1259,8 @@ export class MergeTree {
|
|
|
1243
1259
|
// positions after slide are final
|
|
1244
1260
|
if (
|
|
1245
1261
|
opArgs.op.type === MergeTreeDeltaType.REMOVE ||
|
|
1246
|
-
opArgs.op.type === MergeTreeDeltaType.OBLITERATE
|
|
1262
|
+
opArgs.op.type === MergeTreeDeltaType.OBLITERATE ||
|
|
1263
|
+
opArgs.op.type === MergeTreeDeltaType.OBLITERATE_SIDED
|
|
1247
1264
|
) {
|
|
1248
1265
|
this.slideAckedRemovedSegmentReferences(pendingSegmentGroup.segments);
|
|
1249
1266
|
}
|
|
@@ -1511,11 +1528,11 @@ export class MergeTree {
|
|
|
1511
1528
|
}
|
|
1512
1529
|
|
|
1513
1530
|
this.updateRoot(splitNode);
|
|
1514
|
-
saveIfLocal(newSegment);
|
|
1515
1531
|
|
|
1516
1532
|
insertPos += newSegment.cachedLength;
|
|
1517
1533
|
|
|
1518
1534
|
if (!this.options?.mergeTreeEnableObliterate || this.obliterates.empty()) {
|
|
1535
|
+
saveIfLocal(newSegment);
|
|
1519
1536
|
continue;
|
|
1520
1537
|
}
|
|
1521
1538
|
|
|
@@ -1543,13 +1560,13 @@ export class MergeTree {
|
|
|
1543
1560
|
movedClientIds.unshift(ob.clientId);
|
|
1544
1561
|
movedSeqs.unshift(ob.seq);
|
|
1545
1562
|
} else {
|
|
1546
|
-
if (newest === undefined || normalizedNewestSeq < normalizedObSeq) {
|
|
1547
|
-
normalizedNewestSeq = normalizedObSeq;
|
|
1548
|
-
newest = ob;
|
|
1549
|
-
}
|
|
1550
1563
|
movedClientIds.push(ob.clientId);
|
|
1551
1564
|
movedSeqs.push(ob.seq);
|
|
1552
1565
|
}
|
|
1566
|
+
if (newest === undefined || normalizedNewestSeq < normalizedObSeq) {
|
|
1567
|
+
normalizedNewestSeq = normalizedObSeq;
|
|
1568
|
+
newest = ob;
|
|
1569
|
+
}
|
|
1553
1570
|
}
|
|
1554
1571
|
}
|
|
1555
1572
|
|
|
@@ -1576,7 +1593,11 @@ export class MergeTree {
|
|
|
1576
1593
|
if (newSegment.parent) {
|
|
1577
1594
|
this.blockUpdatePathLengths(newSegment.parent, seq, clientId);
|
|
1578
1595
|
}
|
|
1596
|
+
} else if (oldest && newest?.clientId === clientId) {
|
|
1597
|
+
newSegment.prevObliterateByInserter = newest;
|
|
1579
1598
|
}
|
|
1599
|
+
|
|
1600
|
+
saveIfLocal(newSegment);
|
|
1580
1601
|
}
|
|
1581
1602
|
}
|
|
1582
1603
|
}
|
|
@@ -1601,11 +1622,7 @@ export class MergeTree {
|
|
|
1601
1622
|
return { next };
|
|
1602
1623
|
};
|
|
1603
1624
|
|
|
1604
|
-
private ensureIntervalBoundary(
|
|
1605
|
-
pos: number | "start" | "end",
|
|
1606
|
-
refSeq: number,
|
|
1607
|
-
clientId: number,
|
|
1608
|
-
): void {
|
|
1625
|
+
private ensureIntervalBoundary(pos: number, refSeq: number, clientId: number): void {
|
|
1609
1626
|
const splitNode = this.insertingWalk(
|
|
1610
1627
|
this.root,
|
|
1611
1628
|
pos,
|
|
@@ -1649,21 +1666,14 @@ export class MergeTree {
|
|
|
1649
1666
|
|
|
1650
1667
|
private insertingWalk(
|
|
1651
1668
|
block: MergeBlock,
|
|
1652
|
-
pos: number
|
|
1669
|
+
pos: number,
|
|
1653
1670
|
refSeq: number,
|
|
1654
1671
|
clientId: number,
|
|
1655
1672
|
seq: number,
|
|
1656
1673
|
context: InsertContext,
|
|
1657
1674
|
isLastChildBlock: boolean = true,
|
|
1658
1675
|
): MergeBlock | undefined {
|
|
1659
|
-
let _pos: number;
|
|
1660
|
-
if (pos === "start") {
|
|
1661
|
-
_pos = 0;
|
|
1662
|
-
} else if (pos === "end") {
|
|
1663
|
-
_pos = this.root.mergeTree?.getLength(refSeq, clientId) ?? 0;
|
|
1664
|
-
} else {
|
|
1665
|
-
_pos = pos;
|
|
1666
|
-
}
|
|
1676
|
+
let _pos: number = pos;
|
|
1667
1677
|
|
|
1668
1678
|
const children = block.children;
|
|
1669
1679
|
let childIndex: number;
|
|
@@ -1878,28 +1888,17 @@ export class MergeTree {
|
|
|
1878
1888
|
}
|
|
1879
1889
|
}
|
|
1880
1890
|
|
|
1881
|
-
|
|
1882
|
-
start:
|
|
1883
|
-
end:
|
|
1891
|
+
private obliterateRangeSided(
|
|
1892
|
+
start: InteriorSequencePlace,
|
|
1893
|
+
end: InteriorSequencePlace,
|
|
1884
1894
|
refSeq: number,
|
|
1885
1895
|
clientId: number,
|
|
1886
1896
|
seq: number,
|
|
1887
1897
|
overwrite: boolean = false,
|
|
1888
1898
|
opArgs: IMergeTreeDeltaOpArgs,
|
|
1889
1899
|
): void {
|
|
1890
|
-
|
|
1891
|
-
|
|
1892
|
-
const { startPos, startSide, endPos, endSide } = endpointPosAndSide(start, end);
|
|
1893
|
-
|
|
1894
|
-
assert(
|
|
1895
|
-
startPos !== undefined &&
|
|
1896
|
-
endPos !== undefined &&
|
|
1897
|
-
startSide !== undefined &&
|
|
1898
|
-
endSide !== undefined &&
|
|
1899
|
-
startPos !== "end" &&
|
|
1900
|
-
endPos !== "start",
|
|
1901
|
-
0x9e2 /* start and end cannot be undefined because they were not passed in as undefined */,
|
|
1902
|
-
);
|
|
1900
|
+
const startPos = start.side === Side.Before ? start.pos : start.pos + 1;
|
|
1901
|
+
const endPos = end.side === Side.Before ? end.pos : end.pos + 1;
|
|
1903
1902
|
|
|
1904
1903
|
this.ensureIntervalBoundary(startPos, refSeq, clientId);
|
|
1905
1904
|
this.ensureIntervalBoundary(endPos, refSeq, clientId);
|
|
@@ -1919,20 +1918,9 @@ export class MergeTree {
|
|
|
1919
1918
|
localSeq,
|
|
1920
1919
|
segmentGroup: undefined,
|
|
1921
1920
|
};
|
|
1922
|
-
const normalizedStartPos = startPos === "start" || startPos === undefined ? 0 : startPos;
|
|
1923
|
-
const normalizedEndPos =
|
|
1924
|
-
endPos === "end" || endPos === undefined ? this.getLength(refSeq, clientId) : endPos;
|
|
1925
1921
|
|
|
1926
|
-
const { segment: startSeg } = this.getContainingSegment(
|
|
1927
|
-
|
|
1928
|
-
refSeq,
|
|
1929
|
-
clientId,
|
|
1930
|
-
);
|
|
1931
|
-
const { segment: endSeg } = this.getContainingSegment(
|
|
1932
|
-
normalizedEndPos - 1,
|
|
1933
|
-
refSeq,
|
|
1934
|
-
clientId,
|
|
1935
|
-
);
|
|
1922
|
+
const { segment: startSeg } = this.getContainingSegment(start.pos, refSeq, clientId);
|
|
1923
|
+
const { segment: endSeg } = this.getContainingSegment(end.pos, refSeq, clientId);
|
|
1936
1924
|
assert(
|
|
1937
1925
|
startSeg !== undefined && endSeg !== undefined,
|
|
1938
1926
|
0xa3f /* segments cannot be undefined */,
|
|
@@ -1940,7 +1928,7 @@ export class MergeTree {
|
|
|
1940
1928
|
|
|
1941
1929
|
obliterate.start = this.createLocalReferencePosition(
|
|
1942
1930
|
startSeg,
|
|
1943
|
-
0,
|
|
1931
|
+
start.side === Side.Before ? 0 : Math.max(startSeg.cachedLength - 1, 0),
|
|
1944
1932
|
ReferenceType.StayOnRemove,
|
|
1945
1933
|
{
|
|
1946
1934
|
obliterate,
|
|
@@ -1949,20 +1937,53 @@ export class MergeTree {
|
|
|
1949
1937
|
|
|
1950
1938
|
obliterate.end = this.createLocalReferencePosition(
|
|
1951
1939
|
endSeg,
|
|
1952
|
-
endSeg.cachedLength - 1,
|
|
1940
|
+
end.side === Side.Before ? 0 : Math.max(endSeg.cachedLength - 1, 0),
|
|
1953
1941
|
ReferenceType.StayOnRemove,
|
|
1954
1942
|
{
|
|
1955
1943
|
obliterate,
|
|
1956
1944
|
},
|
|
1957
1945
|
);
|
|
1958
1946
|
|
|
1947
|
+
// Always create a segment group for obliterate,
|
|
1948
|
+
// even if there are no segments currently in the obliteration range.
|
|
1949
|
+
// Segments may be concurrently inserted into the obliteration range,
|
|
1950
|
+
// at which point they are added to the segment group.
|
|
1951
|
+
obliterate.segmentGroup = {
|
|
1952
|
+
segments: [],
|
|
1953
|
+
localSeq,
|
|
1954
|
+
refSeq: this.collabWindow.currentSeq,
|
|
1955
|
+
obliterateInfo: obliterate,
|
|
1956
|
+
};
|
|
1957
|
+
if (this.collabWindow.collaborating && clientId === this.collabWindow.clientId) {
|
|
1958
|
+
this.pendingSegments.push(obliterate.segmentGroup);
|
|
1959
|
+
}
|
|
1960
|
+
this.obliterates.addOrUpdate(obliterate);
|
|
1961
|
+
|
|
1959
1962
|
const markMoved = (
|
|
1960
1963
|
segment: ISegment,
|
|
1961
1964
|
pos: number,
|
|
1962
1965
|
_start: number,
|
|
1963
1966
|
_end: number,
|
|
1964
1967
|
): boolean => {
|
|
1968
|
+
if (
|
|
1969
|
+
(start.side === Side.After && startPos === pos + segment.cachedLength) || // exclusive start segment
|
|
1970
|
+
(end.side === Side.Before &&
|
|
1971
|
+
endPos === pos &&
|
|
1972
|
+
isSegmentPresent(segment, { refSeq, localSeq })) // exclusive end segment
|
|
1973
|
+
) {
|
|
1974
|
+
// We walk these segments because we want to also walk any concurrently inserted segments between here and the obliterated segments.
|
|
1975
|
+
// These segments are outside of the obliteration range though, so return true to keep walking.
|
|
1976
|
+
return true;
|
|
1977
|
+
}
|
|
1965
1978
|
const existingMoveInfo = toMoveInfo(segment);
|
|
1979
|
+
|
|
1980
|
+
if (segment.prevObliterateByInserter?.seq === UnassignedSequenceNumber) {
|
|
1981
|
+
// We chose to not obliterate this segment because we are aware of an unacked local obliteration.
|
|
1982
|
+
// The local obliterate has not been sequenced yet, so it is still the newest obliterate we are aware of.
|
|
1983
|
+
// Other clients will also choose not to obliterate this segment because the most recent obliteration has the same clientId
|
|
1984
|
+
return true;
|
|
1985
|
+
}
|
|
1986
|
+
|
|
1966
1987
|
if (
|
|
1967
1988
|
clientId !== segment.clientId &&
|
|
1968
1989
|
segment.seq !== undefined &&
|
|
@@ -2013,7 +2034,6 @@ export class MergeTree {
|
|
|
2013
2034
|
obliterate.segmentGroup,
|
|
2014
2035
|
localSeq,
|
|
2015
2036
|
);
|
|
2016
|
-
obliterate.segmentGroup.obliterateInfo ??= obliterate;
|
|
2017
2037
|
} else {
|
|
2018
2038
|
if (MergeTree.options.zamboniSegments) {
|
|
2019
2039
|
this.addToLRUSet(segment, seq);
|
|
@@ -2043,14 +2063,12 @@ export class MergeTree {
|
|
|
2043
2063
|
markMoved,
|
|
2044
2064
|
undefined,
|
|
2045
2065
|
afterMarkMoved,
|
|
2046
|
-
start,
|
|
2047
|
-
end,
|
|
2066
|
+
start.pos,
|
|
2067
|
+
end.pos + 1, // include the segment containing the end reference
|
|
2048
2068
|
undefined,
|
|
2049
2069
|
seq === UnassignedSequenceNumber ? undefined : seq,
|
|
2050
2070
|
);
|
|
2051
2071
|
|
|
2052
|
-
this.obliterates.addOrUpdate(obliterate);
|
|
2053
|
-
|
|
2054
2072
|
this.slideAckedRemovedSegmentReferences(localOverlapWithRefs);
|
|
2055
2073
|
// opArgs == undefined => test code
|
|
2056
2074
|
if (movedSegments.length > 0) {
|
|
@@ -2076,6 +2094,39 @@ export class MergeTree {
|
|
|
2076
2094
|
}
|
|
2077
2095
|
}
|
|
2078
2096
|
|
|
2097
|
+
public obliterateRange(
|
|
2098
|
+
start: number | InteriorSequencePlace,
|
|
2099
|
+
end: number | InteriorSequencePlace,
|
|
2100
|
+
refSeq: number,
|
|
2101
|
+
clientId: number,
|
|
2102
|
+
seq: number,
|
|
2103
|
+
overwrite: boolean = false,
|
|
2104
|
+
opArgs: IMergeTreeDeltaOpArgs,
|
|
2105
|
+
): void {
|
|
2106
|
+
errorIfOptionNotTrue(this.options, "mergeTreeEnableObliterate");
|
|
2107
|
+
if (this.options?.mergeTreeEnableSidedObliterate) {
|
|
2108
|
+
assert(
|
|
2109
|
+
typeof start === "object" && typeof end === "object",
|
|
2110
|
+
"Start and end must be of type InteriorSequencePlace if mergeTreeEnableSidedObliterate is enabled.",
|
|
2111
|
+
);
|
|
2112
|
+
this.obliterateRangeSided(start, end, refSeq, clientId, seq, overwrite, opArgs);
|
|
2113
|
+
} else {
|
|
2114
|
+
assert(
|
|
2115
|
+
typeof start === "number" && typeof end === "number",
|
|
2116
|
+
"Start and end must be numbers if mergeTreeEnableSidedObliterate is not enabled.",
|
|
2117
|
+
);
|
|
2118
|
+
this.obliterateRangeSided(
|
|
2119
|
+
{ pos: start, side: Side.Before },
|
|
2120
|
+
{ pos: end - 1, side: Side.After },
|
|
2121
|
+
refSeq,
|
|
2122
|
+
clientId,
|
|
2123
|
+
seq,
|
|
2124
|
+
overwrite,
|
|
2125
|
+
opArgs,
|
|
2126
|
+
);
|
|
2127
|
+
}
|
|
2128
|
+
}
|
|
2129
|
+
|
|
2079
2130
|
public markRangeRemoved(
|
|
2080
2131
|
start: number,
|
|
2081
2132
|
end: number,
|
|
@@ -2684,28 +2735,18 @@ export class MergeTree {
|
|
|
2684
2735
|
leaf: ISegmentAction<TClientData>,
|
|
2685
2736
|
accum: TClientData,
|
|
2686
2737
|
post?: BlockAction<TClientData>,
|
|
2687
|
-
start:
|
|
2688
|
-
end?:
|
|
2738
|
+
start: number = 0,
|
|
2739
|
+
end?: number,
|
|
2689
2740
|
localSeq?: number,
|
|
2690
2741
|
visibilitySeq: number = refSeq,
|
|
2691
2742
|
): void {
|
|
2692
|
-
const
|
|
2693
|
-
if (
|
|
2743
|
+
const endPos = end ?? this.nodeLength(this.root, refSeq, clientId, localSeq) ?? 0;
|
|
2744
|
+
if (endPos === start) {
|
|
2694
2745
|
return;
|
|
2695
2746
|
}
|
|
2696
2747
|
|
|
2697
2748
|
let pos = 0;
|
|
2698
|
-
let { startPos, endPos } = endpointPosAndSide(start, end);
|
|
2699
2749
|
|
|
2700
|
-
startPos = startPos === "start" || startPos === undefined ? 0 : startPos;
|
|
2701
|
-
endPos =
|
|
2702
|
-
endPos === "end" || endPos === undefined
|
|
2703
|
-
? this.root.mergeTree?.getLength(refSeq, clientId) ?? 0
|
|
2704
|
-
: endPos;
|
|
2705
|
-
assert(
|
|
2706
|
-
startPos !== "end" && endPos !== "start",
|
|
2707
|
-
0x9e3 /* start cannot be 'end' and end cannot be 'start' */,
|
|
2708
|
-
);
|
|
2709
2750
|
depthFirstNodeWalk(
|
|
2710
2751
|
this.root,
|
|
2711
2752
|
this.root.children[0],
|
|
@@ -2733,15 +2774,13 @@ export class MergeTree {
|
|
|
2733
2774
|
|
|
2734
2775
|
const nextPos = pos + lenAtRefSeq;
|
|
2735
2776
|
// start is beyond the current node, so we can skip it
|
|
2736
|
-
if (
|
|
2777
|
+
if (start >= nextPos) {
|
|
2737
2778
|
pos = nextPos;
|
|
2738
2779
|
return NodeAction.Skip;
|
|
2739
2780
|
}
|
|
2740
2781
|
|
|
2741
2782
|
if (node.isLeaf()) {
|
|
2742
|
-
if (
|
|
2743
|
-
leaf(node, pos, refSeq, clientId, startPos - pos, endPos - pos, accum) === false
|
|
2744
|
-
) {
|
|
2783
|
+
if (leaf(node, pos, refSeq, clientId, start - pos, endPos - pos, accum) === false) {
|
|
2745
2784
|
return NodeAction.Exit;
|
|
2746
2785
|
}
|
|
2747
2786
|
pos = nextPos;
|
|
@@ -2751,7 +2790,7 @@ export class MergeTree {
|
|
|
2751
2790
|
post === undefined
|
|
2752
2791
|
? undefined
|
|
2753
2792
|
: (block): boolean =>
|
|
2754
|
-
post(block, pos, refSeq, clientId,
|
|
2793
|
+
post(block, pos, refSeq, clientId, start - pos, endPos - pos, accum),
|
|
2755
2794
|
);
|
|
2756
2795
|
}
|
|
2757
2796
|
}
|
package/src/mergeTreeNodes.ts
CHANGED
|
@@ -30,7 +30,6 @@ import {
|
|
|
30
30
|
import { SegmentGroupCollection } from "./segmentGroupCollection.js";
|
|
31
31
|
// eslint-disable-next-line import/no-deprecated
|
|
32
32
|
import { PropertiesManager, PropertiesRollback } from "./segmentPropertiesManager.js";
|
|
33
|
-
import { Side } from "./sequencePlace.js";
|
|
34
33
|
|
|
35
34
|
/**
|
|
36
35
|
* Common properties for a node in a merge tree.
|
|
@@ -161,6 +160,13 @@ export interface IMoveInfo {
|
|
|
161
160
|
* calculations
|
|
162
161
|
*/
|
|
163
162
|
wasMovedOnInsert: boolean;
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* If a segment is inserted into an obliterated range,
|
|
166
|
+
* but the newest obliteration of that range was by the inserting client,
|
|
167
|
+
* then the segment is not obliterated because it is aware of the latest obliteration.
|
|
168
|
+
*/
|
|
169
|
+
prevObliterateByInserter?: ObliterateInfo;
|
|
164
170
|
}
|
|
165
171
|
|
|
166
172
|
export function toMoveInfo(maybe: Partial<IMoveInfo> | undefined): IMoveInfo | undefined {
|
|
@@ -263,14 +269,6 @@ export interface ISegment extends IMergeNodeCommon, Partial<IRemovalInfo>, Parti
|
|
|
263
269
|
* Properties that have been added to this segment via annotation.
|
|
264
270
|
*/
|
|
265
271
|
properties?: PropertySet;
|
|
266
|
-
/**
|
|
267
|
-
* Stores side information passed to obliterate for the start of a range.
|
|
268
|
-
*/
|
|
269
|
-
startSide?: Side.Before | Side.After;
|
|
270
|
-
/**
|
|
271
|
-
* Stores side information passed to obliterate for the end of a range.
|
|
272
|
-
*/
|
|
273
|
-
endSide?: Side.Before | Side.After;
|
|
274
272
|
|
|
275
273
|
/**
|
|
276
274
|
* Add properties to this segment via annotation.
|
|
@@ -670,7 +668,8 @@ export abstract class BaseSegment implements ISegment {
|
|
|
670
668
|
return false;
|
|
671
669
|
}
|
|
672
670
|
|
|
673
|
-
case MergeTreeDeltaType.OBLITERATE:
|
|
671
|
+
case MergeTreeDeltaType.OBLITERATE:
|
|
672
|
+
case MergeTreeDeltaType.OBLITERATE_SIDED: {
|
|
674
673
|
const moveInfo: IMoveInfo | undefined = toMoveInfo(this);
|
|
675
674
|
assert(moveInfo !== undefined, 0x86e /* On obliterate ack, missing move info! */);
|
|
676
675
|
const obliterateInfo = segmentGroup.obliterateInfo;
|