@fluidframework/merge-tree 0.59.4002 → 1.0.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/.eslintrc.js +1 -1
- package/README.md +1 -1
- package/REFERENCEPOSITIONS.md +127 -0
- package/dist/client.d.ts +17 -0
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +47 -41
- package/dist/client.js.map +1 -1
- package/dist/collections.d.ts +5 -4
- package/dist/collections.d.ts.map +1 -1
- package/dist/collections.js +17 -18
- package/dist/collections.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -1
- package/dist/index.js.map +1 -1
- package/dist/localReference.d.ts +4 -0
- package/dist/localReference.d.ts.map +1 -1
- package/dist/localReference.js +24 -3
- package/dist/localReference.js.map +1 -1
- package/dist/mergeTree.d.ts +20 -0
- package/dist/mergeTree.d.ts.map +1 -1
- package/dist/mergeTree.js +139 -48
- package/dist/mergeTree.js.map +1 -1
- package/dist/mergeTreeDeltaCallback.d.ts +8 -10
- package/dist/mergeTreeDeltaCallback.d.ts.map +1 -1
- package/dist/mergeTreeDeltaCallback.js +6 -10
- package/dist/mergeTreeDeltaCallback.js.map +1 -1
- package/dist/opBuilder.js +6 -5
- package/dist/opBuilder.js.map +1 -1
- package/dist/ops.d.ts +12 -10
- package/dist/ops.d.ts.map +1 -1
- package/dist/ops.js +7 -7
- package/dist/ops.js.map +1 -1
- package/dist/referencePositions.d.ts +1 -1
- package/dist/referencePositions.d.ts.map +1 -1
- package/dist/referencePositions.js +3 -2
- package/dist/referencePositions.js.map +1 -1
- package/lib/client.d.ts +17 -0
- package/lib/client.d.ts.map +1 -1
- package/lib/client.js +47 -41
- package/lib/client.js.map +1 -1
- package/lib/collections.d.ts +5 -4
- package/lib/collections.d.ts.map +1 -1
- package/lib/collections.js +17 -18
- package/lib/collections.js.map +1 -1
- package/lib/index.d.ts +1 -1
- package/lib/index.d.ts.map +1 -1
- package/lib/index.js +1 -1
- package/lib/index.js.map +1 -1
- package/lib/localReference.d.ts +4 -0
- package/lib/localReference.d.ts.map +1 -1
- package/lib/localReference.js +22 -2
- package/lib/localReference.js.map +1 -1
- package/lib/mergeTree.d.ts +20 -0
- package/lib/mergeTree.d.ts.map +1 -1
- package/lib/mergeTree.js +140 -49
- package/lib/mergeTree.js.map +1 -1
- package/lib/mergeTreeDeltaCallback.d.ts +8 -10
- package/lib/mergeTreeDeltaCallback.d.ts.map +1 -1
- package/lib/mergeTreeDeltaCallback.js +6 -10
- package/lib/mergeTreeDeltaCallback.js.map +1 -1
- package/lib/opBuilder.js +6 -5
- package/lib/opBuilder.js.map +1 -1
- package/lib/ops.d.ts +12 -10
- package/lib/ops.d.ts.map +1 -1
- package/lib/ops.js +7 -7
- package/lib/ops.js.map +1 -1
- package/lib/referencePositions.d.ts +1 -1
- package/lib/referencePositions.d.ts.map +1 -1
- package/lib/referencePositions.js +3 -2
- package/lib/referencePositions.js.map +1 -1
- package/package.json +69 -74
- package/src/client.ts +27 -18
- package/src/collections.ts +5 -4
- package/src/index.ts +1 -1
- package/src/localReference.ts +24 -2
- package/src/mergeTree.ts +133 -39
- package/src/mergeTreeDeltaCallback.ts +8 -10
- package/src/ops.ts +13 -10
- package/src/referencePositions.ts +3 -2
package/src/client.ts
CHANGED
|
@@ -16,7 +16,7 @@ import { LoggingError } from "@fluidframework/telemetry-utils";
|
|
|
16
16
|
import { IIntegerRange } from "./base";
|
|
17
17
|
import { RedBlackTree } from "./collections";
|
|
18
18
|
import { UnassignedSequenceNumber, UniversalSequenceNumber } from "./constants";
|
|
19
|
-
import { LocalReference } from "./localReference";
|
|
19
|
+
import { LocalReference, _validateReferenceType } from "./localReference";
|
|
20
20
|
import {
|
|
21
21
|
CollaborationWindow,
|
|
22
22
|
compareStrings,
|
|
@@ -575,13 +575,14 @@ export class Client {
|
|
|
575
575
|
|
|
576
576
|
/**
|
|
577
577
|
* Gets the client args from the op if remote, otherwise uses the local clients info
|
|
578
|
-
* @param
|
|
578
|
+
* @param sequencedMessage - The sequencedMessage to get the client sequence args for
|
|
579
579
|
*/
|
|
580
|
-
|
|
580
|
+
private getClientSequenceArgsForMessage(sequencedMessage: ISequencedDocumentMessage | undefined):
|
|
581
|
+
IMergeTreeClientSequenceArgs {
|
|
581
582
|
// If there this no sequenced message, then the op is local
|
|
582
583
|
// and unacked, so use this clients sequenced args
|
|
583
584
|
//
|
|
584
|
-
if (!
|
|
585
|
+
if (!sequencedMessage) {
|
|
585
586
|
const segWindow = this.getCollabWindow();
|
|
586
587
|
return {
|
|
587
588
|
clientId: segWindow.clientId,
|
|
@@ -590,13 +591,21 @@ export class Client {
|
|
|
590
591
|
};
|
|
591
592
|
} else {
|
|
592
593
|
return {
|
|
593
|
-
clientId: this.
|
|
594
|
-
referenceSequenceNumber:
|
|
595
|
-
sequenceNumber:
|
|
594
|
+
clientId: this.getOrAddShortClientId(sequencedMessage.clientId),
|
|
595
|
+
referenceSequenceNumber: sequencedMessage.referenceSequenceNumber,
|
|
596
|
+
sequenceNumber: sequencedMessage.sequenceNumber,
|
|
596
597
|
};
|
|
597
598
|
}
|
|
598
599
|
}
|
|
599
600
|
|
|
601
|
+
/**
|
|
602
|
+
* Gets the client args from the op if remote, otherwise uses the local clients info
|
|
603
|
+
* @param opArgs - The op arg to get the client sequence args for
|
|
604
|
+
*/
|
|
605
|
+
private getClientSequenceArgs(opArgs: IMergeTreeDeltaOpArgs): IMergeTreeClientSequenceArgs {
|
|
606
|
+
return this.getClientSequenceArgsForMessage(opArgs.sequencedMessage);
|
|
607
|
+
}
|
|
608
|
+
|
|
600
609
|
private ackPendingSegment(opArgs: IMergeTreeDeltaOpArgs) {
|
|
601
610
|
const ackOp = (deltaOpArgs: IMergeTreeDeltaOpArgs) => {
|
|
602
611
|
let trace: Trace | undefined;
|
|
@@ -1021,17 +1030,17 @@ export class Client {
|
|
|
1021
1030
|
}
|
|
1022
1031
|
|
|
1023
1032
|
getContainingSegment<T extends ISegment>(pos: number, op?: ISequencedDocumentMessage) {
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
return this.mergeTree.
|
|
1033
|
+
const args = this.getClientSequenceArgsForMessage(op);
|
|
1034
|
+
return this.mergeTree.getContainingSegment<T>(pos, args.referenceSequenceNumber, args.clientId);
|
|
1035
|
+
}
|
|
1036
|
+
|
|
1037
|
+
/**
|
|
1038
|
+
* Returns the position to slide a reference to if a slide is required.
|
|
1039
|
+
* @param segoff - The segment and offset to slide from
|
|
1040
|
+
* @returns - segment and offset to slide the reference to
|
|
1041
|
+
*/
|
|
1042
|
+
getSlideToSegment(segoff: { segment: ISegment | undefined; offset: number | undefined; }) {
|
|
1043
|
+
return this.mergeTree._getSlideToSegment(segoff);
|
|
1035
1044
|
}
|
|
1036
1045
|
|
|
1037
1046
|
getPropertiesAtPosition(pos: number) {
|
package/src/collections.ts
CHANGED
|
@@ -245,10 +245,11 @@ export class Heap<T> {
|
|
|
245
245
|
/* eslint-enable no-bitwise */
|
|
246
246
|
}
|
|
247
247
|
|
|
248
|
-
export const
|
|
249
|
-
RED,
|
|
250
|
-
BLACK,
|
|
251
|
-
}
|
|
248
|
+
export const RBColor = {
|
|
249
|
+
RED: 0,
|
|
250
|
+
BLACK: 1,
|
|
251
|
+
} as const;
|
|
252
|
+
export type RBColor = typeof RBColor[keyof typeof RBColor];
|
|
252
253
|
|
|
253
254
|
export interface RBNode<TKey, TData> {
|
|
254
255
|
key: TKey;
|
package/src/index.ts
CHANGED
|
@@ -7,7 +7,7 @@ export * from "./base";
|
|
|
7
7
|
export * from "./client";
|
|
8
8
|
export * from "./collections";
|
|
9
9
|
export * from "./constants";
|
|
10
|
-
export
|
|
10
|
+
export { LocalReference, LocalReferenceCollection } from "./localReference";
|
|
11
11
|
export * from "./mergeTree";
|
|
12
12
|
export * from "./mergeTreeDeltaCallback";
|
|
13
13
|
export * from "./mergeTreeTracking";
|
package/src/localReference.ts
CHANGED
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
import { assert } from "@fluidframework/common-utils";
|
|
7
|
+
import { UsageError } from "@fluidframework/container-utils";
|
|
7
8
|
import { Client } from "./client";
|
|
8
9
|
import { List, ListMakeHead, ListRemoveEntry } from "./collections";
|
|
9
10
|
import {
|
|
@@ -25,6 +26,26 @@ import {
|
|
|
25
26
|
refTypeIncludesFlag,
|
|
26
27
|
} from "./referencePositions";
|
|
27
28
|
|
|
29
|
+
/**
|
|
30
|
+
* @internal
|
|
31
|
+
*/
|
|
32
|
+
export function _validateReferenceType(refType: ReferenceType) {
|
|
33
|
+
let exclusiveCount = 0;
|
|
34
|
+
if (refTypeIncludesFlag(refType, ReferenceType.Transient)) {
|
|
35
|
+
++exclusiveCount;
|
|
36
|
+
}
|
|
37
|
+
if (refTypeIncludesFlag(refType, ReferenceType.SlideOnRemove)) {
|
|
38
|
+
++exclusiveCount;
|
|
39
|
+
}
|
|
40
|
+
if (refTypeIncludesFlag(refType, ReferenceType.StayOnRemove)) {
|
|
41
|
+
++exclusiveCount;
|
|
42
|
+
}
|
|
43
|
+
if (exclusiveCount > 1) {
|
|
44
|
+
throw new UsageError(
|
|
45
|
+
"Reference types can only be one of Transient, SlideOnRemove, and StayOnRemove");
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
28
49
|
/**
|
|
29
50
|
* @deprecated - Use ReferencePosition
|
|
30
51
|
*/
|
|
@@ -57,6 +78,7 @@ export class LocalReference implements ReferencePosition {
|
|
|
57
78
|
public refType = ReferenceType.Simple,
|
|
58
79
|
properties?: PropertySet,
|
|
59
80
|
) {
|
|
81
|
+
_validateReferenceType(refType);
|
|
60
82
|
this.segment = initSegment;
|
|
61
83
|
this.properties = properties;
|
|
62
84
|
}
|
|
@@ -312,8 +334,8 @@ export class LocalReferenceCollection {
|
|
|
312
334
|
!refTypeIncludesFlag(lref, ReferenceType.Transient),
|
|
313
335
|
0x2df /* "transient references cannot be bound to segments" */);
|
|
314
336
|
assertLocalReferences(lref);
|
|
315
|
-
const refsAtOffset = this.refsByOffset[lref.
|
|
316
|
-
this.refsByOffset[lref.
|
|
337
|
+
const refsAtOffset = this.refsByOffset[lref.offset] =
|
|
338
|
+
this.refsByOffset[lref.offset]
|
|
317
339
|
?? { at: ListMakeHead() };
|
|
318
340
|
const atRefs = refsAtOffset.at =
|
|
319
341
|
refsAtOffset.at
|
package/src/mergeTree.ts
CHANGED
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
/* eslint-disable @typescript-eslint/prefer-optional-chain, no-bitwise */
|
|
10
10
|
|
|
11
11
|
import { assert } from "@fluidframework/common-utils";
|
|
12
|
+
import { UsageError } from "@fluidframework/container-utils";
|
|
12
13
|
import {
|
|
13
14
|
Comparer,
|
|
14
15
|
Heap,
|
|
@@ -109,6 +110,15 @@ export function toRemovalInfo(maybe: Partial<IRemovalInfo> | undefined): IRemova
|
|
|
109
110
|
0x2bf /* "both removedClientIds and removedSeq should be set or not set" */);
|
|
110
111
|
}
|
|
111
112
|
|
|
113
|
+
function isRemoved(segment: ISegment): boolean {
|
|
114
|
+
return toRemovalInfo(segment) !== undefined;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
function isRemovedAndAcked(segment: ISegment): boolean {
|
|
118
|
+
const removalInfo = toRemovalInfo(segment);
|
|
119
|
+
return removalInfo !== undefined && removalInfo.removedSeq !== UnassignedSequenceNumber;
|
|
120
|
+
}
|
|
121
|
+
|
|
112
122
|
/**
|
|
113
123
|
* A segment representing a portion of the merge tree.
|
|
114
124
|
*/
|
|
@@ -554,7 +564,6 @@ export abstract class BaseSegment extends MergeNode implements ISegment {
|
|
|
554
564
|
return true;
|
|
555
565
|
|
|
556
566
|
case MergeTreeDeltaType.REMOVE:
|
|
557
|
-
|
|
558
567
|
const removalInfo: IRemovalInfo | undefined = toRemovalInfo(this);
|
|
559
568
|
assert(removalInfo !== undefined, 0x046 /* "On remove ack, missing removal info!" */);
|
|
560
569
|
this.localRemovedSeq = undefined;
|
|
@@ -562,7 +571,6 @@ export abstract class BaseSegment extends MergeNode implements ISegment {
|
|
|
562
571
|
removalInfo.removedSeq = opArgs.sequencedMessage!.sequenceNumber;
|
|
563
572
|
return true;
|
|
564
573
|
}
|
|
565
|
-
|
|
566
574
|
return false;
|
|
567
575
|
|
|
568
576
|
default:
|
|
@@ -1414,6 +1422,109 @@ export class MergeTree {
|
|
|
1414
1422
|
return { segment, offset };
|
|
1415
1423
|
}
|
|
1416
1424
|
|
|
1425
|
+
/**
|
|
1426
|
+
* @internal must only be used by client
|
|
1427
|
+
* @param segoff - The segment and offset to slide from
|
|
1428
|
+
* @returns The segment and offset to slide to
|
|
1429
|
+
*/
|
|
1430
|
+
public _getSlideToSegment(segoff: { segment: ISegment | undefined; offset: number | undefined; }) {
|
|
1431
|
+
if (!segoff.segment || !isRemovedAndAcked(segoff.segment)) {
|
|
1432
|
+
return segoff;
|
|
1433
|
+
}
|
|
1434
|
+
// Slide to the next farthest valid segment in the tree. If no such segment is found
|
|
1435
|
+
// slide to the last valid segment.
|
|
1436
|
+
// TODO this walks the whole tree to find the segment - could write a more efficient
|
|
1437
|
+
// walk that starts at the segment
|
|
1438
|
+
let foundStart = false;
|
|
1439
|
+
let foundSegmentPastStart = false;
|
|
1440
|
+
let slideToSegment: ISegment | undefined;
|
|
1441
|
+
this.walkAllSegments(this.root, (seg) => {
|
|
1442
|
+
if (seg.seq !== UnassignedSequenceNumber && !isRemovedAndAcked(seg)) {
|
|
1443
|
+
slideToSegment = seg;
|
|
1444
|
+
if (foundStart) {
|
|
1445
|
+
foundSegmentPastStart = true;
|
|
1446
|
+
return false;
|
|
1447
|
+
}
|
|
1448
|
+
}
|
|
1449
|
+
if (!foundStart && seg === segoff.segment) {
|
|
1450
|
+
foundStart = true;
|
|
1451
|
+
}
|
|
1452
|
+
return true;
|
|
1453
|
+
});
|
|
1454
|
+
let offset = 0;
|
|
1455
|
+
if (slideToSegment && !foundSegmentPastStart) {
|
|
1456
|
+
// If slid nearer then offset should be at the end of the segment
|
|
1457
|
+
offset = slideToSegment.cachedLength - 1;
|
|
1458
|
+
}
|
|
1459
|
+
return { segment: slideToSegment, offset };
|
|
1460
|
+
}
|
|
1461
|
+
|
|
1462
|
+
/**
|
|
1463
|
+
* This method should only be called when the current client sequence number is
|
|
1464
|
+
* max(remove segment sequence number, add reference sequence number).
|
|
1465
|
+
* Otherwise eventual consistency is not guaranteed.
|
|
1466
|
+
* See `packages\dds\merge-tree\REFERENCEPOSITIONS.md`
|
|
1467
|
+
*/
|
|
1468
|
+
private slideReferences(segment: ISegment, refsToSlide: LocalReference[]) {
|
|
1469
|
+
assert(
|
|
1470
|
+
isRemovedAndAcked(segment),
|
|
1471
|
+
0x2f1 /* slideReferences from a segment which has not been removed and acked */);
|
|
1472
|
+
assert(!!segment.localRefs, 0x2f2 /* Ref not in the segment localRefs */);
|
|
1473
|
+
const newSegoff = this._getSlideToSegment({ segment, offset: 0 });
|
|
1474
|
+
const newSegment = newSegoff.segment;
|
|
1475
|
+
if (newSegment && !newSegment.localRefs) {
|
|
1476
|
+
newSegment.localRefs = new LocalReferenceCollection(newSegment);
|
|
1477
|
+
}
|
|
1478
|
+
for (const ref of refsToSlide) {
|
|
1479
|
+
const removedRef = segment.localRefs.removeLocalRef(ref);
|
|
1480
|
+
assert(ref === removedRef, 0x2f3 /* Ref not in the segment localRefs */);
|
|
1481
|
+
if (!newSegment) {
|
|
1482
|
+
// No valid segments (all nodes removed or not yet created)
|
|
1483
|
+
ref.segment = undefined;
|
|
1484
|
+
ref.offset = 0;
|
|
1485
|
+
} else {
|
|
1486
|
+
ref.segment = newSegment;
|
|
1487
|
+
ref.offset = newSegoff.offset ?? 0;
|
|
1488
|
+
assert(!!newSegment.localRefs, 0x2f4 /* localRefs must be allocated */);
|
|
1489
|
+
newSegment.localRefs.addLocalRef(ref);
|
|
1490
|
+
}
|
|
1491
|
+
}
|
|
1492
|
+
// TODO is it required to update the path lengths?
|
|
1493
|
+
if (newSegment) {
|
|
1494
|
+
this.blockUpdatePathLengths(newSegment.parent, TreeMaintenanceSequenceNumber,
|
|
1495
|
+
LocalClientId);
|
|
1496
|
+
}
|
|
1497
|
+
}
|
|
1498
|
+
|
|
1499
|
+
private updateSegmentRefsAfterMarkRemoved(segment: ISegment, pending: boolean) {
|
|
1500
|
+
if (!segment.localRefs || segment.localRefs.empty) {
|
|
1501
|
+
return;
|
|
1502
|
+
}
|
|
1503
|
+
const refsToSlide: LocalReference[] = [];
|
|
1504
|
+
const refsToStay: LocalReference[] = [];
|
|
1505
|
+
for (const lref of segment.localRefs) {
|
|
1506
|
+
if (refTypeIncludesFlag(lref, ReferenceType.StayOnRemove)) {
|
|
1507
|
+
refsToStay.push(lref);
|
|
1508
|
+
} else if (refTypeIncludesFlag(lref, ReferenceType.SlideOnRemove)) {
|
|
1509
|
+
if (pending) {
|
|
1510
|
+
refsToStay.push(lref);
|
|
1511
|
+
} else {
|
|
1512
|
+
refsToSlide.push(lref);
|
|
1513
|
+
}
|
|
1514
|
+
}
|
|
1515
|
+
}
|
|
1516
|
+
// Rethink implementation of keeping and sliding refs once other reference
|
|
1517
|
+
// changes are complete. This works but is fragile and possibly slow.
|
|
1518
|
+
if (!pending) {
|
|
1519
|
+
this.slideReferences(segment, refsToSlide);
|
|
1520
|
+
}
|
|
1521
|
+
segment.localRefs.clear();
|
|
1522
|
+
for (const lref of refsToStay) {
|
|
1523
|
+
lref.segment = segment;
|
|
1524
|
+
segment.localRefs.addLocalRef(lref);
|
|
1525
|
+
}
|
|
1526
|
+
}
|
|
1527
|
+
|
|
1417
1528
|
private blockLength(node: IMergeBlock, refSeq: number, clientId: number) {
|
|
1418
1529
|
if ((this.collabWindow.collaborating) && (clientId !== this.collabWindow.clientId)) {
|
|
1419
1530
|
return node.partialLengths!.getPartialLength(refSeq, clientId);
|
|
@@ -1676,7 +1787,13 @@ export class MergeTree {
|
|
|
1676
1787
|
if (pendingSegmentGroup !== undefined) {
|
|
1677
1788
|
const deltaSegments: IMergeTreeSegmentDelta[] = [];
|
|
1678
1789
|
pendingSegmentGroup.segments.map((pendingSegment) => {
|
|
1679
|
-
|
|
1790
|
+
const modified = pendingSegment.ack(pendingSegmentGroup, opArgs, this);
|
|
1791
|
+
// This computation of overwrite appears incorrect. Leaving as is to avoid breaking something.
|
|
1792
|
+
overwrite = !modified || overwrite;
|
|
1793
|
+
|
|
1794
|
+
if (modified && opArgs.op.type === MergeTreeDeltaType.REMOVE) {
|
|
1795
|
+
this.updateSegmentRefsAfterMarkRemoved(pendingSegment, false);
|
|
1796
|
+
}
|
|
1680
1797
|
if (MergeTree.options.zamboniSegments) {
|
|
1681
1798
|
this.addToLRUSet(pendingSegment, seq);
|
|
1682
1799
|
}
|
|
@@ -2338,7 +2455,7 @@ export class MergeTree {
|
|
|
2338
2455
|
this.ensureIntervalBoundary(end, refSeq, clientId);
|
|
2339
2456
|
let segmentGroup: SegmentGroup;
|
|
2340
2457
|
const removedSegments: IMergeTreeSegmentDelta[] = [];
|
|
2341
|
-
const
|
|
2458
|
+
const segmentsWithRefs: ISegment[] = [];
|
|
2342
2459
|
const localSeq = seq === UnassignedSequenceNumber ? ++this.collabWindow.localSeq : undefined;
|
|
2343
2460
|
const markRemoved = (segment: ISegment, pos: number, _start: number, _end: number) => {
|
|
2344
2461
|
const existingRemovalInfo = toRemovalInfo(segment);
|
|
@@ -2362,10 +2479,9 @@ export class MergeTree {
|
|
|
2362
2479
|
segment.localRemovedSeq = localSeq;
|
|
2363
2480
|
|
|
2364
2481
|
removedSegments.push({ segment });
|
|
2365
|
-
|
|
2366
|
-
|
|
2367
|
-
|
|
2368
|
-
segment.localRefs = undefined;
|
|
2482
|
+
}
|
|
2483
|
+
if (segment.localRefs && !segment.localRefs.empty) {
|
|
2484
|
+
segmentsWithRefs.push(segment);
|
|
2369
2485
|
}
|
|
2370
2486
|
|
|
2371
2487
|
// Save segment so can assign removed sequence number when acked by server
|
|
@@ -2389,37 +2505,9 @@ export class MergeTree {
|
|
|
2389
2505
|
return true;
|
|
2390
2506
|
};
|
|
2391
2507
|
this.mapRange({ leaf: markRemoved, post: afterMarkRemoved }, refSeq, clientId, undefined, start, end);
|
|
2392
|
-
|
|
2393
|
-
|
|
2394
|
-
|
|
2395
|
-
if (start < length) {
|
|
2396
|
-
const afterSegOff = this.getContainingSegment(start, refSeq, clientId);
|
|
2397
|
-
refSegment = afterSegOff.segment;
|
|
2398
|
-
assert(!!refSegment, 0x052 /* "Missing reference segment!" */);
|
|
2399
|
-
if (!refSegment.localRefs) {
|
|
2400
|
-
refSegment.localRefs = new LocalReferenceCollection(refSegment);
|
|
2401
|
-
}
|
|
2402
|
-
refSegment.localRefs.addBeforeTombstones(...savedLocalRefs);
|
|
2403
|
-
} else if (length > 0) {
|
|
2404
|
-
const beforeSegOff = this.getContainingSegment(length - 1, refSeq, clientId);
|
|
2405
|
-
refSegment = beforeSegOff.segment;
|
|
2406
|
-
assert(!!refSegment, 0x053 /* "Missing reference segment!" */);
|
|
2407
|
-
if (!refSegment.localRefs) {
|
|
2408
|
-
refSegment.localRefs = new LocalReferenceCollection(refSegment);
|
|
2409
|
-
}
|
|
2410
|
-
refSegment.localRefs.addAfterTombstones(...savedLocalRefs);
|
|
2411
|
-
} else {
|
|
2412
|
-
// TODO: The tree is empty, so there isn't anywhere to put these
|
|
2413
|
-
// they should be preserved somehow
|
|
2414
|
-
for (const refsCollection of savedLocalRefs) {
|
|
2415
|
-
refsCollection.clear();
|
|
2416
|
-
}
|
|
2417
|
-
}
|
|
2418
|
-
|
|
2419
|
-
if (refSegment) {
|
|
2420
|
-
this.blockUpdatePathLengths(refSegment.parent, TreeMaintenanceSequenceNumber,
|
|
2421
|
-
LocalClientId);
|
|
2422
|
-
}
|
|
2508
|
+
const pending = this.collabWindow.collaborating && clientId === this.collabWindow.clientId;
|
|
2509
|
+
for (const segment of segmentsWithRefs) {
|
|
2510
|
+
this.updateSegmentRefsAfterMarkRemoved(segment, pending);
|
|
2423
2511
|
}
|
|
2424
2512
|
|
|
2425
2513
|
// opArgs == undefined => test code
|
|
@@ -2460,6 +2548,12 @@ export class MergeTree {
|
|
|
2460
2548
|
segment: ISegment, offset: number, refType: ReferenceType, properties: PropertySet | undefined,
|
|
2461
2549
|
client: Client,
|
|
2462
2550
|
): ReferencePosition {
|
|
2551
|
+
if (isRemoved(segment)) {
|
|
2552
|
+
if (!refTypeIncludesFlag(refType, ReferenceType.SlideOnRemove | ReferenceType.Transient)) {
|
|
2553
|
+
throw new UsageError(
|
|
2554
|
+
"Can only create SlideOnRemove or Transient local reference position on a removed segment");
|
|
2555
|
+
}
|
|
2556
|
+
}
|
|
2463
2557
|
const localRefs = segment.localRefs ?? new LocalReferenceCollection(segment);
|
|
2464
2558
|
segment.localRefs = localRefs;
|
|
2465
2559
|
|
|
@@ -13,28 +13,26 @@ import { PropertySet } from "./properties";
|
|
|
13
13
|
import { ISegment } from "./mergeTree";
|
|
14
14
|
|
|
15
15
|
export type MergeTreeDeltaOperationType =
|
|
16
|
-
MergeTreeDeltaType.ANNOTATE | MergeTreeDeltaType.INSERT | MergeTreeDeltaType.REMOVE;
|
|
16
|
+
typeof MergeTreeDeltaType.ANNOTATE | typeof MergeTreeDeltaType.INSERT | typeof MergeTreeDeltaType.REMOVE;
|
|
17
17
|
|
|
18
18
|
// Note: Assigned negative integers to avoid clashing with MergeTreeDeltaType
|
|
19
|
-
export const
|
|
20
|
-
APPEND
|
|
21
|
-
SPLIT
|
|
19
|
+
export const MergeTreeMaintenanceType = {
|
|
20
|
+
APPEND: -1,
|
|
21
|
+
SPLIT: -2,
|
|
22
22
|
/**
|
|
23
23
|
* Notification that a segment has been unlinked from the MergeTree. This occurs during
|
|
24
24
|
* Zamboni when:
|
|
25
25
|
*
|
|
26
|
-
* a) The minSeq has moved past the segment's removeSeq, in which case the segment
|
|
27
|
-
* can no longer be referenced by incoming remote ops, and...
|
|
28
|
-
*
|
|
29
26
|
* b) The segment's tracking collection is empty (e.g., not being tracked for undo/redo).
|
|
30
27
|
*/
|
|
31
|
-
UNLINK
|
|
28
|
+
UNLINK: -3,
|
|
32
29
|
/**
|
|
33
30
|
* Notification that a local change has been acknowledged by the server.
|
|
34
31
|
* This means that it has made the round trip to the server and has had a sequence number assigned.
|
|
35
32
|
*/
|
|
36
|
-
ACKNOWLEDGED
|
|
37
|
-
}
|
|
33
|
+
ACKNOWLEDGED: -4,
|
|
34
|
+
} as const;
|
|
35
|
+
export type MergeTreeMaintenanceType = typeof MergeTreeMaintenanceType[keyof typeof MergeTreeMaintenanceType];
|
|
38
36
|
|
|
39
37
|
export type MergeTreeDeltaOperationTypes = MergeTreeDeltaOperationType | MergeTreeMaintenanceType;
|
|
40
38
|
|
package/src/ops.ts
CHANGED
|
@@ -11,6 +11,7 @@ export enum ReferenceType {
|
|
|
11
11
|
RangeBegin = 0x10,
|
|
12
12
|
RangeEnd = 0x20,
|
|
13
13
|
SlideOnRemove = 0x40,
|
|
14
|
+
StayOnRemove = 0x80,
|
|
14
15
|
Transient = 0x100,
|
|
15
16
|
}
|
|
16
17
|
|
|
@@ -19,12 +20,14 @@ export interface IMarkerDef {
|
|
|
19
20
|
}
|
|
20
21
|
|
|
21
22
|
// Note: Assigned positive integers to avoid clashing with MergeTreeMaintenanceType
|
|
22
|
-
export const
|
|
23
|
-
INSERT
|
|
24
|
-
REMOVE
|
|
25
|
-
ANNOTATE
|
|
26
|
-
GROUP
|
|
27
|
-
}
|
|
23
|
+
export const MergeTreeDeltaType = {
|
|
24
|
+
INSERT: 0,
|
|
25
|
+
REMOVE: 1,
|
|
26
|
+
ANNOTATE: 2,
|
|
27
|
+
GROUP: 3,
|
|
28
|
+
} as const;
|
|
29
|
+
|
|
30
|
+
export type MergeTreeDeltaType = typeof MergeTreeDeltaType[keyof typeof MergeTreeDeltaType];
|
|
28
31
|
|
|
29
32
|
export interface IMergeTreeDelta {
|
|
30
33
|
/**
|
|
@@ -54,7 +57,7 @@ export interface IRelativePosition {
|
|
|
54
57
|
}
|
|
55
58
|
|
|
56
59
|
export interface IMergeTreeInsertMsg extends IMergeTreeDelta {
|
|
57
|
-
type: MergeTreeDeltaType.INSERT;
|
|
60
|
+
type: typeof MergeTreeDeltaType.INSERT;
|
|
58
61
|
pos1?: number;
|
|
59
62
|
relativePos1?: IRelativePosition;
|
|
60
63
|
pos2?: number;
|
|
@@ -63,7 +66,7 @@ export interface IMergeTreeInsertMsg extends IMergeTreeDelta {
|
|
|
63
66
|
}
|
|
64
67
|
|
|
65
68
|
export interface IMergeTreeRemoveMsg extends IMergeTreeDelta {
|
|
66
|
-
type: MergeTreeDeltaType.REMOVE;
|
|
69
|
+
type: typeof MergeTreeDeltaType.REMOVE;
|
|
67
70
|
pos1?: number;
|
|
68
71
|
relativePos1?: IRelativePosition;
|
|
69
72
|
pos2?: number;
|
|
@@ -78,7 +81,7 @@ export interface ICombiningOp {
|
|
|
78
81
|
}
|
|
79
82
|
|
|
80
83
|
export interface IMergeTreeAnnotateMsg extends IMergeTreeDelta {
|
|
81
|
-
type: MergeTreeDeltaType.ANNOTATE;
|
|
84
|
+
type: typeof MergeTreeDeltaType.ANNOTATE;
|
|
82
85
|
pos1?: number;
|
|
83
86
|
relativePos1?: IRelativePosition;
|
|
84
87
|
pos2?: number;
|
|
@@ -88,7 +91,7 @@ export interface IMergeTreeAnnotateMsg extends IMergeTreeDelta {
|
|
|
88
91
|
}
|
|
89
92
|
|
|
90
93
|
export interface IMergeTreeGroupMsg extends IMergeTreeDelta {
|
|
91
|
-
type: MergeTreeDeltaType.GROUP;
|
|
94
|
+
type: typeof MergeTreeDeltaType.GROUP;
|
|
92
95
|
ops: IMergeTreeDeltaOp[];
|
|
93
96
|
}
|
|
94
97
|
|
|
@@ -11,9 +11,10 @@ import { PropertySet, MapLike } from "./properties";
|
|
|
11
11
|
export const reservedTileLabelsKey = "referenceTileLabels";
|
|
12
12
|
export const reservedRangeLabelsKey = "referenceRangeLabels";
|
|
13
13
|
|
|
14
|
-
export function refTypeIncludesFlag(
|
|
14
|
+
export function refTypeIncludesFlag(refPosOrType: ReferencePosition | ReferenceType, flags: ReferenceType): boolean {
|
|
15
|
+
const refType = typeof refPosOrType === "number" ? refPosOrType : refPosOrType.refType;
|
|
15
16
|
// eslint-disable-next-line no-bitwise
|
|
16
|
-
return (
|
|
17
|
+
return (refType & flags) !== 0;
|
|
17
18
|
}
|
|
18
19
|
|
|
19
20
|
export const refGetTileLabels = (refPos: ReferencePosition): string[] | undefined =>
|