@fluidframework/merge-tree 1.2.6 → 2.0.0-dev.1.3.0.96595
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.mocharc.js +12 -0
- package/README.md +2 -2
- package/dist/MergeTreeTextHelper.d.ts +23 -0
- package/dist/MergeTreeTextHelper.d.ts.map +1 -0
- package/dist/MergeTreeTextHelper.js +133 -0
- package/dist/MergeTreeTextHelper.js.map +1 -0
- package/dist/base.d.ts +2 -26
- package/dist/base.d.ts.map +1 -1
- package/dist/base.js.map +1 -1
- package/dist/client.d.ts +27 -16
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +81 -101
- package/dist/client.js.map +1 -1
- package/dist/collections/heap.d.ts +28 -0
- package/dist/collections/heap.d.ts.map +1 -0
- package/dist/collections/heap.js +65 -0
- package/dist/collections/heap.js.map +1 -0
- package/dist/collections/index.d.ts +11 -0
- package/dist/collections/index.d.ts.map +1 -0
- package/dist/collections/index.js +23 -0
- package/dist/collections/index.js.map +1 -0
- package/dist/collections/intervalTree.d.ts +60 -0
- package/dist/collections/intervalTree.d.ts.map +1 -0
- package/dist/collections/intervalTree.js +99 -0
- package/dist/collections/intervalTree.js.map +1 -0
- package/dist/collections/list.d.ts +39 -0
- package/dist/collections/list.d.ts.map +1 -0
- package/dist/collections/list.js +155 -0
- package/dist/collections/list.js.map +1 -0
- package/dist/collections/rbTree.d.ts +154 -0
- package/dist/collections/rbTree.d.ts.map +1 -0
- package/dist/{collections.js → collections/rbTree.js} +15 -478
- package/dist/collections/rbTree.js.map +1 -0
- package/dist/collections/stack.d.ts +16 -0
- package/dist/collections/stack.d.ts.map +1 -0
- package/dist/collections/stack.js +30 -0
- package/dist/collections/stack.js.map +1 -0
- package/dist/collections/tst.d.ts +55 -0
- package/dist/collections/tst.d.ts.map +1 -0
- package/dist/collections/tst.js +171 -0
- package/dist/collections/tst.js.map +1 -0
- package/dist/index.d.ts +3 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -2
- package/dist/index.js.map +1 -1
- package/dist/localReference.d.ts +48 -99
- package/dist/localReference.d.ts.map +1 -1
- package/dist/localReference.js +132 -169
- package/dist/localReference.js.map +1 -1
- package/dist/mergeTree.d.ts +71 -302
- package/dist/mergeTree.d.ts.map +1 -1
- package/dist/mergeTree.js +395 -642
- package/dist/mergeTree.js.map +1 -1
- package/dist/mergeTreeDeltaCallback.d.ts +1 -1
- package/dist/mergeTreeDeltaCallback.d.ts.map +1 -1
- package/dist/mergeTreeDeltaCallback.js.map +1 -1
- package/dist/mergeTreeNodes.d.ts +344 -0
- package/dist/mergeTreeNodes.d.ts.map +1 -0
- package/dist/mergeTreeNodes.js +383 -0
- package/dist/mergeTreeNodes.js.map +1 -0
- package/dist/mergeTreeTracking.d.ts +1 -1
- package/dist/mergeTreeTracking.d.ts.map +1 -1
- package/dist/mergeTreeTracking.js.map +1 -1
- package/dist/opBuilder.d.ts +1 -1
- package/dist/opBuilder.d.ts.map +1 -1
- package/dist/opBuilder.js.map +1 -1
- package/dist/partialLengths.d.ts +188 -18
- package/dist/partialLengths.d.ts.map +1 -1
- package/dist/partialLengths.js +495 -253
- package/dist/partialLengths.js.map +1 -1
- package/dist/properties.d.ts.map +1 -1
- package/dist/properties.js.map +1 -1
- package/dist/referencePositions.d.ts +6 -26
- package/dist/referencePositions.d.ts.map +1 -1
- package/dist/referencePositions.js +3 -20
- package/dist/referencePositions.js.map +1 -1
- package/dist/segmentGroupCollection.d.ts +3 -1
- package/dist/segmentGroupCollection.d.ts.map +1 -1
- package/dist/segmentGroupCollection.js +14 -1
- package/dist/segmentGroupCollection.js.map +1 -1
- package/dist/segmentPropertiesManager.d.ts +10 -1
- package/dist/segmentPropertiesManager.d.ts.map +1 -1
- package/dist/segmentPropertiesManager.js +42 -13
- package/dist/segmentPropertiesManager.js.map +1 -1
- package/dist/snapshotChunks.d.ts +2 -1
- package/dist/snapshotChunks.d.ts.map +1 -1
- package/dist/snapshotChunks.js.map +1 -1
- package/dist/snapshotLoader.d.ts.map +1 -1
- package/dist/snapshotLoader.js.map +1 -1
- package/dist/snapshotV1.d.ts +1 -1
- package/dist/snapshotV1.d.ts.map +1 -1
- package/dist/snapshotV1.js +1 -1
- package/dist/snapshotV1.js.map +1 -1
- package/dist/snapshotlegacy.d.ts +5 -1
- package/dist/snapshotlegacy.d.ts.map +1 -1
- package/dist/snapshotlegacy.js +4 -0
- package/dist/snapshotlegacy.js.map +1 -1
- package/dist/sortedSegmentSet.d.ts +1 -1
- package/dist/sortedSegmentSet.d.ts.map +1 -1
- package/dist/sortedSegmentSet.js.map +1 -1
- package/dist/textSegment.d.ts +7 -7
- package/dist/textSegment.d.ts.map +1 -1
- package/dist/textSegment.js +3 -125
- package/dist/textSegment.js.map +1 -1
- package/{DEV.md → docs/DEV.md} +2 -2
- package/docs/Obliterate.md +639 -0
- package/{REFERENCEPOSITIONS.md → docs/REFERENCEPOSITIONS.md} +2 -2
- package/lib/MergeTreeTextHelper.d.ts +23 -0
- package/lib/MergeTreeTextHelper.d.ts.map +1 -0
- package/lib/MergeTreeTextHelper.js +129 -0
- package/lib/MergeTreeTextHelper.js.map +1 -0
- package/lib/base.d.ts +2 -26
- package/lib/base.d.ts.map +1 -1
- package/lib/base.js.map +1 -1
- package/lib/client.d.ts +27 -16
- package/lib/client.d.ts.map +1 -1
- package/lib/client.js +79 -99
- package/lib/client.js.map +1 -1
- package/lib/collections/heap.d.ts +28 -0
- package/lib/collections/heap.d.ts.map +1 -0
- package/lib/collections/heap.js +61 -0
- package/lib/collections/heap.js.map +1 -0
- package/lib/collections/index.d.ts +11 -0
- package/lib/collections/index.d.ts.map +1 -0
- package/lib/collections/index.js +11 -0
- package/lib/collections/index.js.map +1 -0
- package/lib/collections/intervalTree.d.ts +60 -0
- package/lib/collections/intervalTree.d.ts.map +1 -0
- package/lib/collections/intervalTree.js +94 -0
- package/lib/collections/intervalTree.js.map +1 -0
- package/lib/collections/list.d.ts +39 -0
- package/lib/collections/list.d.ts.map +1 -0
- package/lib/collections/list.js +149 -0
- package/lib/collections/list.js.map +1 -0
- package/lib/collections/rbTree.d.ts +154 -0
- package/lib/collections/rbTree.d.ts.map +1 -0
- package/lib/{collections.js → collections/rbTree.js} +14 -469
- package/lib/collections/rbTree.js.map +1 -0
- package/lib/collections/stack.d.ts +16 -0
- package/lib/collections/stack.d.ts.map +1 -0
- package/lib/collections/stack.js +26 -0
- package/lib/collections/stack.js.map +1 -0
- package/lib/collections/tst.d.ts +55 -0
- package/lib/collections/tst.d.ts.map +1 -0
- package/lib/collections/tst.js +167 -0
- package/lib/collections/tst.js.map +1 -0
- package/lib/index.d.ts +3 -1
- package/lib/index.d.ts.map +1 -1
- package/lib/index.js +3 -1
- package/lib/index.js.map +1 -1
- package/lib/localReference.d.ts +48 -99
- package/lib/localReference.d.ts.map +1 -1
- package/lib/localReference.js +132 -170
- package/lib/localReference.js.map +1 -1
- package/lib/mergeTree.d.ts +71 -302
- package/lib/mergeTree.d.ts.map +1 -1
- package/lib/mergeTree.js +371 -607
- package/lib/mergeTree.js.map +1 -1
- package/lib/mergeTreeDeltaCallback.d.ts +1 -1
- package/lib/mergeTreeDeltaCallback.d.ts.map +1 -1
- package/lib/mergeTreeDeltaCallback.js.map +1 -1
- package/lib/mergeTreeNodes.d.ts +344 -0
- package/lib/mergeTreeNodes.d.ts.map +1 -0
- package/lib/mergeTreeNodes.js +369 -0
- package/lib/mergeTreeNodes.js.map +1 -0
- package/lib/mergeTreeTracking.d.ts +1 -1
- package/lib/mergeTreeTracking.d.ts.map +1 -1
- package/lib/mergeTreeTracking.js.map +1 -1
- package/lib/opBuilder.d.ts +1 -1
- package/lib/opBuilder.d.ts.map +1 -1
- package/lib/opBuilder.js.map +1 -1
- package/lib/partialLengths.d.ts +188 -18
- package/lib/partialLengths.d.ts.map +1 -1
- package/lib/partialLengths.js +491 -249
- package/lib/partialLengths.js.map +1 -1
- package/lib/properties.d.ts.map +1 -1
- package/lib/properties.js.map +1 -1
- package/lib/referencePositions.d.ts +6 -26
- package/lib/referencePositions.d.ts.map +1 -1
- package/lib/referencePositions.js +3 -20
- package/lib/referencePositions.js.map +1 -1
- package/lib/segmentGroupCollection.d.ts +3 -1
- package/lib/segmentGroupCollection.d.ts.map +1 -1
- package/lib/segmentGroupCollection.js +14 -1
- package/lib/segmentGroupCollection.js.map +1 -1
- package/lib/segmentPropertiesManager.d.ts +10 -1
- package/lib/segmentPropertiesManager.d.ts.map +1 -1
- package/lib/segmentPropertiesManager.js +42 -13
- package/lib/segmentPropertiesManager.js.map +1 -1
- package/lib/snapshotChunks.d.ts +2 -1
- package/lib/snapshotChunks.d.ts.map +1 -1
- package/lib/snapshotChunks.js.map +1 -1
- package/lib/snapshotLoader.d.ts.map +1 -1
- package/lib/snapshotLoader.js.map +1 -1
- package/lib/snapshotV1.d.ts +1 -1
- package/lib/snapshotV1.d.ts.map +1 -1
- package/lib/snapshotV1.js +1 -1
- package/lib/snapshotV1.js.map +1 -1
- package/lib/snapshotlegacy.d.ts +5 -1
- package/lib/snapshotlegacy.d.ts.map +1 -1
- package/lib/snapshotlegacy.js +4 -0
- package/lib/snapshotlegacy.js.map +1 -1
- package/lib/sortedSegmentSet.d.ts +1 -1
- package/lib/sortedSegmentSet.d.ts.map +1 -1
- package/lib/sortedSegmentSet.js.map +1 -1
- package/lib/textSegment.d.ts +7 -7
- package/lib/textSegment.d.ts.map +1 -1
- package/lib/textSegment.js +1 -122
- package/lib/textSegment.js.map +1 -1
- package/package.json +99 -20
- package/src/MergeTreeTextHelper.ts +170 -0
- package/src/base.ts +2 -35
- package/src/client.ts +91 -111
- package/src/collections/heap.ts +75 -0
- package/src/collections/index.ts +11 -0
- package/src/collections/intervalTree.ts +146 -0
- package/src/collections/list.ts +165 -0
- package/src/{collections.ts → collections/rbTree.ts} +84 -563
- package/src/collections/stack.ts +27 -0
- package/src/collections/tst.ts +212 -0
- package/src/index.ts +8 -2
- package/src/localReference.ts +152 -203
- package/src/mergeTree.ts +578 -996
- package/src/mergeTreeDeltaCallback.ts +1 -1
- package/src/mergeTreeNodes.ts +752 -0
- package/src/mergeTreeTracking.ts +1 -1
- package/src/opBuilder.ts +1 -1
- package/src/partialLengths.ts +631 -258
- package/src/properties.ts +1 -0
- package/src/referencePositions.ts +10 -44
- package/src/segmentGroupCollection.ts +17 -2
- package/src/segmentPropertiesManager.ts +46 -12
- package/src/snapshotChunks.ts +2 -1
- package/src/snapshotLoader.ts +2 -1
- package/src/snapshotV1.ts +3 -3
- package/src/snapshotlegacy.ts +6 -2
- package/src/sortedSegmentSet.ts +1 -1
- package/src/textSegment.ts +10 -157
- package/dist/collections.d.ts +0 -197
- package/dist/collections.d.ts.map +0 -1
- package/dist/collections.js.map +0 -1
- package/lib/collections.d.ts +0 -197
- package/lib/collections.d.ts.map +0 -1
- package/lib/collections.js.map +0 -1
package/src/mergeTree.ts
CHANGED
|
@@ -24,7 +24,35 @@ import {
|
|
|
24
24
|
UnassignedSequenceNumber,
|
|
25
25
|
UniversalSequenceNumber,
|
|
26
26
|
} from "./constants";
|
|
27
|
-
import {
|
|
27
|
+
import {
|
|
28
|
+
assertLocalReferences,
|
|
29
|
+
LocalReferenceCollection,
|
|
30
|
+
LocalReferencePosition,
|
|
31
|
+
} from "./localReference";
|
|
32
|
+
import {
|
|
33
|
+
BaseSegment,
|
|
34
|
+
BlockUpdateActions,
|
|
35
|
+
CollaborationWindow,
|
|
36
|
+
IHierBlock,
|
|
37
|
+
IMergeBlock,
|
|
38
|
+
IMergeNode,
|
|
39
|
+
IncrementalExecOp,
|
|
40
|
+
IncrementalMapState,
|
|
41
|
+
InsertContext,
|
|
42
|
+
internedSpaces,
|
|
43
|
+
ISegment,
|
|
44
|
+
ISegmentAction,
|
|
45
|
+
ISegmentChanges,
|
|
46
|
+
Marker,
|
|
47
|
+
MaxNodesInBlock,
|
|
48
|
+
MergeBlock,
|
|
49
|
+
MergeNode,
|
|
50
|
+
MergeTreeStats,
|
|
51
|
+
MinListener,
|
|
52
|
+
SegmentActions,
|
|
53
|
+
SegmentGroup,
|
|
54
|
+
toRemovalInfo,
|
|
55
|
+
} from "./mergeTreeNodes";
|
|
28
56
|
import {
|
|
29
57
|
IMergeTreeDeltaOpArgs,
|
|
30
58
|
IMergeTreeSegmentDelta,
|
|
@@ -32,83 +60,38 @@ import {
|
|
|
32
60
|
MergeTreeMaintenanceCallback,
|
|
33
61
|
MergeTreeMaintenanceType,
|
|
34
62
|
} from "./mergeTreeDeltaCallback";
|
|
35
|
-
import {
|
|
63
|
+
import { createAnnotateRangeOp, createInsertSegmentOp, createRemoveRangeOp } from "./opBuilder";
|
|
36
64
|
import {
|
|
37
65
|
ICombiningOp,
|
|
38
|
-
|
|
39
|
-
IMarkerDef,
|
|
66
|
+
IMergeTreeDeltaOp,
|
|
40
67
|
IRelativePosition,
|
|
41
68
|
MergeTreeDeltaType,
|
|
42
69
|
ReferenceType,
|
|
43
70
|
} from "./ops";
|
|
44
71
|
import { PartialSequenceLengths } from "./partialLengths";
|
|
45
72
|
import {
|
|
46
|
-
clone,
|
|
47
73
|
createMap,
|
|
48
74
|
extend,
|
|
49
|
-
extendIfUndefined,
|
|
50
75
|
MapLike,
|
|
51
76
|
matchProperties,
|
|
52
77
|
PropertySet,
|
|
53
78
|
} from "./properties";
|
|
54
79
|
import {
|
|
55
80
|
refTypeIncludesFlag,
|
|
56
|
-
RangeStackMap,
|
|
57
81
|
ReferencePosition,
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
82
|
+
DetachedReferencePosition,
|
|
83
|
+
RangeStackMap,
|
|
84
|
+
refHasRangeLabel,
|
|
85
|
+
refGetRangeLabels,
|
|
86
|
+
refGetTileLabels,
|
|
87
|
+
refHasTileLabel,
|
|
64
88
|
} from "./referencePositions";
|
|
65
|
-
import {
|
|
66
|
-
import { PropertiesManager } from "./segmentPropertiesManager";
|
|
67
|
-
import { Client } from "./client";
|
|
68
|
-
|
|
69
|
-
export interface IMergeNodeCommon {
|
|
70
|
-
parent?: IMergeBlock;
|
|
71
|
-
/**
|
|
72
|
-
* The length of the contents of the node.
|
|
73
|
-
*/
|
|
74
|
-
cachedLength: number;
|
|
75
|
-
index: number;
|
|
76
|
-
ordinal: string;
|
|
77
|
-
isLeaf(): this is ISegment;
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
export type IMergeNode = IMergeBlock | ISegment;
|
|
81
|
-
|
|
82
|
-
// Node with segments as children
|
|
83
|
-
export interface IMergeBlock extends IMergeNodeCommon {
|
|
84
|
-
needsScour?: boolean;
|
|
85
|
-
childCount: number;
|
|
86
|
-
children: IMergeNode[];
|
|
87
|
-
partialLengths?: PartialSequenceLengths;
|
|
88
|
-
hierBlock(): IHierBlock | undefined;
|
|
89
|
-
assignChild(child: IMergeNode, index: number, updateOrdinal?: boolean): void;
|
|
90
|
-
setOrdinal(child: IMergeNode, index: number): void;
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
export interface IHierBlock extends IMergeBlock {
|
|
94
|
-
hierToString(indentCount: number): string;
|
|
95
|
-
addNodeReferences(mergeTree: MergeTree, node: IMergeNode): void;
|
|
96
|
-
rightmostTiles: MapLike<ReferencePosition>;
|
|
97
|
-
leftmostTiles: MapLike<ReferencePosition>;
|
|
98
|
-
rangeStacks: RangeStackMap;
|
|
99
|
-
}
|
|
89
|
+
import { PropertiesRollback } from "./segmentPropertiesManager";
|
|
100
90
|
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
}
|
|
105
|
-
export function toRemovalInfo(maybe: Partial<IRemovalInfo> | undefined): IRemovalInfo | undefined {
|
|
106
|
-
if (maybe?.removedClientIds !== undefined && maybe?.removedSeq !== undefined) {
|
|
107
|
-
return maybe as IRemovalInfo;
|
|
108
|
-
}
|
|
109
|
-
assert(maybe?.removedClientIds === undefined && maybe?.removedSeq === undefined,
|
|
110
|
-
0x2bf /* "both removedClientIds and removedSeq should be set or not set" */);
|
|
111
|
-
}
|
|
91
|
+
const minListenerComparer: Comparer<MinListener> = {
|
|
92
|
+
min: { minRequired: Number.MIN_VALUE, onMinGE: () => { assert(false, 0x048 /* "onMinGE()" */); } },
|
|
93
|
+
compare: (a, b) => a.minRequired - b.minRequired,
|
|
94
|
+
};
|
|
112
95
|
|
|
113
96
|
function isRemoved(segment: ISegment): boolean {
|
|
114
97
|
return toRemovalInfo(segment) !== undefined;
|
|
@@ -119,153 +102,111 @@ function isRemovedAndAcked(segment: ISegment): boolean {
|
|
|
119
102
|
return removalInfo !== undefined && removalInfo.removedSeq !== UnassignedSequenceNumber;
|
|
120
103
|
}
|
|
121
104
|
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
readonly segmentGroups: SegmentGroupCollection;
|
|
128
|
-
readonly trackingCollection: TrackingGroupCollection;
|
|
129
|
-
propertyManager?: PropertiesManager;
|
|
130
|
-
localSeq?: number;
|
|
131
|
-
localRemovedSeq?: number;
|
|
132
|
-
seq?: number; // If not present assumed to be previous to window min
|
|
133
|
-
clientId: number;
|
|
134
|
-
localRefs?: LocalReferenceCollection;
|
|
135
|
-
properties?: PropertySet;
|
|
136
|
-
addProperties(
|
|
137
|
-
newProps: PropertySet,
|
|
138
|
-
op?: ICombiningOp,
|
|
139
|
-
seq?: number,
|
|
140
|
-
collabWindow?: CollaborationWindow,
|
|
141
|
-
): PropertySet | undefined;
|
|
142
|
-
clone(): ISegment;
|
|
143
|
-
canAppend(segment: ISegment): boolean;
|
|
144
|
-
append(segment: ISegment): void;
|
|
145
|
-
splitAt(pos: number): ISegment | undefined;
|
|
146
|
-
toJSONObject(): any;
|
|
147
|
-
/**
|
|
148
|
-
* Acks the current segment against the segment group, op, and merge tree.
|
|
149
|
-
*
|
|
150
|
-
* Throws error if the segment state doesn't match segment group or op.
|
|
151
|
-
* E.g. Segment group not first is pending queue.
|
|
152
|
-
* Inserted segment does not have unassigned sequence number.
|
|
153
|
-
*
|
|
154
|
-
* Returns true if the op modifies the segment, otherwise false.
|
|
155
|
-
* The only current false case is overlapping remove, where a segment is removed
|
|
156
|
-
* by a previously sequenced operation before the current operation is acked.
|
|
157
|
-
*/
|
|
158
|
-
ack(segmentGroup: SegmentGroup, opArgs: IMergeTreeDeltaOpArgs, mergeTree: MergeTree): boolean;
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
export interface IMarkerModifiedAction {
|
|
162
|
-
// eslint-disable-next-line @typescript-eslint/prefer-function-type
|
|
163
|
-
(marker: Marker): void;
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
export interface ISegmentAction<TClientData> {
|
|
167
|
-
// eslint-disable-next-line @typescript-eslint/prefer-function-type
|
|
168
|
-
(segment: ISegment, pos: number, refSeq: number, clientId: number, start: number,
|
|
169
|
-
end: number, accum: TClientData): boolean;
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
export interface ISegmentChanges {
|
|
173
|
-
next?: ISegment;
|
|
174
|
-
replaceCurrent?: ISegment;
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
export interface BlockAction<TClientData> {
|
|
178
|
-
// eslint-disable-next-line @typescript-eslint/prefer-function-type
|
|
179
|
-
(
|
|
180
|
-
block: IMergeBlock,
|
|
181
|
-
pos: number,
|
|
182
|
-
refSeq: number,
|
|
183
|
-
clientId: number,
|
|
184
|
-
start: number | undefined,
|
|
185
|
-
end: number | undefined,
|
|
186
|
-
accum: TClientData,
|
|
187
|
-
): boolean;
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
export interface NodeAction<TClientData> {
|
|
191
|
-
// eslint-disable-next-line @typescript-eslint/prefer-function-type
|
|
192
|
-
(
|
|
193
|
-
node: IMergeNode,
|
|
194
|
-
pos: number,
|
|
195
|
-
refSeq: number,
|
|
196
|
-
clientId: number,
|
|
197
|
-
start: number | undefined,
|
|
198
|
-
end: number | undefined,
|
|
199
|
-
clientData: TClientData,
|
|
200
|
-
): boolean;
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
export interface IncrementalSegmentAction<TContext> {
|
|
204
|
-
(segment: ISegment, state: IncrementalMapState<TContext>);
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
export interface IncrementalBlockAction<TContext> {
|
|
208
|
-
(state: IncrementalMapState<TContext>);
|
|
105
|
+
function nodeTotalLength(mergeTree: MergeTree, node: IMergeNode) {
|
|
106
|
+
if (!node.isLeaf()) {
|
|
107
|
+
return node.cachedLength;
|
|
108
|
+
}
|
|
109
|
+
return mergeTree.localNetLength(node);
|
|
209
110
|
}
|
|
210
111
|
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
112
|
+
const LRUSegmentComparer: Comparer<LRUSegment> = {
|
|
113
|
+
min: { maxSeq: -2 },
|
|
114
|
+
compare: (a, b) => a.maxSeq - b.maxSeq,
|
|
115
|
+
};
|
|
214
116
|
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
continuePredicate?: (continueFromBlock: IMergeBlock) => boolean;
|
|
117
|
+
interface IReferenceSearchInfo {
|
|
118
|
+
mergeTree: MergeTree;
|
|
119
|
+
tileLabel: string;
|
|
120
|
+
tilePrecedesPos?: boolean;
|
|
121
|
+
tile?: ReferencePosition;
|
|
221
122
|
}
|
|
222
123
|
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
pre?: BlockAction<TClientData>;
|
|
228
|
-
post?: BlockAction<TClientData>;
|
|
124
|
+
interface IMarkerSearchRangeInfo {
|
|
125
|
+
mergeTree: MergeTree;
|
|
126
|
+
rangeLabels: string[];
|
|
127
|
+
stacks: RangeStackMap;
|
|
229
128
|
}
|
|
230
129
|
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
130
|
+
function applyLeafRangeMarker(marker: Marker, searchInfo: IMarkerSearchRangeInfo) {
|
|
131
|
+
for (const rangeLabel of searchInfo.rangeLabels) {
|
|
132
|
+
if (refHasRangeLabel(marker, rangeLabel)) {
|
|
133
|
+
let currentStack = searchInfo.stacks[rangeLabel];
|
|
134
|
+
if (currentStack === undefined) {
|
|
135
|
+
currentStack = new Stack<Marker>();
|
|
136
|
+
searchInfo.stacks[rangeLabel] = currentStack;
|
|
137
|
+
}
|
|
138
|
+
applyRangeReference(currentStack, marker);
|
|
139
|
+
}
|
|
140
|
+
}
|
|
235
141
|
}
|
|
236
142
|
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
143
|
+
function recordRangeLeaf(
|
|
144
|
+
segment: ISegment, segpos: number,
|
|
145
|
+
refSeq: number, clientId: number, start: number | undefined, end: number | undefined,
|
|
146
|
+
searchInfo: IMarkerSearchRangeInfo) {
|
|
147
|
+
if (Marker.is(segment)) {
|
|
148
|
+
if (segment.refType &
|
|
149
|
+
(ReferenceType.NestBegin | ReferenceType.NestEnd)) {
|
|
150
|
+
applyLeafRangeMarker(segment, searchInfo);
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
return false;
|
|
240
154
|
}
|
|
241
155
|
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
156
|
+
function rangeShift(
|
|
157
|
+
node: IMergeNode, segpos: number, refSeq: number, clientId: number,
|
|
158
|
+
offset: number | undefined, end: number | undefined, searchInfo: IMarkerSearchRangeInfo) {
|
|
159
|
+
if (node.isLeaf()) {
|
|
160
|
+
const seg = node;
|
|
161
|
+
if (((searchInfo.mergeTree.localNetLength(seg) ?? 0) > 0) && Marker.is(seg)) {
|
|
162
|
+
if (seg.refType &
|
|
163
|
+
(ReferenceType.NestBegin | ReferenceType.NestEnd)) {
|
|
164
|
+
applyLeafRangeMarker(seg, searchInfo);
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
} else {
|
|
168
|
+
const block = <IHierBlock>node;
|
|
169
|
+
applyStackDelta(searchInfo.stacks, block.rangeStacks);
|
|
170
|
+
}
|
|
171
|
+
return true;
|
|
253
172
|
}
|
|
254
173
|
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
174
|
+
function recordTileStart(
|
|
175
|
+
segment: ISegment,
|
|
176
|
+
segpos: number,
|
|
177
|
+
refSeq: number,
|
|
178
|
+
clientId: number,
|
|
179
|
+
start: number,
|
|
180
|
+
end: number,
|
|
181
|
+
searchInfo: IReferenceSearchInfo) {
|
|
182
|
+
if (Marker.is(segment)) {
|
|
183
|
+
if (refHasTileLabel(segment, searchInfo.tileLabel)) {
|
|
184
|
+
searchInfo.tile = segment;
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
return false;
|
|
258
188
|
}
|
|
259
189
|
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
190
|
+
function tileShift(
|
|
191
|
+
node: IMergeNode, segpos: number, refSeq: number, clientId: number,
|
|
192
|
+
offset: number | undefined, end: number | undefined, searchInfo: IReferenceSearchInfo) {
|
|
193
|
+
if (node.isLeaf()) {
|
|
194
|
+
const seg = node;
|
|
195
|
+
if ((searchInfo.mergeTree.localNetLength(seg) > 0) && Marker.is(seg)) {
|
|
196
|
+
if (refHasTileLabel(seg, searchInfo.tileLabel)) {
|
|
197
|
+
searchInfo.tile = seg;
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
} else {
|
|
201
|
+
const block = <IHierBlock>node;
|
|
202
|
+
const marker = searchInfo.tilePrecedesPos
|
|
203
|
+
? <Marker>block.rightmostTiles[searchInfo.tileLabel]
|
|
204
|
+
: <Marker>block.leftmostTiles[searchInfo.tileLabel];
|
|
205
|
+
if (marker !== undefined) {
|
|
206
|
+
searchInfo.tile = marker;
|
|
207
|
+
}
|
|
268
208
|
}
|
|
209
|
+
return true;
|
|
269
210
|
}
|
|
270
211
|
|
|
271
212
|
function addTile(tile: ReferencePosition, tiles: object) {
|
|
@@ -328,685 +269,144 @@ function addNodeReferences(
|
|
|
328
269
|
leftmostTiles: MapLike<ReferencePosition>, rangeStacks: RangeStackMap) {
|
|
329
270
|
function updateRangeInfo(label: string, refPos: ReferencePosition) {
|
|
330
271
|
let stack = rangeStacks[label];
|
|
331
|
-
if (stack === undefined) {
|
|
332
|
-
stack = new Stack<ReferencePosition>();
|
|
333
|
-
rangeStacks[label] = stack;
|
|
334
|
-
}
|
|
335
|
-
applyRangeReference(stack, refPos);
|
|
336
|
-
}
|
|
337
|
-
if (node.isLeaf()) {
|
|
338
|
-
const segment = node;
|
|
339
|
-
if ((mergeTree.localNetLength(segment) ?? 0) > 0) {
|
|
340
|
-
if (Marker.is(segment)) {
|
|
341
|
-
const markerId = segment.getId();
|
|
342
|
-
// Also in insertMarker but need for reload segs case
|
|
343
|
-
// can add option for this only from reload segs
|
|
344
|
-
if (markerId) {
|
|
345
|
-
mergeTree.mapIdToSegment(markerId, segment);
|
|
346
|
-
}
|
|
347
|
-
if (refTypeIncludesFlag(segment, ReferenceType.Tile)) {
|
|
348
|
-
addTile(segment, rightmostTiles);
|
|
349
|
-
addTileIfNotPresent(segment, leftmostTiles);
|
|
350
|
-
}
|
|
351
|
-
if (segment.refType & (ReferenceType.NestBegin | ReferenceType.NestEnd)) {
|
|
352
|
-
const rangeLabels = refGetRangeLabels(segment);
|
|
353
|
-
if (rangeLabels) {
|
|
354
|
-
for (const label of rangeLabels) {
|
|
355
|
-
updateRangeInfo(label, segment);
|
|
356
|
-
}
|
|
357
|
-
}
|
|
358
|
-
}
|
|
359
|
-
} else {
|
|
360
|
-
const baseSegment = node as BaseSegment;
|
|
361
|
-
if (baseSegment.localRefs && (baseSegment.localRefs.hierRefCount !== undefined) &&
|
|
362
|
-
(baseSegment.localRefs.hierRefCount > 0)) {
|
|
363
|
-
for (const lref of baseSegment.localRefs) {
|
|
364
|
-
if (refTypeIncludesFlag(lref, ReferenceType.Tile)) {
|
|
365
|
-
addTile(lref, rightmostTiles);
|
|
366
|
-
addTileIfNotPresent(lref, leftmostTiles);
|
|
367
|
-
}
|
|
368
|
-
if (lref.refType & (ReferenceType.NestBegin | ReferenceType.NestEnd)) {
|
|
369
|
-
for (const label of refGetRangeLabels(lref)!) {
|
|
370
|
-
updateRangeInfo(label, lref);
|
|
371
|
-
}
|
|
372
|
-
}
|
|
373
|
-
}
|
|
374
|
-
}
|
|
375
|
-
}
|
|
376
|
-
}
|
|
377
|
-
} else {
|
|
378
|
-
const block = <IHierBlock>node;
|
|
379
|
-
applyStackDelta(rangeStacks, block.rangeStacks);
|
|
380
|
-
extend(rightmostTiles, block.rightmostTiles);
|
|
381
|
-
extendIfUndefined(leftmostTiles, block.leftmostTiles);
|
|
382
|
-
}
|
|
383
|
-
}
|
|
384
|
-
|
|
385
|
-
export function ordinalToArray(ord: string) {
|
|
386
|
-
const a: number[] = [];
|
|
387
|
-
if (ord) {
|
|
388
|
-
for (let i = 0, len = ord.length; i < len; i++) {
|
|
389
|
-
a.push(ord.charCodeAt(i));
|
|
390
|
-
}
|
|
391
|
-
}
|
|
392
|
-
return a;
|
|
393
|
-
}
|
|
394
|
-
|
|
395
|
-
// Note that the actual branching factor of the MergeTree is `MaxNodesInBlock - 1`. This is because
|
|
396
|
-
// the MergeTree always inserts first, then checks for overflow and splits if the child count equals
|
|
397
|
-
// `MaxNodesInBlock`. (i.e., `MaxNodesInBlock` contains 1 extra slot for temporary storage to
|
|
398
|
-
// facilitate splits.)
|
|
399
|
-
export const MaxNodesInBlock = 8;
|
|
400
|
-
|
|
401
|
-
export class MergeBlock extends MergeNode implements IMergeBlock {
|
|
402
|
-
public children: IMergeNode[];
|
|
403
|
-
public constructor(public childCount: number) {
|
|
404
|
-
super();
|
|
405
|
-
this.children = new Array<IMergeNode>(MaxNodesInBlock);
|
|
406
|
-
}
|
|
407
|
-
|
|
408
|
-
public hierBlock(): HierMergeBlock | undefined {
|
|
409
|
-
return undefined;
|
|
410
|
-
}
|
|
411
|
-
|
|
412
|
-
public setOrdinal(child: IMergeNode, index: number) {
|
|
413
|
-
let childCount = this.childCount;
|
|
414
|
-
if (childCount === 8) {
|
|
415
|
-
childCount = 7;
|
|
416
|
-
}
|
|
417
|
-
assert((childCount >= 1) && (childCount <= 7), 0x040 /* "Child count is not within [1,7] range!" */);
|
|
418
|
-
let localOrdinal: number;
|
|
419
|
-
const ordinalWidth = 1 << (MaxNodesInBlock - (childCount + 1));
|
|
420
|
-
if (index === 0) {
|
|
421
|
-
localOrdinal = ordinalWidth - 1;
|
|
422
|
-
} else {
|
|
423
|
-
const prevOrd = this.children[index - 1].ordinal;
|
|
424
|
-
const prevOrdCode = prevOrd.charCodeAt(prevOrd.length - 1);
|
|
425
|
-
localOrdinal = prevOrdCode + ordinalWidth;
|
|
426
|
-
}
|
|
427
|
-
child.ordinal = this.ordinal + String.fromCharCode(localOrdinal);
|
|
428
|
-
assert(child.ordinal.length === (this.ordinal.length + 1), 0x041 /* "Unexpected child ordinal length!" */);
|
|
429
|
-
if (index > 0) {
|
|
430
|
-
assert(
|
|
431
|
-
child.ordinal > this.children[index - 1].ordinal,
|
|
432
|
-
0x042, /* "Child ordinal <= previous sibling ordinal!" */
|
|
433
|
-
);
|
|
434
|
-
}
|
|
435
|
-
}
|
|
436
|
-
|
|
437
|
-
public assignChild(child: IMergeNode, index: number, updateOrdinal = true) {
|
|
438
|
-
child.parent = this;
|
|
439
|
-
child.index = index;
|
|
440
|
-
if (updateOrdinal) {
|
|
441
|
-
this.setOrdinal(child, index);
|
|
442
|
-
}
|
|
443
|
-
this.children[index] = child;
|
|
444
|
-
}
|
|
445
|
-
}
|
|
446
|
-
|
|
447
|
-
class HierMergeBlock extends MergeBlock implements IMergeBlock {
|
|
448
|
-
public rightmostTiles: MapLike<ReferencePosition>;
|
|
449
|
-
public leftmostTiles: MapLike<ReferencePosition>;
|
|
450
|
-
public rangeStacks: MapLike<Stack<ReferencePosition>>;
|
|
451
|
-
|
|
452
|
-
constructor(childCount: number) {
|
|
453
|
-
super(childCount);
|
|
454
|
-
this.rightmostTiles = createMap<ReferencePosition>();
|
|
455
|
-
this.leftmostTiles = createMap<ReferencePosition>();
|
|
456
|
-
this.rangeStacks = createMap<Stack<ReferencePosition>>();
|
|
457
|
-
}
|
|
458
|
-
|
|
459
|
-
public addNodeReferences(mergeTree: MergeTree, node: IMergeNode) {
|
|
460
|
-
addNodeReferences(mergeTree, node, this.rightmostTiles, this.leftmostTiles,
|
|
461
|
-
this.rangeStacks);
|
|
462
|
-
}
|
|
463
|
-
|
|
464
|
-
public hierBlock() {
|
|
465
|
-
return this;
|
|
466
|
-
}
|
|
467
|
-
|
|
468
|
-
public hierToString(indentCount: number) {
|
|
469
|
-
let strbuf = "";
|
|
470
|
-
// eslint-disable-next-line guard-for-in, no-restricted-syntax
|
|
471
|
-
for (const key in this.rangeStacks) {
|
|
472
|
-
const stack = this.rangeStacks[key];
|
|
473
|
-
strbuf += internedSpaces(indentCount);
|
|
474
|
-
strbuf += `${key}: `;
|
|
475
|
-
for (const item of stack.items) {
|
|
476
|
-
strbuf += `${item.toString()} `;
|
|
477
|
-
}
|
|
478
|
-
strbuf += "\n";
|
|
479
|
-
}
|
|
480
|
-
return strbuf;
|
|
481
|
-
}
|
|
482
|
-
}
|
|
483
|
-
|
|
484
|
-
function nodeTotalLength(mergeTree: MergeTree, node: IMergeNode) {
|
|
485
|
-
if (!node.isLeaf()) {
|
|
486
|
-
return node.cachedLength;
|
|
487
|
-
}
|
|
488
|
-
return mergeTree.localNetLength(node);
|
|
489
|
-
}
|
|
490
|
-
|
|
491
|
-
export abstract class BaseSegment extends MergeNode implements ISegment {
|
|
492
|
-
public clientId: number = LocalClientId;
|
|
493
|
-
public seq: number = UniversalSequenceNumber;
|
|
494
|
-
public removedSeq?: number;
|
|
495
|
-
public removedClientIds?: number[];
|
|
496
|
-
public readonly segmentGroups: SegmentGroupCollection = new SegmentGroupCollection(this);
|
|
497
|
-
public readonly trackingCollection: TrackingGroupCollection = new TrackingGroupCollection(this);
|
|
498
|
-
public propertyManager?: PropertiesManager;
|
|
499
|
-
public properties?: PropertySet;
|
|
500
|
-
public localRefs?: LocalReferenceCollection;
|
|
501
|
-
public abstract readonly type: string;
|
|
502
|
-
public localSeq?: number;
|
|
503
|
-
public localRemovedSeq?: number;
|
|
504
|
-
|
|
505
|
-
public addProperties(newProps: PropertySet, op?: ICombiningOp, seq?: number, collabWindow?: CollaborationWindow) {
|
|
506
|
-
if (!this.propertyManager) {
|
|
507
|
-
this.propertyManager = new PropertiesManager();
|
|
508
|
-
}
|
|
509
|
-
if (!this.properties) {
|
|
510
|
-
this.properties = createMap<any>();
|
|
511
|
-
}
|
|
512
|
-
return this.propertyManager.addProperties(
|
|
513
|
-
this.properties,
|
|
514
|
-
newProps,
|
|
515
|
-
op,
|
|
516
|
-
seq,
|
|
517
|
-
collabWindow && collabWindow.collaborating,
|
|
518
|
-
);
|
|
519
|
-
}
|
|
520
|
-
|
|
521
|
-
public hasProperty(key: string): boolean {
|
|
522
|
-
return !!this.properties && (this.properties[key] !== undefined);
|
|
523
|
-
}
|
|
524
|
-
|
|
525
|
-
public isLeaf() {
|
|
526
|
-
return true;
|
|
527
|
-
}
|
|
528
|
-
|
|
529
|
-
protected cloneInto(b: ISegment) {
|
|
530
|
-
b.clientId = this.clientId;
|
|
531
|
-
// TODO: deep clone properties
|
|
532
|
-
b.properties = clone(this.properties);
|
|
533
|
-
b.removedClientIds = this.removedClientIds?.slice();
|
|
534
|
-
// TODO: copy removed client overlap and branch removal info
|
|
535
|
-
b.removedSeq = this.removedSeq;
|
|
536
|
-
b.seq = this.seq;
|
|
537
|
-
}
|
|
538
|
-
|
|
539
|
-
public canAppend(segment: ISegment): boolean {
|
|
540
|
-
return false;
|
|
541
|
-
}
|
|
542
|
-
|
|
543
|
-
protected addSerializedProps(jseg: IJSONSegment) {
|
|
544
|
-
if (this.properties) {
|
|
545
|
-
jseg.props = this.properties;
|
|
546
|
-
}
|
|
547
|
-
}
|
|
548
|
-
|
|
549
|
-
public abstract toJSONObject(): any;
|
|
550
|
-
|
|
551
|
-
public ack(segmentGroup: SegmentGroup, opArgs: IMergeTreeDeltaOpArgs, mergeTree: MergeTree): boolean {
|
|
552
|
-
const currentSegmentGroup = this.segmentGroups.dequeue();
|
|
553
|
-
assert(currentSegmentGroup === segmentGroup, 0x043 /* "On ack, unexpected segmentGroup!" */);
|
|
554
|
-
switch (opArgs.op.type) {
|
|
555
|
-
case MergeTreeDeltaType.ANNOTATE:
|
|
556
|
-
assert(!!this.propertyManager, 0x044 /* "On annotate ack, missing segment property manager!" */);
|
|
557
|
-
this.propertyManager.ackPendingProperties(opArgs.op);
|
|
558
|
-
return true;
|
|
559
|
-
|
|
560
|
-
case MergeTreeDeltaType.INSERT:
|
|
561
|
-
assert(this.seq === UnassignedSequenceNumber, 0x045 /* "On insert, seq number already assigned!" */);
|
|
562
|
-
this.seq = opArgs.sequencedMessage!.sequenceNumber;
|
|
563
|
-
this.localSeq = undefined;
|
|
564
|
-
return true;
|
|
565
|
-
|
|
566
|
-
case MergeTreeDeltaType.REMOVE:
|
|
567
|
-
const removalInfo: IRemovalInfo | undefined = toRemovalInfo(this);
|
|
568
|
-
assert(removalInfo !== undefined, 0x046 /* "On remove ack, missing removal info!" */);
|
|
569
|
-
this.localRemovedSeq = undefined;
|
|
570
|
-
if (removalInfo.removedSeq === UnassignedSequenceNumber) {
|
|
571
|
-
removalInfo.removedSeq = opArgs.sequencedMessage!.sequenceNumber;
|
|
572
|
-
return true;
|
|
573
|
-
}
|
|
574
|
-
return false;
|
|
575
|
-
|
|
576
|
-
default:
|
|
577
|
-
throw new Error(`${opArgs.op.type} is in unrecognized operation type`);
|
|
578
|
-
}
|
|
579
|
-
}
|
|
580
|
-
|
|
581
|
-
public splitAt(pos: number): ISegment | undefined {
|
|
582
|
-
if (pos > 0) {
|
|
583
|
-
const leafSegment = this.createSplitSegmentAt(pos);
|
|
584
|
-
if (leafSegment) {
|
|
585
|
-
this.copyPropertiesTo(leafSegment);
|
|
586
|
-
leafSegment.parent = this.parent;
|
|
587
|
-
|
|
588
|
-
// Give the leaf a temporary yet valid ordinal.
|
|
589
|
-
// when this segment is put in the tree, it will get it's real ordinal,
|
|
590
|
-
// but this ordinal meets all the necessary invariants for now.
|
|
591
|
-
leafSegment.ordinal = this.ordinal + String.fromCharCode(0);
|
|
592
|
-
|
|
593
|
-
leafSegment.removedClientIds = this.removedClientIds?.slice();
|
|
594
|
-
leafSegment.removedSeq = this.removedSeq;
|
|
595
|
-
leafSegment.localRemovedSeq = this.localRemovedSeq;
|
|
596
|
-
leafSegment.seq = this.seq;
|
|
597
|
-
leafSegment.localSeq = this.localSeq;
|
|
598
|
-
leafSegment.clientId = this.clientId;
|
|
599
|
-
this.segmentGroups.copyTo(leafSegment);
|
|
600
|
-
this.trackingCollection.copyTo(leafSegment);
|
|
601
|
-
if (this.localRefs) {
|
|
602
|
-
this.localRefs.split(pos, leafSegment);
|
|
603
|
-
}
|
|
604
|
-
}
|
|
605
|
-
return leafSegment;
|
|
606
|
-
}
|
|
607
|
-
}
|
|
608
|
-
|
|
609
|
-
private copyPropertiesTo(other: ISegment) {
|
|
610
|
-
if (this.propertyManager) {
|
|
611
|
-
if (this.properties) {
|
|
612
|
-
other.propertyManager = new PropertiesManager();
|
|
613
|
-
other.properties = this.propertyManager.copyTo(
|
|
614
|
-
this.properties,
|
|
615
|
-
other.properties,
|
|
616
|
-
other.propertyManager,
|
|
617
|
-
);
|
|
618
|
-
}
|
|
619
|
-
}
|
|
620
|
-
}
|
|
621
|
-
|
|
622
|
-
public abstract clone(): ISegment;
|
|
623
|
-
public abstract append(segment: ISegment): void;
|
|
624
|
-
protected abstract createSplitSegmentAt(pos: number): BaseSegment | undefined;
|
|
625
|
-
}
|
|
626
|
-
|
|
627
|
-
export const reservedMarkerIdKey = "markerId";
|
|
628
|
-
export const reservedMarkerSimpleTypeKey = "markerSimpleType";
|
|
629
|
-
|
|
630
|
-
export interface IJSONMarkerSegment extends IJSONSegment {
|
|
631
|
-
marker: IMarkerDef;
|
|
632
|
-
}
|
|
633
|
-
|
|
634
|
-
export class Marker extends BaseSegment implements ReferencePosition {
|
|
635
|
-
public static readonly type = "Marker";
|
|
636
|
-
public static is(segment: ISegment): segment is Marker {
|
|
637
|
-
return segment.type === Marker.type;
|
|
638
|
-
}
|
|
639
|
-
public readonly type = Marker.type;
|
|
640
|
-
|
|
641
|
-
public static make(
|
|
642
|
-
refType: ReferenceType, props?: PropertySet) {
|
|
643
|
-
const marker = new Marker(refType);
|
|
644
|
-
if (props) {
|
|
645
|
-
marker.addProperties(props);
|
|
646
|
-
}
|
|
647
|
-
return marker;
|
|
648
|
-
}
|
|
649
|
-
|
|
650
|
-
constructor(public refType: ReferenceType) {
|
|
651
|
-
super();
|
|
652
|
-
this.cachedLength = 1;
|
|
653
|
-
}
|
|
654
|
-
|
|
655
|
-
toJSONObject() {
|
|
656
|
-
const obj: IJSONMarkerSegment = { marker: { refType: this.refType } };
|
|
657
|
-
super.addSerializedProps(obj);
|
|
658
|
-
return obj;
|
|
659
|
-
}
|
|
660
|
-
|
|
661
|
-
static fromJSONObject(spec: any) {
|
|
662
|
-
if (spec && typeof spec === "object" && "marker" in spec) {
|
|
663
|
-
return Marker.make(
|
|
664
|
-
spec.marker.refType,
|
|
665
|
-
spec.props as PropertySet);
|
|
666
|
-
}
|
|
667
|
-
return undefined;
|
|
668
|
-
}
|
|
669
|
-
|
|
670
|
-
clone() {
|
|
671
|
-
const b = Marker.make(this.refType, this.properties);
|
|
672
|
-
this.cloneInto(b);
|
|
673
|
-
return b;
|
|
674
|
-
}
|
|
675
|
-
|
|
676
|
-
getSegment() {
|
|
677
|
-
return this;
|
|
678
|
-
}
|
|
679
|
-
|
|
680
|
-
getOffset() {
|
|
681
|
-
return 0;
|
|
682
|
-
}
|
|
683
|
-
|
|
684
|
-
hasSimpleType(simpleTypeName: string) {
|
|
685
|
-
return !!this.properties &&
|
|
686
|
-
this.properties[reservedMarkerSimpleTypeKey] === simpleTypeName;
|
|
687
|
-
}
|
|
688
|
-
|
|
689
|
-
getProperties() {
|
|
690
|
-
return this.properties;
|
|
691
|
-
}
|
|
692
|
-
|
|
693
|
-
getId(): string | undefined {
|
|
694
|
-
if (this.properties && this.properties[reservedMarkerIdKey]) {
|
|
695
|
-
return this.properties[reservedMarkerIdKey] as string;
|
|
696
|
-
}
|
|
697
|
-
}
|
|
698
|
-
|
|
699
|
-
/**
|
|
700
|
-
* @deprecated - use refHasTileLabels
|
|
701
|
-
*/
|
|
702
|
-
hasTileLabels() {
|
|
703
|
-
return refHasTileLabels(this);
|
|
704
|
-
}
|
|
705
|
-
/**
|
|
706
|
-
* @deprecated - use refHasRangeLabels
|
|
707
|
-
*/
|
|
708
|
-
hasRangeLabels() {
|
|
709
|
-
return refHasRangeLabels(this);
|
|
710
|
-
}
|
|
711
|
-
/**
|
|
712
|
-
* @deprecated - use refHasTileLabel
|
|
713
|
-
*/
|
|
714
|
-
hasTileLabel(label: string): boolean {
|
|
715
|
-
return refHasTileLabel(this, label);
|
|
716
|
-
}
|
|
717
|
-
/**
|
|
718
|
-
* @deprecated - use refHasRangeLabel
|
|
719
|
-
*/
|
|
720
|
-
hasRangeLabel(label: string): boolean {
|
|
721
|
-
return refHasRangeLabel(this, label);
|
|
722
|
-
}
|
|
723
|
-
/**
|
|
724
|
-
* @deprecated - use refGetTileLabels
|
|
725
|
-
*/
|
|
726
|
-
getTileLabels(): string[] | undefined {
|
|
727
|
-
return refGetTileLabels(this);
|
|
728
|
-
}
|
|
729
|
-
/**
|
|
730
|
-
* @deprecated - use refGetRangeLabels
|
|
731
|
-
*/
|
|
732
|
-
getRangeLabels(): string[] | undefined {
|
|
733
|
-
return refGetRangeLabels(this);
|
|
734
|
-
}
|
|
735
|
-
|
|
736
|
-
toString() {
|
|
737
|
-
let bbuf = "";
|
|
738
|
-
if (refTypeIncludesFlag(this, ReferenceType.Tile)) {
|
|
739
|
-
bbuf += "Tile";
|
|
740
|
-
}
|
|
741
|
-
if (refTypeIncludesFlag(this, ReferenceType.NestBegin)) {
|
|
742
|
-
if (bbuf.length > 0) {
|
|
743
|
-
bbuf += "; ";
|
|
744
|
-
}
|
|
745
|
-
bbuf += "RangeBegin";
|
|
746
|
-
}
|
|
747
|
-
if (refTypeIncludesFlag(this, ReferenceType.NestEnd)) {
|
|
748
|
-
if (bbuf.length > 0) {
|
|
749
|
-
bbuf += "; ";
|
|
750
|
-
}
|
|
751
|
-
bbuf += "RangeEnd";
|
|
752
|
-
}
|
|
753
|
-
let lbuf = "";
|
|
754
|
-
const id = this.getId();
|
|
755
|
-
if (id) {
|
|
756
|
-
bbuf += ` (${id}) `;
|
|
757
|
-
}
|
|
758
|
-
const tileLabels = refGetTileLabels(this);
|
|
759
|
-
if (tileLabels) {
|
|
760
|
-
lbuf += "tile -- ";
|
|
761
|
-
for (let i = 0, len = tileLabels.length; i < len; i++) {
|
|
762
|
-
const tileLabel = tileLabels[i];
|
|
763
|
-
if (i > 0) {
|
|
764
|
-
lbuf += "; ";
|
|
765
|
-
}
|
|
766
|
-
lbuf += tileLabel;
|
|
767
|
-
}
|
|
768
|
-
}
|
|
769
|
-
const rangeLabels = refGetRangeLabels(this);
|
|
770
|
-
if (rangeLabels) {
|
|
771
|
-
let rangeKind = "begin";
|
|
772
|
-
if (refTypeIncludesFlag(this, ReferenceType.NestEnd)) {
|
|
773
|
-
rangeKind = "end";
|
|
774
|
-
}
|
|
775
|
-
if (tileLabels) {
|
|
776
|
-
lbuf += " ";
|
|
777
|
-
}
|
|
778
|
-
lbuf += `range ${rangeKind} -- `;
|
|
779
|
-
const labels = rangeLabels;
|
|
780
|
-
for (let i = 0, len = labels.length; i < len; i++) {
|
|
781
|
-
const rangeLabel = labels[i];
|
|
782
|
-
if (i > 0) {
|
|
783
|
-
lbuf += "; ";
|
|
784
|
-
}
|
|
785
|
-
lbuf += rangeLabel;
|
|
786
|
-
}
|
|
787
|
-
}
|
|
788
|
-
let pbuf = "";
|
|
789
|
-
if (this.properties) {
|
|
790
|
-
pbuf += JSON.stringify(this.properties, (key, value) => {
|
|
791
|
-
// Avoid circular reference when stringifying makers containing handles.
|
|
792
|
-
// (Substitute a debug string instead.)
|
|
793
|
-
const handle = !!value && value.IFluidHandle;
|
|
794
|
-
|
|
795
|
-
// eslint-disable-next-line @typescript-eslint/no-unsafe-return
|
|
796
|
-
return handle
|
|
797
|
-
? `#Handle(${handle.routeContext.path}/${handle.path})`
|
|
798
|
-
: value;
|
|
799
|
-
});
|
|
800
|
-
}
|
|
801
|
-
return `M ${bbuf}: ${lbuf} ${pbuf}`;
|
|
802
|
-
}
|
|
803
|
-
|
|
804
|
-
protected createSplitSegmentAt(pos: number) {
|
|
805
|
-
return undefined;
|
|
806
|
-
}
|
|
807
|
-
|
|
808
|
-
canAppend(segment: ISegment): boolean {
|
|
809
|
-
return false;
|
|
810
|
-
}
|
|
811
|
-
|
|
812
|
-
append() { throw new Error("Can not append to marker"); }
|
|
813
|
-
}
|
|
814
|
-
|
|
815
|
-
export enum IncrementalExecOp {
|
|
816
|
-
Go,
|
|
817
|
-
Stop,
|
|
818
|
-
Yield,
|
|
819
|
-
}
|
|
820
|
-
|
|
821
|
-
export class IncrementalMapState<TContext> {
|
|
822
|
-
op = IncrementalExecOp.Go;
|
|
823
|
-
constructor(
|
|
824
|
-
public block: IMergeBlock,
|
|
825
|
-
public actions: IncrementalSegmentActions<TContext>,
|
|
826
|
-
public pos: number,
|
|
827
|
-
public refSeq: number,
|
|
828
|
-
public clientId: number,
|
|
829
|
-
public context: TContext,
|
|
830
|
-
public start: number,
|
|
831
|
-
public end: number,
|
|
832
|
-
public childIndex = 0,
|
|
833
|
-
) {
|
|
834
|
-
}
|
|
835
|
-
}
|
|
836
|
-
|
|
837
|
-
export class CollaborationWindow {
|
|
838
|
-
clientId = LocalClientId;
|
|
839
|
-
collaborating = false;
|
|
840
|
-
// Lowest-numbered segment in window; no client can reference a state before this one
|
|
841
|
-
minSeq = 0;
|
|
842
|
-
// Highest-numbered segment in window and current
|
|
843
|
-
// reference segment for this client
|
|
844
|
-
currentSeq = 0;
|
|
845
|
-
|
|
846
|
-
localSeq = 0;
|
|
847
|
-
|
|
848
|
-
loadFrom(a: CollaborationWindow) {
|
|
849
|
-
this.clientId = a.clientId;
|
|
850
|
-
this.collaborating = a.collaborating;
|
|
851
|
-
this.minSeq = a.minSeq;
|
|
852
|
-
this.currentSeq = a.currentSeq;
|
|
853
|
-
}
|
|
854
|
-
}
|
|
855
|
-
|
|
856
|
-
export const compareNumbers = (a: number, b: number) => a - b;
|
|
857
|
-
|
|
858
|
-
export const compareStrings = (a: string, b: string) => a.localeCompare(b);
|
|
859
|
-
|
|
860
|
-
const indentStrings = ["", " ", " "];
|
|
861
|
-
export function internedSpaces(n: number) {
|
|
862
|
-
if (indentStrings[n] === undefined) {
|
|
863
|
-
indentStrings[n] = "";
|
|
864
|
-
for (let i = 0; i < n; i++) {
|
|
865
|
-
indentStrings[n] += " ";
|
|
866
|
-
}
|
|
867
|
-
}
|
|
868
|
-
return indentStrings[n];
|
|
869
|
-
}
|
|
870
|
-
|
|
871
|
-
export interface IConsensusInfo {
|
|
872
|
-
marker: Marker;
|
|
873
|
-
callback: (m: Marker) => void;
|
|
874
|
-
}
|
|
875
|
-
|
|
876
|
-
export interface ClientSeq {
|
|
877
|
-
refSeq: number;
|
|
878
|
-
clientId: string;
|
|
879
|
-
}
|
|
880
|
-
|
|
881
|
-
export const clientSeqComparer: Comparer<ClientSeq> = {
|
|
882
|
-
min: { refSeq: -1, clientId: "" },
|
|
883
|
-
compare: (a, b) => a.refSeq - b.refSeq,
|
|
884
|
-
};
|
|
885
|
-
|
|
886
|
-
export interface LRUSegment {
|
|
887
|
-
segment?: ISegment;
|
|
888
|
-
maxSeq: number;
|
|
889
|
-
}
|
|
890
|
-
|
|
891
|
-
const LRUSegmentComparer: Comparer<LRUSegment> = {
|
|
892
|
-
min: { maxSeq: -2 },
|
|
893
|
-
compare: (a, b) => a.maxSeq - b.maxSeq,
|
|
894
|
-
};
|
|
895
|
-
|
|
896
|
-
export interface SegmentAccumulator {
|
|
897
|
-
segments: ISegment[];
|
|
898
|
-
}
|
|
899
|
-
|
|
900
|
-
interface IReferenceSearchInfo {
|
|
901
|
-
mergeTree: MergeTree;
|
|
902
|
-
tileLabel: string;
|
|
903
|
-
posPrecedesTile?: boolean;
|
|
904
|
-
tile?: ReferencePosition;
|
|
905
|
-
}
|
|
906
|
-
|
|
907
|
-
interface IMarkerSearchRangeInfo {
|
|
908
|
-
mergeTree: MergeTree;
|
|
909
|
-
rangeLabels: string[];
|
|
910
|
-
stacks: RangeStackMap;
|
|
911
|
-
}
|
|
912
|
-
|
|
913
|
-
function applyLeafRangeMarker(marker: Marker, searchInfo: IMarkerSearchRangeInfo) {
|
|
914
|
-
for (const rangeLabel of searchInfo.rangeLabels) {
|
|
915
|
-
if (refHasRangeLabel(marker, rangeLabel)) {
|
|
916
|
-
let currentStack = searchInfo.stacks[rangeLabel];
|
|
917
|
-
if (currentStack === undefined) {
|
|
918
|
-
currentStack = new Stack<Marker>();
|
|
919
|
-
searchInfo.stacks[rangeLabel] = currentStack;
|
|
920
|
-
}
|
|
921
|
-
applyRangeReference(currentStack, marker);
|
|
922
|
-
}
|
|
923
|
-
}
|
|
924
|
-
}
|
|
925
|
-
function recordRangeLeaf(
|
|
926
|
-
segment: ISegment, segpos: number,
|
|
927
|
-
refSeq: number, clientId: number, start: number | undefined, end: number | undefined,
|
|
928
|
-
searchInfo: IMarkerSearchRangeInfo) {
|
|
929
|
-
if (Marker.is(segment)) {
|
|
930
|
-
if (segment.refType &
|
|
931
|
-
(ReferenceType.NestBegin | ReferenceType.NestEnd)) {
|
|
932
|
-
applyLeafRangeMarker(segment, searchInfo);
|
|
272
|
+
if (stack === undefined) {
|
|
273
|
+
stack = new Stack<ReferencePosition>();
|
|
274
|
+
rangeStacks[label] = stack;
|
|
933
275
|
}
|
|
276
|
+
applyRangeReference(stack, refPos);
|
|
934
277
|
}
|
|
935
|
-
return false;
|
|
936
|
-
}
|
|
937
|
-
|
|
938
|
-
function rangeShift(
|
|
939
|
-
node: IMergeNode, segpos: number, refSeq: number, clientId: number,
|
|
940
|
-
offset: number | undefined, end: number | undefined, searchInfo: IMarkerSearchRangeInfo) {
|
|
941
278
|
if (node.isLeaf()) {
|
|
942
|
-
const
|
|
943
|
-
if ((
|
|
944
|
-
if (
|
|
945
|
-
|
|
946
|
-
|
|
279
|
+
const segment = node;
|
|
280
|
+
if ((mergeTree.localNetLength(segment) ?? 0) > 0) {
|
|
281
|
+
if (Marker.is(segment)) {
|
|
282
|
+
const markerId = segment.getId();
|
|
283
|
+
// Also in insertMarker but need for reload segs case
|
|
284
|
+
// can add option for this only from reload segs
|
|
285
|
+
if (markerId) {
|
|
286
|
+
mergeTree.mapIdToSegment(markerId, segment);
|
|
287
|
+
}
|
|
288
|
+
if (refTypeIncludesFlag(segment, ReferenceType.Tile)) {
|
|
289
|
+
addTile(segment, rightmostTiles);
|
|
290
|
+
addTileIfNotPresent(segment, leftmostTiles);
|
|
291
|
+
}
|
|
292
|
+
if (segment.refType & (ReferenceType.NestBegin | ReferenceType.NestEnd)) {
|
|
293
|
+
const rangeLabels = refGetRangeLabels(segment);
|
|
294
|
+
if (rangeLabels) {
|
|
295
|
+
for (const label of rangeLabels) {
|
|
296
|
+
updateRangeInfo(label, segment);
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
} else {
|
|
301
|
+
const baseSegment = node as BaseSegment;
|
|
302
|
+
if (baseSegment.localRefs && (baseSegment.localRefs.hierRefCount !== undefined) &&
|
|
303
|
+
(baseSegment.localRefs.hierRefCount > 0)) {
|
|
304
|
+
for (const lref of baseSegment.localRefs) {
|
|
305
|
+
if (refTypeIncludesFlag(lref, ReferenceType.Tile)) {
|
|
306
|
+
addTile(lref, rightmostTiles);
|
|
307
|
+
addTileIfNotPresent(lref, leftmostTiles);
|
|
308
|
+
}
|
|
309
|
+
if (lref.refType & (ReferenceType.NestBegin | ReferenceType.NestEnd)) {
|
|
310
|
+
for (const label of refGetRangeLabels(lref)!) {
|
|
311
|
+
updateRangeInfo(label, lref);
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
}
|
|
947
316
|
}
|
|
948
317
|
}
|
|
949
318
|
} else {
|
|
950
319
|
const block = <IHierBlock>node;
|
|
951
|
-
applyStackDelta(
|
|
320
|
+
applyStackDelta(rangeStacks, block.rangeStacks);
|
|
321
|
+
extend(rightmostTiles, block.rightmostTiles);
|
|
322
|
+
extendIfUndefined(leftmostTiles, block.leftmostTiles);
|
|
952
323
|
}
|
|
953
|
-
return true;
|
|
954
324
|
}
|
|
955
325
|
|
|
956
|
-
function
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
searchInfo: IReferenceSearchInfo) {
|
|
964
|
-
if (Marker.is(segment)) {
|
|
965
|
-
if (refHasTileLabel(segment, searchInfo.tileLabel)) {
|
|
966
|
-
searchInfo.tile = segment;
|
|
326
|
+
function extendIfUndefined<T>(base: MapLike<T>, extension: MapLike<T> | undefined) {
|
|
327
|
+
if (extension !== undefined) {
|
|
328
|
+
// eslint-disable-next-line no-restricted-syntax
|
|
329
|
+
for (const key in extension) {
|
|
330
|
+
if (base[key] === undefined) {
|
|
331
|
+
base[key] = extension[key];
|
|
332
|
+
}
|
|
967
333
|
}
|
|
968
334
|
}
|
|
969
|
-
return
|
|
335
|
+
return base;
|
|
970
336
|
}
|
|
971
337
|
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
338
|
+
class HierMergeBlock extends MergeBlock implements IHierBlock {
|
|
339
|
+
public rightmostTiles: MapLike<ReferencePosition>;
|
|
340
|
+
public leftmostTiles: MapLike<ReferencePosition>;
|
|
341
|
+
public rangeStacks: MapLike<Stack<ReferencePosition>>;
|
|
342
|
+
|
|
343
|
+
constructor(childCount: number) {
|
|
344
|
+
super(childCount);
|
|
345
|
+
this.rightmostTiles = createMap<ReferencePosition>();
|
|
346
|
+
this.leftmostTiles = createMap<ReferencePosition>();
|
|
347
|
+
this.rangeStacks = createMap<Stack<ReferencePosition>>();
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
/**
|
|
351
|
+
* @deprecated For internal use only. public export will be removed.
|
|
352
|
+
* @internal
|
|
353
|
+
*/
|
|
354
|
+
public addNodeReferences(mergeTree: MergeTree, node: IMergeNode) {
|
|
355
|
+
addNodeReferences(mergeTree, node, this.rightmostTiles, this.leftmostTiles,
|
|
356
|
+
this.rangeStacks);
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
public hierBlock() {
|
|
360
|
+
return this;
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
public hierToString(indentCount: number) {
|
|
364
|
+
let strbuf = "";
|
|
365
|
+
// eslint-disable-next-line guard-for-in, no-restricted-syntax
|
|
366
|
+
for (const key in this.rangeStacks) {
|
|
367
|
+
const stack = this.rangeStacks[key];
|
|
368
|
+
strbuf += internedSpaces(indentCount);
|
|
369
|
+
strbuf += `${key}: `;
|
|
370
|
+
for (const item of stack.items) {
|
|
371
|
+
strbuf += `${item.toString()} `;
|
|
980
372
|
}
|
|
373
|
+
strbuf += "\n";
|
|
981
374
|
}
|
|
982
|
-
|
|
983
|
-
const block = <IHierBlock>node;
|
|
984
|
-
let marker: Marker;
|
|
985
|
-
if (searchInfo.posPrecedesTile) {
|
|
986
|
-
marker = <Marker>block.rightmostTiles[searchInfo.tileLabel];
|
|
987
|
-
} else {
|
|
988
|
-
marker = <Marker>block.leftmostTiles[searchInfo.tileLabel];
|
|
989
|
-
}
|
|
990
|
-
if (marker !== undefined) {
|
|
991
|
-
searchInfo.tile = marker;
|
|
992
|
-
}
|
|
375
|
+
return strbuf;
|
|
993
376
|
}
|
|
994
|
-
return true;
|
|
995
377
|
}
|
|
996
378
|
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
379
|
+
/**
|
|
380
|
+
* @deprecated For internal use only. public export will be removed.
|
|
381
|
+
* @internal
|
|
382
|
+
*/
|
|
383
|
+
export interface ClientSeq {
|
|
384
|
+
refSeq: number;
|
|
385
|
+
clientId: string;
|
|
1000
386
|
}
|
|
1001
387
|
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
388
|
+
/**
|
|
389
|
+
* @deprecated For internal use only. public export will be removed.
|
|
390
|
+
* @internal
|
|
391
|
+
*/
|
|
392
|
+
export const clientSeqComparer: Comparer<ClientSeq> = {
|
|
393
|
+
min: { refSeq: -1, clientId: "" },
|
|
394
|
+
compare: (a, b) => a.refSeq - b.refSeq,
|
|
1005
395
|
};
|
|
1006
396
|
|
|
1007
|
-
|
|
397
|
+
/**
|
|
398
|
+
* @deprecated For internal use only. public export will be removed.
|
|
399
|
+
* @internal
|
|
400
|
+
*/
|
|
401
|
+
export interface LRUSegment {
|
|
402
|
+
segment?: ISegment;
|
|
403
|
+
maxSeq: number;
|
|
404
|
+
}
|
|
1008
405
|
|
|
1009
|
-
|
|
406
|
+
/**
|
|
407
|
+
* @deprecated For internal use only. public export will be removed.
|
|
408
|
+
* @internal
|
|
409
|
+
*/
|
|
1010
410
|
export class MergeTree {
|
|
1011
411
|
private static readonly zamboniSegmentsMaxCount = 2;
|
|
1012
412
|
public static readonly options = {
|
|
@@ -1021,8 +421,19 @@ export class MergeTree {
|
|
|
1021
421
|
root: IMergeBlock;
|
|
1022
422
|
private readonly blockUpdateActions: BlockUpdateActions = MergeTree.initBlockUpdateActions;
|
|
1023
423
|
public readonly collabWindow = new CollaborationWindow();
|
|
424
|
+
|
|
425
|
+
/**
|
|
426
|
+
* @deprecated for internal use only. public export will be removed.
|
|
427
|
+
* @internal
|
|
428
|
+
*/
|
|
1024
429
|
public pendingSegments: List<SegmentGroup> | undefined;
|
|
1025
430
|
private segmentsToScour: Heap<LRUSegment> | undefined;
|
|
431
|
+
/**
|
|
432
|
+
* Whether or not all blocks in the mergeTree currently have information about local partial lengths computed.
|
|
433
|
+
* This information is only necessary on reconnect, and otherwise costly to bookkeep.
|
|
434
|
+
* This field enables tracking whether partials need to be recomputed using localSeq information.
|
|
435
|
+
*/
|
|
436
|
+
private localPartialsComputed = false;
|
|
1026
437
|
// TODO: add remove on segment remove
|
|
1027
438
|
// for now assume only markers have ids and so point directly at the Segment
|
|
1028
439
|
// if we need to have pointers to non-markers, we can change to point at local refs
|
|
@@ -1072,11 +483,36 @@ export class MergeTree {
|
|
|
1072
483
|
return b;
|
|
1073
484
|
}
|
|
1074
485
|
|
|
1075
|
-
|
|
486
|
+
/**
|
|
487
|
+
* Compute the net length of this segment from a local perspective.
|
|
488
|
+
* @param segment - Segment whose length to find
|
|
489
|
+
* @param localSeq - localSeq at which to find the length of this segment. If not provided,
|
|
490
|
+
* default is to consider the local client's current perspective. Only local sequence
|
|
491
|
+
* numbers corresponding to un-acked operations give valid results.
|
|
492
|
+
*/
|
|
493
|
+
public localNetLength(segment: ISegment, refSeq?: number, localSeq?: number) {
|
|
1076
494
|
const removalInfo = toRemovalInfo(segment);
|
|
1077
|
-
if (
|
|
1078
|
-
return 0;
|
|
495
|
+
if (localSeq === undefined) {
|
|
496
|
+
return removalInfo !== undefined ? 0 : segment.cachedLength;
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
assert(refSeq !== undefined, 0x398 /* localSeq provided for local length without refSeq */);
|
|
500
|
+
assert(segment.seq !== undefined, 0x399 /* segment with no seq in mergeTree */);
|
|
501
|
+
const { seq, removedSeq, localRemovedSeq } = segment;
|
|
502
|
+
if (seq !== UnassignedSequenceNumber) {
|
|
503
|
+
// inserted remotely
|
|
504
|
+
if (seq > refSeq
|
|
505
|
+
|| (removedSeq !== undefined && removedSeq !== UnassignedSequenceNumber && removedSeq <= refSeq)
|
|
506
|
+
|| (localRemovedSeq !== undefined && localRemovedSeq <= localSeq)) {
|
|
507
|
+
return 0;
|
|
508
|
+
}
|
|
509
|
+
return segment.cachedLength;
|
|
1079
510
|
} else {
|
|
511
|
+
assert(segment.localSeq !== undefined, 0x39a /* unacked segment with undefined localSeq */);
|
|
512
|
+
// inserted locally, still un-acked
|
|
513
|
+
if (segment.localSeq > localSeq || (localRemovedSeq !== undefined && localRemovedSeq <= localSeq)) {
|
|
514
|
+
return 0;
|
|
515
|
+
}
|
|
1080
516
|
return segment.cachedLength;
|
|
1081
517
|
}
|
|
1082
518
|
}
|
|
@@ -1218,11 +654,7 @@ export class MergeTree {
|
|
|
1218
654
|
segment.trackingCollection.trackingGroups.forEach((tg) => tg.unlink(segment));
|
|
1219
655
|
} else {
|
|
1220
656
|
holdNodes.push(segment);
|
|
1221
|
-
|
|
1222
|
-
prevSegment = segment;
|
|
1223
|
-
} else {
|
|
1224
|
-
prevSegment = undefined;
|
|
1225
|
-
}
|
|
657
|
+
prevSegment = this.localNetLength(segment) > 0 ? segment : undefined;
|
|
1226
658
|
}
|
|
1227
659
|
} else {
|
|
1228
660
|
holdNodes.push(segment);
|
|
@@ -1390,7 +822,7 @@ export class MergeTree {
|
|
|
1390
822
|
*/
|
|
1391
823
|
public get length() { return this.root.cachedLength; }
|
|
1392
824
|
|
|
1393
|
-
public getPosition(node: MergeNode, refSeq: number, clientId: number) {
|
|
825
|
+
public getPosition(node: MergeNode, refSeq: number, clientId: number, localSeq?: number) {
|
|
1394
826
|
let totalOffset = 0;
|
|
1395
827
|
let parent = node.parent;
|
|
1396
828
|
let prevParent: IMergeBlock | undefined;
|
|
@@ -1401,7 +833,7 @@ export class MergeTree {
|
|
|
1401
833
|
if ((prevParent && (child === prevParent)) || (child === node)) {
|
|
1402
834
|
break;
|
|
1403
835
|
}
|
|
1404
|
-
totalOffset += this.nodeLength(child, refSeq, clientId) ?? 0;
|
|
836
|
+
totalOffset += this.nodeLength(child, refSeq, clientId, localSeq) ?? 0;
|
|
1405
837
|
}
|
|
1406
838
|
prevParent = parent;
|
|
1407
839
|
parent = parent.parent;
|
|
@@ -1409,7 +841,9 @@ export class MergeTree {
|
|
|
1409
841
|
return totalOffset;
|
|
1410
842
|
}
|
|
1411
843
|
|
|
1412
|
-
public getContainingSegment<T extends ISegment>(pos: number, refSeq: number, clientId: number) {
|
|
844
|
+
public getContainingSegment<T extends ISegment>(pos: number, refSeq: number, clientId: number, localSeq?: number) {
|
|
845
|
+
assert(localSeq === undefined || clientId === this.collabWindow.clientId,
|
|
846
|
+
0x39b /* localSeq provided for non-local client */);
|
|
1413
847
|
let segment: T | undefined;
|
|
1414
848
|
let offset: number | undefined;
|
|
1415
849
|
|
|
@@ -1418,18 +852,19 @@ export class MergeTree {
|
|
|
1418
852
|
offset = start;
|
|
1419
853
|
return false;
|
|
1420
854
|
};
|
|
1421
|
-
this.searchBlock(this.root, pos, 0, refSeq, clientId, { leaf }, undefined);
|
|
855
|
+
this.searchBlock(this.root, pos, 0, refSeq, clientId, { leaf }, undefined, localSeq);
|
|
1422
856
|
return { segment, offset };
|
|
1423
857
|
}
|
|
1424
858
|
|
|
1425
859
|
/**
|
|
1426
|
-
* @
|
|
1427
|
-
* @param
|
|
1428
|
-
* @returns The segment
|
|
860
|
+
* @remarks Must only be used by client.
|
|
861
|
+
* @param segment - The segment to slide from.
|
|
862
|
+
* @returns The segment to.
|
|
863
|
+
* @internal
|
|
1429
864
|
*/
|
|
1430
|
-
public _getSlideToSegment(
|
|
1431
|
-
if (!
|
|
1432
|
-
return
|
|
865
|
+
public _getSlideToSegment(segment: ISegment | undefined): ISegment | undefined {
|
|
866
|
+
if (!segment || !isRemovedAndAcked(segment)) {
|
|
867
|
+
return segment;
|
|
1433
868
|
}
|
|
1434
869
|
let slideToSegment: ISegment | undefined;
|
|
1435
870
|
const goFurtherToFindSlideToSegment = (seg) => {
|
|
@@ -1440,22 +875,13 @@ export class MergeTree {
|
|
|
1440
875
|
return true;
|
|
1441
876
|
};
|
|
1442
877
|
// Slide to the next farthest valid segment in the tree.
|
|
1443
|
-
this.rightExcursion(
|
|
878
|
+
this.rightExcursion(segment, goFurtherToFindSlideToSegment);
|
|
1444
879
|
if (slideToSegment) {
|
|
1445
|
-
return
|
|
880
|
+
return slideToSegment;
|
|
1446
881
|
}
|
|
1447
882
|
// If no such segment is found, slide to the last valid segment.
|
|
1448
|
-
this.leftExcursion(
|
|
1449
|
-
|
|
1450
|
-
// Workaround TypeScript issue (https://github.com/microsoft/TypeScript/issues/9998)
|
|
1451
|
-
slideToSegment = slideToSegment as ISegment | undefined;
|
|
1452
|
-
|
|
1453
|
-
if (slideToSegment) {
|
|
1454
|
-
// If slid nearer then offset should be at the end of the segment
|
|
1455
|
-
return { segment: slideToSegment, offset: slideToSegment.cachedLength - 1 };
|
|
1456
|
-
}
|
|
1457
|
-
|
|
1458
|
-
return { segment: undefined, offset: 0 };
|
|
883
|
+
this.leftExcursion(segment, goFurtherToFindSlideToSegment);
|
|
884
|
+
return slideToSegment;
|
|
1459
885
|
}
|
|
1460
886
|
|
|
1461
887
|
/**
|
|
@@ -1464,31 +890,26 @@ export class MergeTree {
|
|
|
1464
890
|
* Otherwise eventual consistency is not guaranteed.
|
|
1465
891
|
* See `packages\dds\merge-tree\REFERENCEPOSITIONS.md`
|
|
1466
892
|
*/
|
|
1467
|
-
private slideReferences(segment: ISegment, refsToSlide:
|
|
893
|
+
private slideReferences(segment: ISegment, refsToSlide: Iterable<LocalReferencePosition>) {
|
|
1468
894
|
assert(
|
|
1469
895
|
isRemovedAndAcked(segment),
|
|
1470
896
|
0x2f1 /* slideReferences from a segment which has not been removed and acked */);
|
|
1471
897
|
assert(!!segment.localRefs, 0x2f2 /* Ref not in the segment localRefs */);
|
|
1472
|
-
const
|
|
1473
|
-
|
|
1474
|
-
|
|
1475
|
-
newSegment.
|
|
1476
|
-
|
|
1477
|
-
for (const ref of refsToSlide) {
|
|
1478
|
-
ref.callbacks?.beforeSlide?.();
|
|
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;
|
|
898
|
+
const newSegment = this._getSlideToSegment(segment);
|
|
899
|
+
if (newSegment) {
|
|
900
|
+
const localRefs = newSegment.localRefs ??= new LocalReferenceCollection(newSegment);
|
|
901
|
+
if (newSegment.ordinal < segment.ordinal) {
|
|
902
|
+
localRefs.addAfterTombstones(refsToSlide);
|
|
1485
903
|
} else {
|
|
1486
|
-
|
|
1487
|
-
|
|
1488
|
-
|
|
1489
|
-
|
|
904
|
+
localRefs.addBeforeTombstones(refsToSlide);
|
|
905
|
+
}
|
|
906
|
+
} else {
|
|
907
|
+
for (const ref of refsToSlide) {
|
|
908
|
+
ref.callbacks?.beforeSlide?.();
|
|
909
|
+
assertLocalReferences(ref);
|
|
910
|
+
ref.link(undefined, 0, undefined);
|
|
911
|
+
ref.callbacks?.afterSlide?.();
|
|
1490
912
|
}
|
|
1491
|
-
ref.callbacks?.afterSlide?.();
|
|
1492
913
|
}
|
|
1493
914
|
// TODO is it required to update the path lengths?
|
|
1494
915
|
if (newSegment) {
|
|
@@ -1497,21 +918,23 @@ export class MergeTree {
|
|
|
1497
918
|
}
|
|
1498
919
|
}
|
|
1499
920
|
|
|
1500
|
-
private updateSegmentRefsAfterMarkRemoved(segment: ISegment, pending: boolean)
|
|
921
|
+
private updateSegmentRefsAfterMarkRemoved(segment: ISegment, pending: boolean):
|
|
922
|
+
LocalReferencePosition[] | undefined {
|
|
1501
923
|
if (!segment.localRefs || segment.localRefs.empty) {
|
|
1502
|
-
return;
|
|
924
|
+
return undefined;
|
|
1503
925
|
}
|
|
1504
|
-
const refsToSlide:
|
|
1505
|
-
const
|
|
926
|
+
const refsToSlide: ReferencePosition[] = [];
|
|
927
|
+
const removedRefs: LocalReferencePosition[] = [];
|
|
1506
928
|
for (const lref of segment.localRefs) {
|
|
1507
929
|
if (refTypeIncludesFlag(lref, ReferenceType.StayOnRemove)) {
|
|
1508
|
-
|
|
1509
|
-
}
|
|
1510
|
-
if (pending) {
|
|
1511
|
-
refsToStay.push(lref);
|
|
1512
|
-
} else {
|
|
930
|
+
continue;
|
|
931
|
+
} if (refTypeIncludesFlag(lref, ReferenceType.SlideOnRemove)) {
|
|
932
|
+
if (!pending) {
|
|
1513
933
|
refsToSlide.push(lref);
|
|
1514
934
|
}
|
|
935
|
+
} else {
|
|
936
|
+
segment.localRefs.removeLocalRef(lref);
|
|
937
|
+
removedRefs.push(lref);
|
|
1515
938
|
}
|
|
1516
939
|
}
|
|
1517
940
|
// Rethink implementation of keeping and sliding refs once other reference
|
|
@@ -1519,28 +942,40 @@ export class MergeTree {
|
|
|
1519
942
|
if (!pending) {
|
|
1520
943
|
this.slideReferences(segment, refsToSlide);
|
|
1521
944
|
}
|
|
1522
|
-
|
|
1523
|
-
|
|
1524
|
-
lref.segment = segment;
|
|
1525
|
-
segment.localRefs.addLocalRef(lref);
|
|
1526
|
-
}
|
|
945
|
+
// return only the refs that have been entirely removed
|
|
946
|
+
return removedRefs.length > 0 ? removedRefs : undefined;
|
|
1527
947
|
}
|
|
1528
948
|
|
|
1529
949
|
private blockLength(node: IMergeBlock, refSeq: number, clientId: number) {
|
|
1530
|
-
|
|
1531
|
-
|
|
1532
|
-
|
|
1533
|
-
return node.cachedLength;
|
|
1534
|
-
}
|
|
950
|
+
return (this.collabWindow.collaborating) && (clientId !== this.collabWindow.clientId)
|
|
951
|
+
? node.partialLengths!.getPartialLength(refSeq, clientId)
|
|
952
|
+
: node.cachedLength;
|
|
1535
953
|
}
|
|
1536
954
|
|
|
1537
|
-
private nodeLength(node: IMergeNode, refSeq: number, clientId: number) {
|
|
955
|
+
private nodeLength(node: IMergeNode, refSeq: number, clientId: number, localSeq?: number) {
|
|
1538
956
|
if ((!this.collabWindow.collaborating) || (this.collabWindow.clientId === clientId)) {
|
|
1539
|
-
|
|
1540
|
-
|
|
957
|
+
if (node.isLeaf()) {
|
|
958
|
+
return this.localNetLength(node, refSeq, localSeq);
|
|
959
|
+
} else if (localSeq === undefined) {
|
|
960
|
+
// Local client sees all segments, even when collaborating
|
|
1541
961
|
return node.cachedLength;
|
|
1542
962
|
} else {
|
|
1543
|
-
|
|
963
|
+
if (!this.localPartialsComputed) {
|
|
964
|
+
const rebaseCollabWindow = new CollaborationWindow();
|
|
965
|
+
rebaseCollabWindow.loadFrom(this.collabWindow);
|
|
966
|
+
if (refSeq < this.collabWindow.minSeq) {
|
|
967
|
+
rebaseCollabWindow.minSeq = refSeq;
|
|
968
|
+
}
|
|
969
|
+
this.root.partialLengths = PartialSequenceLengths.combine(
|
|
970
|
+
this.root,
|
|
971
|
+
rebaseCollabWindow,
|
|
972
|
+
true,
|
|
973
|
+
true,
|
|
974
|
+
);
|
|
975
|
+
this.localPartialsComputed = true;
|
|
976
|
+
}
|
|
977
|
+
// Local client should see all segments except those after localSeq.
|
|
978
|
+
return node.partialLengths!.getPartialLength(refSeq, clientId, localSeq);
|
|
1544
979
|
}
|
|
1545
980
|
} else {
|
|
1546
981
|
// Sequence number within window
|
|
@@ -1562,11 +997,7 @@ export class MergeTree {
|
|
|
1562
997
|
((segment.seq !== UnassignedSequenceNumber) && (segment.seq! <= refSeq)))) {
|
|
1563
998
|
// Segment happened by reference sequence number or segment from requesting client
|
|
1564
999
|
if (removalInfo !== undefined) {
|
|
1565
|
-
|
|
1566
|
-
return 0;
|
|
1567
|
-
} else {
|
|
1568
|
-
return segment.cachedLength;
|
|
1569
|
-
}
|
|
1000
|
+
return removalInfo.removedClientIds.includes(clientId) ? 0 : segment.cachedLength;
|
|
1570
1001
|
} else {
|
|
1571
1002
|
return segment.cachedLength;
|
|
1572
1003
|
}
|
|
@@ -1626,13 +1057,23 @@ export class MergeTree {
|
|
|
1626
1057
|
refSeq = this.collabWindow.currentSeq,
|
|
1627
1058
|
clientId = this.collabWindow.clientId) {
|
|
1628
1059
|
const seg = refPos.getSegment();
|
|
1629
|
-
if (seg
|
|
1630
|
-
|
|
1631
|
-
return offset + this.getPosition(seg, refSeq, clientId);
|
|
1060
|
+
if (seg?.parent === undefined) {
|
|
1061
|
+
return DetachedReferencePosition;
|
|
1632
1062
|
}
|
|
1633
|
-
|
|
1063
|
+
if (refPos.isLeaf()) {
|
|
1064
|
+
return this.getPosition(refPos, refSeq, clientId);
|
|
1065
|
+
}
|
|
1066
|
+
if (refTypeIncludesFlag(refPos, ReferenceType.Transient)
|
|
1067
|
+
|| seg.localRefs?.has(refPos)) {
|
|
1068
|
+
const offset = isRemoved(seg) ? 0 : refPos.getOffset();
|
|
1069
|
+
return offset + this.getPosition(seg, refSeq, clientId);
|
|
1070
|
+
}
|
|
1071
|
+
return DetachedReferencePosition;
|
|
1634
1072
|
}
|
|
1635
|
-
|
|
1073
|
+
/**
|
|
1074
|
+
* @deprecated for internal use only. public export will be removed.
|
|
1075
|
+
* @internal
|
|
1076
|
+
*/
|
|
1636
1077
|
public getStackContext(startPos: number, clientId: number, rangeLabels: string[]) {
|
|
1637
1078
|
const searchInfo: IMarkerSearchRangeInfo = {
|
|
1638
1079
|
mergeTree: this,
|
|
@@ -1646,14 +1087,22 @@ export class MergeTree {
|
|
|
1646
1087
|
}
|
|
1647
1088
|
|
|
1648
1089
|
// TODO: filter function
|
|
1649
|
-
|
|
1090
|
+
/**
|
|
1091
|
+
* Finds the nearest reference with ReferenceType.Tile to `startPos` in the direction dictated by `tilePrecedesPos`.
|
|
1092
|
+
*
|
|
1093
|
+
* @param startPos - Position at which to start the search
|
|
1094
|
+
* @param clientId - clientId dictating the perspective to search from
|
|
1095
|
+
* @param tileLabel - Label of the tile to search for
|
|
1096
|
+
* @param tilePrecedesPos - Whether the desired tile comes before (true) or after (false) `startPos`
|
|
1097
|
+
*/
|
|
1098
|
+
public findTile(startPos: number, clientId: number, tileLabel: string, tilePrecedesPos = true) {
|
|
1650
1099
|
const searchInfo: IReferenceSearchInfo = {
|
|
1651
1100
|
mergeTree: this,
|
|
1652
|
-
|
|
1101
|
+
tilePrecedesPos,
|
|
1653
1102
|
tileLabel,
|
|
1654
1103
|
};
|
|
1655
1104
|
|
|
1656
|
-
if (
|
|
1105
|
+
if (tilePrecedesPos) {
|
|
1657
1106
|
this.search(startPos, UniversalSequenceNumber, clientId,
|
|
1658
1107
|
{ leaf: recordTileStart, shift: tileShift }, searchInfo);
|
|
1659
1108
|
} else {
|
|
@@ -1667,8 +1116,8 @@ export class MergeTree {
|
|
|
1667
1116
|
const marker = <Marker>searchInfo.tile;
|
|
1668
1117
|
pos = this.getPosition(marker, UniversalSequenceNumber, clientId);
|
|
1669
1118
|
} else {
|
|
1670
|
-
const localRef =
|
|
1671
|
-
pos =
|
|
1119
|
+
const localRef = searchInfo.tile;
|
|
1120
|
+
pos = this.referencePositionToLocalPosition(localRef, UniversalSequenceNumber, clientId);
|
|
1672
1121
|
}
|
|
1673
1122
|
return { tile: searchInfo.tile, pos };
|
|
1674
1123
|
}
|
|
@@ -1681,8 +1130,15 @@ export class MergeTree {
|
|
|
1681
1130
|
}
|
|
1682
1131
|
|
|
1683
1132
|
private searchBlock<TClientData>(
|
|
1684
|
-
block: IMergeBlock,
|
|
1685
|
-
|
|
1133
|
+
block: IMergeBlock,
|
|
1134
|
+
pos: number,
|
|
1135
|
+
segpos: number,
|
|
1136
|
+
refSeq: number,
|
|
1137
|
+
clientId: number,
|
|
1138
|
+
actions: SegmentActions<TClientData> | undefined,
|
|
1139
|
+
clientData: TClientData,
|
|
1140
|
+
localSeq?: number,
|
|
1141
|
+
): ISegment | undefined {
|
|
1686
1142
|
let _pos = pos;
|
|
1687
1143
|
let _segpos = segpos;
|
|
1688
1144
|
const children = block.children;
|
|
@@ -1692,14 +1148,14 @@ export class MergeTree {
|
|
|
1692
1148
|
const contains = actions && actions.contains;
|
|
1693
1149
|
for (let childIndex = 0; childIndex < block.childCount; childIndex++) {
|
|
1694
1150
|
const child = children[childIndex];
|
|
1695
|
-
const len = this.nodeLength(child, refSeq, clientId) ?? 0;
|
|
1151
|
+
const len = this.nodeLength(child, refSeq, clientId, localSeq) ?? 0;
|
|
1696
1152
|
if (
|
|
1697
1153
|
(!contains && _pos < len)
|
|
1698
1154
|
|| (contains && contains(child, _pos, refSeq, clientId, undefined, undefined, clientData))
|
|
1699
1155
|
) {
|
|
1700
1156
|
// Found entry containing pos
|
|
1701
1157
|
if (!child.isLeaf()) {
|
|
1702
|
-
return this.searchBlock(child, _pos, _segpos, refSeq, clientId, actions, clientData);
|
|
1158
|
+
return this.searchBlock(child, _pos, _segpos, refSeq, clientId, actions, clientData, localSeq);
|
|
1703
1159
|
} else {
|
|
1704
1160
|
if (actions && actions.leaf) {
|
|
1705
1161
|
actions.leaf(child, _segpos, refSeq, clientId, _pos, -1, clientData);
|
|
@@ -1824,13 +1280,26 @@ export class MergeTree {
|
|
|
1824
1280
|
}
|
|
1825
1281
|
}
|
|
1826
1282
|
|
|
1827
|
-
private addToPendingList(segment: ISegment, segmentGroup?: SegmentGroup, localSeq?: number
|
|
1283
|
+
private addToPendingList(segment: ISegment, segmentGroup?: SegmentGroup, localSeq?: number,
|
|
1284
|
+
previousProps?: PropertySet) {
|
|
1828
1285
|
let _segmentGroup = segmentGroup;
|
|
1829
1286
|
if (_segmentGroup === undefined) {
|
|
1830
1287
|
// TODO: review the cast
|
|
1831
1288
|
_segmentGroup = { segments: [], localSeq } as SegmentGroup;
|
|
1289
|
+
if (previousProps) {
|
|
1290
|
+
_segmentGroup.previousProps = [];
|
|
1291
|
+
}
|
|
1832
1292
|
this.pendingSegments!.enqueue(_segmentGroup);
|
|
1833
1293
|
}
|
|
1294
|
+
|
|
1295
|
+
if ((!_segmentGroup.previousProps && previousProps) ||
|
|
1296
|
+
(_segmentGroup.previousProps && !previousProps)) {
|
|
1297
|
+
throw new Error("All segments in group should have previousProps or none");
|
|
1298
|
+
}
|
|
1299
|
+
if (previousProps) {
|
|
1300
|
+
_segmentGroup.previousProps!.push(previousProps);
|
|
1301
|
+
}
|
|
1302
|
+
|
|
1834
1303
|
segment.segmentGroups.enqueue(_segmentGroup);
|
|
1835
1304
|
return _segmentGroup;
|
|
1836
1305
|
}
|
|
@@ -1929,7 +1398,7 @@ export class MergeTree {
|
|
|
1929
1398
|
const splitNode = this.split(block);
|
|
1930
1399
|
if (block === this.root) {
|
|
1931
1400
|
this.updateRoot(splitNode);
|
|
1932
|
-
// Update root already updates all
|
|
1401
|
+
// Update root already updates all its children ordinals
|
|
1933
1402
|
ordinalUpdateNode = undefined;
|
|
1934
1403
|
} else {
|
|
1935
1404
|
this.insertChildNode(block.parent!, splitNode, block.index + 1);
|
|
@@ -2398,10 +1867,12 @@ export class MergeTree {
|
|
|
2398
1867
|
* @param clientId - The id of the client making the annotate
|
|
2399
1868
|
* @param seq - The sequence number of the annotate operation
|
|
2400
1869
|
* @param opArgs - The op args for the annotate op. this is passed to the merge tree callback if there is one
|
|
1870
|
+
* @param rollback - Whether this is for a local rollback and what kind
|
|
2401
1871
|
*/
|
|
2402
1872
|
public annotateRange(
|
|
2403
1873
|
start: number, end: number, props: PropertySet, combiningOp: ICombiningOp | undefined, refSeq: number,
|
|
2404
|
-
clientId: number, seq: number, opArgs: IMergeTreeDeltaOpArgs
|
|
1874
|
+
clientId: number, seq: number, opArgs: IMergeTreeDeltaOpArgs,
|
|
1875
|
+
rollback: PropertiesRollback = PropertiesRollback.None) {
|
|
2405
1876
|
this.ensureIntervalBoundary(start, refSeq, clientId);
|
|
2406
1877
|
this.ensureIntervalBoundary(end, refSeq, clientId);
|
|
2407
1878
|
const deltaSegments: IMergeTreeSegmentDelta[] = [];
|
|
@@ -2409,11 +1880,12 @@ export class MergeTree {
|
|
|
2409
1880
|
let segmentGroup: SegmentGroup | undefined;
|
|
2410
1881
|
|
|
2411
1882
|
const annotateSegment = (segment: ISegment) => {
|
|
2412
|
-
const propertyDeltas = segment.addProperties(props, combiningOp, seq, this.collabWindow);
|
|
1883
|
+
const propertyDeltas = segment.addProperties(props, combiningOp, seq, this.collabWindow, rollback);
|
|
2413
1884
|
deltaSegments.push({ segment, propertyDeltas });
|
|
2414
1885
|
if (this.collabWindow.collaborating) {
|
|
2415
1886
|
if (seq === UnassignedSequenceNumber) {
|
|
2416
|
-
segmentGroup = this.addToPendingList(segment, segmentGroup, localSeq
|
|
1887
|
+
segmentGroup = this.addToPendingList(segment, segmentGroup, localSeq,
|
|
1888
|
+
propertyDeltas ? propertyDeltas : {});
|
|
2417
1889
|
} else {
|
|
2418
1890
|
if (MergeTree.options.zamboniSegments) {
|
|
2419
1891
|
this.addToLRUSet(segment, seq);
|
|
@@ -2449,13 +1921,13 @@ export class MergeTree {
|
|
|
2449
1921
|
seq: number,
|
|
2450
1922
|
overwrite = false,
|
|
2451
1923
|
opArgs: IMergeTreeDeltaOpArgs,
|
|
2452
|
-
) {
|
|
1924
|
+
): void {
|
|
2453
1925
|
let _overwrite = overwrite;
|
|
2454
1926
|
this.ensureIntervalBoundary(start, refSeq, clientId);
|
|
2455
1927
|
this.ensureIntervalBoundary(end, refSeq, clientId);
|
|
2456
1928
|
let segmentGroup: SegmentGroup;
|
|
2457
1929
|
const removedSegments: IMergeTreeSegmentDelta[] = [];
|
|
2458
|
-
const
|
|
1930
|
+
const localOverlapWithRefs: ISegment[] = [];
|
|
2459
1931
|
const localSeq = seq === UnassignedSequenceNumber ? ++this.collabWindow.localSeq : undefined;
|
|
2460
1932
|
const markRemoved = (segment: ISegment, pos: number, _start: number, _end: number) => {
|
|
2461
1933
|
const existingRemovalInfo = toRemovalInfo(segment);
|
|
@@ -2464,11 +1936,14 @@ export class MergeTree {
|
|
|
2464
1936
|
if (existingRemovalInfo.removedSeq === UnassignedSequenceNumber) {
|
|
2465
1937
|
// we removed this locally, but someone else removed it first
|
|
2466
1938
|
// so put them at the head of the list
|
|
2467
|
-
//
|
|
2468
|
-
//
|
|
1939
|
+
// The list isn't ordered, but we keep the first removal at the head
|
|
1940
|
+
// for partialLengths bookkeeping purposes
|
|
2469
1941
|
existingRemovalInfo.removedClientIds.unshift(clientId);
|
|
1942
|
+
|
|
2470
1943
|
existingRemovalInfo.removedSeq = seq;
|
|
2471
|
-
segment.
|
|
1944
|
+
if (segment.localRefs?.empty === false) {
|
|
1945
|
+
localOverlapWithRefs.push(segment);
|
|
1946
|
+
}
|
|
2472
1947
|
} else {
|
|
2473
1948
|
// Do not replace earlier sequence number for remove
|
|
2474
1949
|
existingRemovalInfo.removedClientIds.push(clientId);
|
|
@@ -2480,9 +1955,6 @@ export class MergeTree {
|
|
|
2480
1955
|
|
|
2481
1956
|
removedSegments.push({ segment });
|
|
2482
1957
|
}
|
|
2483
|
-
if (segment.localRefs && !segment.localRefs.empty) {
|
|
2484
|
-
segmentsWithRefs.push(segment);
|
|
2485
|
-
}
|
|
2486
1958
|
|
|
2487
1959
|
// Save segment so can assign removed sequence number when acked by server
|
|
2488
1960
|
if (this.collabWindow.collaborating) {
|
|
@@ -2505,11 +1977,12 @@ export class MergeTree {
|
|
|
2505
1977
|
return true;
|
|
2506
1978
|
};
|
|
2507
1979
|
this.mapRange({ leaf: markRemoved, post: afterMarkRemoved }, refSeq, clientId, undefined, start, end);
|
|
2508
|
-
|
|
2509
|
-
|
|
2510
|
-
|
|
2511
|
-
|
|
2512
|
-
|
|
1980
|
+
// these segments are already viewed as being removed locally and are not event-ed
|
|
1981
|
+
// so can slide non-StayOnRemove refs immediately
|
|
1982
|
+
localOverlapWithRefs.forEach(
|
|
1983
|
+
(s) => this.slideReferences(s, Array.from(s.localRefs!)
|
|
1984
|
+
.filter((localRef) => !refTypeIncludesFlag(localRef, ReferenceType.StayOnRemove))),
|
|
1985
|
+
);
|
|
2513
1986
|
// opArgs == undefined => test code
|
|
2514
1987
|
if (this.mergeTreeDeltaCallback && removedSegments.length > 0) {
|
|
2515
1988
|
this.mergeTreeDeltaCallback(
|
|
@@ -2519,6 +1992,20 @@ export class MergeTree {
|
|
|
2519
1992
|
deltaSegments: removedSegments,
|
|
2520
1993
|
});
|
|
2521
1994
|
}
|
|
1995
|
+
const pending = this.collabWindow.collaborating && clientId === this.collabWindow.clientId;
|
|
1996
|
+
// these events are newly removed
|
|
1997
|
+
// so we slide after eventing in case the consumer wants to make reference
|
|
1998
|
+
// changes at remove time, like add a ref to track undo redo.
|
|
1999
|
+
removedSegments.forEach((rSeg) => {
|
|
2000
|
+
const removedRefs = this.updateSegmentRefsAfterMarkRemoved(rSeg.segment, pending);
|
|
2001
|
+
if (segmentGroup && removedRefs) {
|
|
2002
|
+
if (!segmentGroup.removedReferences) {
|
|
2003
|
+
segmentGroup.removedReferences = [];
|
|
2004
|
+
}
|
|
2005
|
+
segmentGroup.removedReferences.push(...removedRefs);
|
|
2006
|
+
}
|
|
2007
|
+
});
|
|
2008
|
+
|
|
2522
2009
|
if (this.collabWindow.collaborating && (seq !== UnassignedSequenceNumber)) {
|
|
2523
2010
|
if (MergeTree.options.zamboniSegments) {
|
|
2524
2011
|
this.zamboniSegments();
|
|
@@ -2526,10 +2013,122 @@ export class MergeTree {
|
|
|
2526
2013
|
}
|
|
2527
2014
|
}
|
|
2528
2015
|
|
|
2016
|
+
/**
|
|
2017
|
+
* Revert an unacked local op
|
|
2018
|
+
*/
|
|
2019
|
+
public rollback(op: IMergeTreeDeltaOp, localOpMetadata: SegmentGroup) {
|
|
2020
|
+
if (op.type === MergeTreeDeltaType.REMOVE) {
|
|
2021
|
+
const pendingSegmentGroup = this.pendingSegments?.pop?.();
|
|
2022
|
+
if (pendingSegmentGroup === undefined || pendingSegmentGroup !== localOpMetadata) {
|
|
2023
|
+
throw new Error("Rollback op doesn't match last edit");
|
|
2024
|
+
}
|
|
2025
|
+
for (const segment of pendingSegmentGroup.segments) {
|
|
2026
|
+
const segmentSegmentGroup = segment.segmentGroups.pop ? segment.segmentGroups.pop() : undefined;
|
|
2027
|
+
assert(segmentSegmentGroup === pendingSegmentGroup, 0x39c /* Unexpected segmentGroup in segment */);
|
|
2028
|
+
|
|
2029
|
+
assert(segment.removedClientIds !== undefined
|
|
2030
|
+
&& segment.removedClientIds[0] === this.collabWindow.clientId,
|
|
2031
|
+
0x39d /* Rollback segment removedClientId does not match local client */);
|
|
2032
|
+
segment.removedClientIds = undefined;
|
|
2033
|
+
segment.removedSeq = undefined;
|
|
2034
|
+
segment.localRemovedSeq = undefined;
|
|
2035
|
+
|
|
2036
|
+
if (this.mergeTreeDeltaCallback) {
|
|
2037
|
+
const insertOp = createInsertSegmentOp(this.findRollbackPosition(segment), segment);
|
|
2038
|
+
this.mergeTreeDeltaCallback(
|
|
2039
|
+
{ op: insertOp },
|
|
2040
|
+
{
|
|
2041
|
+
operation: MergeTreeDeltaType.INSERT,
|
|
2042
|
+
deltaSegments: [{ segment }],
|
|
2043
|
+
});
|
|
2044
|
+
}
|
|
2045
|
+
|
|
2046
|
+
for (let updateNode = segment.parent; updateNode !== undefined; updateNode = updateNode.parent) {
|
|
2047
|
+
this.blockUpdateLength(updateNode, UnassignedSequenceNumber, this.collabWindow.clientId);
|
|
2048
|
+
}
|
|
2049
|
+
}
|
|
2050
|
+
if (pendingSegmentGroup.removedReferences) {
|
|
2051
|
+
for (const ref of pendingSegmentGroup.removedReferences) {
|
|
2052
|
+
const seg = ref.getSegment();
|
|
2053
|
+
if (!seg) {
|
|
2054
|
+
throw new Error("Cannot rollback reference without segment");
|
|
2055
|
+
}
|
|
2056
|
+
const localRefs = seg.localRefs ??= new LocalReferenceCollection(seg);
|
|
2057
|
+
localRefs.addLocalRef(ref, ref.getOffset());
|
|
2058
|
+
}
|
|
2059
|
+
}
|
|
2060
|
+
} else if (op.type === MergeTreeDeltaType.INSERT || op.type === MergeTreeDeltaType.ANNOTATE) {
|
|
2061
|
+
const pendingSegmentGroup = this.pendingSegments?.pop?.();
|
|
2062
|
+
if (pendingSegmentGroup === undefined || pendingSegmentGroup !== localOpMetadata
|
|
2063
|
+
|| (op.type === MergeTreeDeltaType.ANNOTATE && !pendingSegmentGroup.previousProps)) {
|
|
2064
|
+
throw new Error("Rollback op doesn't match last edit");
|
|
2065
|
+
}
|
|
2066
|
+
let i = 0;
|
|
2067
|
+
for (const segment of pendingSegmentGroup.segments) {
|
|
2068
|
+
const segmentSegmentGroup = segment.segmentGroups.pop ? segment.segmentGroups.pop() : undefined;
|
|
2069
|
+
assert(segmentSegmentGroup === pendingSegmentGroup, 0x39e /* Unexpected segmentGroup in segment */);
|
|
2070
|
+
|
|
2071
|
+
const start = this.findRollbackPosition(segment);
|
|
2072
|
+
if (op.type === MergeTreeDeltaType.INSERT) {
|
|
2073
|
+
const removeOp = createRemoveRangeOp(start, start + segment.cachedLength);
|
|
2074
|
+
this.markRangeRemoved(
|
|
2075
|
+
start,
|
|
2076
|
+
start + segment.cachedLength,
|
|
2077
|
+
UniversalSequenceNumber,
|
|
2078
|
+
this.collabWindow.clientId,
|
|
2079
|
+
UniversalSequenceNumber,
|
|
2080
|
+
false,
|
|
2081
|
+
{ op: removeOp });
|
|
2082
|
+
} else /* op.type === MergeTreeDeltaType.ANNOTATE */ {
|
|
2083
|
+
const props = pendingSegmentGroup.previousProps![i];
|
|
2084
|
+
const rollbackType = (op.combiningOp && op.combiningOp.name === "rewrite") ?
|
|
2085
|
+
PropertiesRollback.Rewrite : PropertiesRollback.Rollback;
|
|
2086
|
+
const annotateOp = createAnnotateRangeOp(start, start + segment.cachedLength, props, undefined);
|
|
2087
|
+
this.annotateRange(
|
|
2088
|
+
start,
|
|
2089
|
+
start + segment.cachedLength,
|
|
2090
|
+
props,
|
|
2091
|
+
undefined,
|
|
2092
|
+
UniversalSequenceNumber,
|
|
2093
|
+
this.collabWindow.clientId,
|
|
2094
|
+
UniversalSequenceNumber,
|
|
2095
|
+
{ op: annotateOp },
|
|
2096
|
+
rollbackType);
|
|
2097
|
+
i++;
|
|
2098
|
+
}
|
|
2099
|
+
}
|
|
2100
|
+
} else {
|
|
2101
|
+
throw new Error("Unsupported op type for rollback");
|
|
2102
|
+
}
|
|
2103
|
+
}
|
|
2104
|
+
|
|
2105
|
+
/**
|
|
2106
|
+
* Walk the segments up to the current segment and calculate its position
|
|
2107
|
+
*/
|
|
2108
|
+
private findRollbackPosition(segment: ISegment) {
|
|
2109
|
+
let segmentPosition = 0;
|
|
2110
|
+
this.walkAllSegments(this.root, (seg) => {
|
|
2111
|
+
// If we've found the desired segment, terminate the walk and return 'segmentPosition'.
|
|
2112
|
+
if (seg === segment) {
|
|
2113
|
+
return false;
|
|
2114
|
+
}
|
|
2115
|
+
|
|
2116
|
+
// If not removed, increase position
|
|
2117
|
+
if (seg.removedSeq === undefined) {
|
|
2118
|
+
segmentPosition += seg.cachedLength;
|
|
2119
|
+
}
|
|
2120
|
+
|
|
2121
|
+
return true;
|
|
2122
|
+
});
|
|
2123
|
+
|
|
2124
|
+
return segmentPosition;
|
|
2125
|
+
}
|
|
2126
|
+
|
|
2529
2127
|
private nodeUpdateLengthNewStructure(node: IMergeBlock, recur = false) {
|
|
2530
2128
|
this.blockUpdate(node);
|
|
2531
2129
|
if (this.collabWindow.collaborating) {
|
|
2532
|
-
|
|
2130
|
+
this.localPartialsComputed = false;
|
|
2131
|
+
node.partialLengths = PartialSequenceLengths.combine(node, this.collabWindow, recur);
|
|
2533
2132
|
}
|
|
2534
2133
|
}
|
|
2535
2134
|
|
|
@@ -2545,53 +2144,26 @@ export class MergeTree {
|
|
|
2545
2144
|
}
|
|
2546
2145
|
}
|
|
2547
2146
|
public createLocalReferencePosition(
|
|
2548
|
-
segment: ISegment, offset: number
|
|
2549
|
-
client: Client,
|
|
2147
|
+
segment: ISegment, offset: number, refType: ReferenceType, properties: PropertySet | undefined,
|
|
2550
2148
|
): LocalReferencePosition {
|
|
2551
|
-
if (
|
|
2552
|
-
|
|
2553
|
-
|
|
2554
|
-
|
|
2555
|
-
|
|
2149
|
+
if (
|
|
2150
|
+
isRemovedAndAcked(segment)
|
|
2151
|
+
&& !refTypeIncludesFlag(refType, ReferenceType.SlideOnRemove | ReferenceType.Transient)
|
|
2152
|
+
) {
|
|
2153
|
+
throw new UsageError(
|
|
2154
|
+
"Can only create SlideOnRemove or Transient local reference position on a removed segment",
|
|
2155
|
+
);
|
|
2556
2156
|
}
|
|
2557
2157
|
const localRefs = segment.localRefs ?? new LocalReferenceCollection(segment);
|
|
2558
2158
|
segment.localRefs = localRefs;
|
|
2559
2159
|
|
|
2560
|
-
const segRef = localRefs.createLocalRef(offset, refType, properties
|
|
2160
|
+
const segRef = localRefs.createLocalRef(offset, refType, properties);
|
|
2561
2161
|
|
|
2562
2162
|
this.blockUpdatePathLengths(segment.parent, TreeMaintenanceSequenceNumber,
|
|
2563
2163
|
LocalClientId);
|
|
2564
2164
|
return segRef;
|
|
2565
2165
|
}
|
|
2566
2166
|
|
|
2567
|
-
/**
|
|
2568
|
-
* @deprecated - use removeLocalReferencePosition
|
|
2569
|
-
*/
|
|
2570
|
-
public removeLocalReference(segment: ISegment, lref: LocalReference) {
|
|
2571
|
-
if (segment.localRefs) {
|
|
2572
|
-
const removedRef = segment.localRefs.removeLocalRef(lref);
|
|
2573
|
-
if (removedRef) {
|
|
2574
|
-
this.blockUpdatePathLengths(segment.parent, TreeMaintenanceSequenceNumber,
|
|
2575
|
-
LocalClientId);
|
|
2576
|
-
}
|
|
2577
|
-
}
|
|
2578
|
-
}
|
|
2579
|
-
|
|
2580
|
-
/**
|
|
2581
|
-
* @deprecated - use createLocalReference
|
|
2582
|
-
*/
|
|
2583
|
-
public addLocalReference(lref: LocalReference) {
|
|
2584
|
-
const segment = lref.segment!;
|
|
2585
|
-
let localRefs = segment.localRefs;
|
|
2586
|
-
if (!localRefs) {
|
|
2587
|
-
localRefs = new LocalReferenceCollection(segment);
|
|
2588
|
-
segment.localRefs = localRefs;
|
|
2589
|
-
}
|
|
2590
|
-
localRefs.addLocalRef(lref);
|
|
2591
|
-
this.blockUpdatePathLengths(segment.parent, TreeMaintenanceSequenceNumber,
|
|
2592
|
-
LocalClientId);
|
|
2593
|
-
}
|
|
2594
|
-
|
|
2595
2167
|
private blockUpdate(block: IMergeBlock) {
|
|
2596
2168
|
let len = 0;
|
|
2597
2169
|
const hierBlock = block.hierBlock();
|
|
@@ -2604,8 +2176,13 @@ export class MergeTree {
|
|
|
2604
2176
|
const child = block.children[i];
|
|
2605
2177
|
len += nodeTotalLength(this, child) ?? 0;
|
|
2606
2178
|
if (hierBlock) {
|
|
2607
|
-
|
|
2608
|
-
|
|
2179
|
+
addNodeReferences(
|
|
2180
|
+
this,
|
|
2181
|
+
child,
|
|
2182
|
+
hierBlock.rightmostTiles,
|
|
2183
|
+
hierBlock.leftmostTiles,
|
|
2184
|
+
hierBlock.rangeStacks);
|
|
2185
|
+
}
|
|
2609
2186
|
if (this.blockUpdateActions) {
|
|
2610
2187
|
this.blockUpdateActions.child(block, i);
|
|
2611
2188
|
}
|
|
@@ -2632,6 +2209,7 @@ export class MergeTree {
|
|
|
2632
2209
|
|
|
2633
2210
|
private blockUpdateLength(node: IMergeBlock, seq: number, clientId: number) {
|
|
2634
2211
|
this.blockUpdate(node);
|
|
2212
|
+
this.localPartialsComputed = false;
|
|
2635
2213
|
if (
|
|
2636
2214
|
this.collabWindow.collaborating
|
|
2637
2215
|
&& seq !== UnassignedSequenceNumber
|
|
@@ -2642,9 +2220,9 @@ export class MergeTree {
|
|
|
2642
2220
|
&& MergeTree.options.incrementalUpdate
|
|
2643
2221
|
&& clientId !== NonCollabClient
|
|
2644
2222
|
) {
|
|
2645
|
-
node.partialLengths.update(
|
|
2223
|
+
node.partialLengths.update(node, seq, clientId, this.collabWindow);
|
|
2646
2224
|
} else {
|
|
2647
|
-
node.partialLengths = PartialSequenceLengths.combine(
|
|
2225
|
+
node.partialLengths = PartialSequenceLengths.combine(node, this.collabWindow);
|
|
2648
2226
|
}
|
|
2649
2227
|
}
|
|
2650
2228
|
}
|
|
@@ -2679,6 +2257,10 @@ export class MergeTree {
|
|
|
2679
2257
|
this.nodeMap(this.root, actions, 0, refSeq, clientId, accum, start, end);
|
|
2680
2258
|
}
|
|
2681
2259
|
|
|
2260
|
+
/**
|
|
2261
|
+
* @deprecated for internal use only. public export will be removed.
|
|
2262
|
+
* @internal
|
|
2263
|
+
*/
|
|
2682
2264
|
public incrementalBlockMap<TContext>(stateStack: Stack<IncrementalMapState<TContext>>) {
|
|
2683
2265
|
while (!stateStack.empty()) {
|
|
2684
2266
|
// We already check the stack is not empty
|