@fluidframework/merge-tree 0.53.0 → 0.54.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/lib/mergeTree.js CHANGED
@@ -3,7 +3,9 @@
3
3
  * Licensed under the MIT License.
4
4
  */
5
5
  /* eslint-disable @typescript-eslint/no-non-null-assertion */
6
- /* eslint-disable @typescript-eslint/consistent-type-assertions, @typescript-eslint/no-shadow, max-len, no-bitwise */
6
+ /* eslint-disable @typescript-eslint/consistent-type-assertions */
7
+ /* eslint-disable @typescript-eslint/no-shadow */
8
+ /* eslint-disable no-bitwise */
7
9
  import { assert, Trace } from "@fluidframework/common-utils";
8
10
  import { Heap, ListMakeHead, Stack, } from "./collections";
9
11
  import { LocalClientId, NonCollabClient, TreeMaintenanceSequenceNumber, UnassignedSequenceNumber, UniversalSequenceNumber, } from "./constants";
@@ -151,6 +153,7 @@ export function ordinalToArray(ord) {
151
153
  // `MaxNodesInBlock`. (i.e., `MaxNodesInBlock` contains 1 extra slot for temporary storage to
152
154
  // facilitate splits.)
153
155
  export const MaxNodesInBlock = 8;
156
+ const traceOrdinals = false;
154
157
  export class MergeBlock extends MergeNode {
155
158
  constructor(childCount) {
156
159
  super();
@@ -177,12 +180,14 @@ export class MergeBlock extends MergeNode {
177
180
  localOrdinal = prevOrdCode + ordinalWidth;
178
181
  }
179
182
  child.ordinal = this.ordinal + String.fromCharCode(localOrdinal);
180
- if (MergeBlock.traceOrdinals) {
183
+ if (traceOrdinals) {
184
+ // eslint-disable-next-line max-len
181
185
  console.log(`so: prnt chld prev ${ordinalToArray(this.ordinal)} ${ordinalToArray(child.ordinal)} ${(index > 0) ? ordinalToArray(this.children[index - 1].ordinal) : "NA"}`);
182
186
  }
183
187
  assert(child.ordinal.length === (this.ordinal.length + 1), 0x041 /* "Unexpected child ordinal length!" */);
184
188
  if (index > 0) {
185
- assert(child.ordinal > this.children[index - 1].ordinal, 0x042 /* "Child ordinal <= previous sibling ordinal!" */);
189
+ assert(child.ordinal > this.children[index - 1].ordinal, 0x042);
190
+ // eslint-disable-next-line max-len
186
191
  // console.log(`${ordinalToArray(this.ordinal)} ${ordinalToArray(child.ordinal)} ${ordinalToArray(this.children[index - 1].ordinal)}`);
187
192
  // console.log(`ord width ${ordinalWidth}`);
188
193
  }
@@ -196,7 +201,6 @@ export class MergeBlock extends MergeNode {
196
201
  this.children[index] = child;
197
202
  }
198
203
  }
199
- MergeBlock.traceOrdinals = false;
200
204
  class HierMergeBlock extends MergeBlock {
201
205
  constructor(childCount) {
202
206
  super(childCount);
@@ -233,7 +237,7 @@ function nodeTotalLength(mergeTree, node) {
233
237
  }
234
238
  export class BaseSegment extends MergeNode {
235
239
  constructor() {
236
- super();
240
+ super(...arguments);
237
241
  this.clientId = LocalClientId;
238
242
  this.seq = UniversalSequenceNumber;
239
243
  this.segmentGroups = new SegmentGroupCollection(this);
@@ -285,7 +289,8 @@ export class BaseSegment extends MergeNode {
285
289
  this.localSeq = undefined;
286
290
  return true;
287
291
  case 1 /* REMOVE */:
288
- const removalInfo = mergeTree.getRemovalInfo(this);
292
+ // eslint-disable-next-line @typescript-eslint/no-this-alias
293
+ const removalInfo = this;
289
294
  assert(!!removalInfo, 0x046 /* "On remove ack, missing removal info!" */);
290
295
  assert(!!removalInfo.removedSeq, 0x047 /* "On remove ack, missing removed sequence number!" */);
291
296
  this.localRemovedSeq = undefined;
@@ -344,10 +349,10 @@ export const reservedTileLabelsKey = "referenceTileLabels";
344
349
  export const reservedRangeLabelsKey = "referenceRangeLabels";
345
350
  export const reservedMarkerIdKey = "markerId";
346
351
  export const reservedMarkerSimpleTypeKey = "markerSimpleType";
347
- export const refGetTileLabels = (refPos) => (refPos.refType & ReferenceType.Tile) &&
348
- refPos.properties ? refPos.properties[reservedTileLabelsKey] : undefined;
349
- export const refGetRangeLabels = (refPos) => (refPos.refType & (ReferenceType.NestBegin | ReferenceType.NestEnd)) &&
350
- refPos.properties ? refPos.properties[reservedRangeLabelsKey] : undefined;
352
+ export const refGetTileLabels = (refPos) => (refPos.refType & ReferenceType.Tile)
353
+ && refPos.properties ? refPos.properties[reservedTileLabelsKey] : undefined;
354
+ export const refGetRangeLabels = (refPos) => (refPos.refType & (ReferenceType.NestBegin | ReferenceType.NestEnd))
355
+ && refPos.properties ? refPos.properties[reservedRangeLabelsKey] : undefined;
351
356
  export function refHasTileLabel(refPos, label) {
352
357
  const tileLabels = refPos.getTileLabels();
353
358
  if (tileLabels) {
@@ -669,11 +674,12 @@ export class MergeTree {
669
674
  this.packTime = 0;
670
675
  this.ordTime = 0;
671
676
  this.maxOrdTime = 0;
677
+ this.blockUpdateActions = MergeTree.initBlockUpdateActions;
672
678
  this.collabWindow = new CollaborationWindow();
673
- // TODO: change this to ES6 map; add remove on segment remove
679
+ // TODO: add remove on segment remove
674
680
  // for now assume only markers have ids and so point directly at the Segment
675
681
  // if we need to have pointers to non-markers, we can change to point at local refs
676
- this.idToSegment = createMap();
682
+ this.idToSegment = new Map();
677
683
  this.splitLeafSegment = (segment, pos) => {
678
684
  if (!(pos > 0 && segment)) {
679
685
  return {};
@@ -687,7 +693,6 @@ export class MergeTree {
687
693
  }
688
694
  return { next };
689
695
  };
690
- this.blockUpdateActions = MergeTree.initBlockUpdateActions;
691
696
  this.root = this.makeBlock(0);
692
697
  }
693
698
  makeBlock(childCount) {
@@ -730,7 +735,7 @@ export class MergeTree {
730
735
  return b;
731
736
  }
732
737
  localNetLength(segment) {
733
- const removalInfo = this.getRemovalInfo(segment);
738
+ const removalInfo = segment;
734
739
  if (removalInfo.removedSeq !== undefined) {
735
740
  return 0;
736
741
  }
@@ -740,13 +745,14 @@ export class MergeTree {
740
745
  }
741
746
  // TODO: remove id when segment removed
742
747
  mapIdToSegment(id, segment) {
743
- this.idToSegment[id] = segment;
748
+ this.idToSegment.set(id, segment);
744
749
  }
745
750
  addNode(block, node) {
746
751
  const index = block.childCount++;
747
752
  block.assignChild(node, index, false);
748
753
  return index;
749
754
  }
755
+ /* eslint-disable max-len */
750
756
  reloadFromSegments(segments) {
751
757
  // This code assumes that a later call to `startCollaboration()` will initialize partial lengths.
752
758
  assert(!this.collabWindow.collaborating, 0x049 /* "Trying to reload from segments while collaborating!" */);
@@ -795,6 +801,7 @@ export class MergeTree {
795
801
  console.log(`reload time ${elapsedMicroseconds(clockStart)}`);
796
802
  }
797
803
  }
804
+ /* eslint-enable max-len */
798
805
  // For now assume min starts at zero
799
806
  startCollaboration(localClientId, minSeq, currentSeq) {
800
807
  this.collabWindow.clientId = localClientId;
@@ -842,10 +849,10 @@ export class MergeTree {
842
849
  }
843
850
  else {
844
851
  if (MergeTree.traceZRemove) {
845
- // eslint-disable-next-line @typescript-eslint/dot-notation
852
+ // eslint-disable-next-line @typescript-eslint/dot-notation, max-len
846
853
  console.log(`${this.getLongClientId(this.collabWindow.clientId)}: Zremove ${segment["text"]}; cli ${this.getLongClientId(segment.clientId)}`);
847
854
  }
848
- // Notify maintenance event observers that the segment is being unlinked from the MergeTree.
855
+ // Notify maintenance event observers that the segment is being unlinked from the MergeTree
849
856
  if (this.mergeTreeMaintenanceCallback) {
850
857
  this.mergeTreeMaintenanceCallback({
851
858
  operation: -3 /* UNLINK */,
@@ -865,7 +872,7 @@ export class MergeTree {
865
872
  && this.localNetLength(segment) > 0;
866
873
  if (canAppend) {
867
874
  if (MergeTree.traceAppend) {
868
- // eslint-disable-next-line @typescript-eslint/dot-notation
875
+ // eslint-disable-next-line @typescript-eslint/dot-notation, max-len
869
876
  console.log(`${this.getLongClientId(this.collabWindow.clientId)}: append ${prevSegment["text"]} + ${segment["text"]}; cli ${this.getLongClientId(prevSegment.clientId)} + cli ${this.getLongClientId(segment.clientId)}`);
870
877
  }
871
878
  prevSegment.append(segment);
@@ -1016,7 +1023,14 @@ export class MergeTree {
1016
1023
  }
1017
1024
  getStats() {
1018
1025
  const nodeGetStats = (block) => {
1019
- const stats = { maxHeight: 0, nodeCount: 0, leafCount: 0, removedLeafCount: 0, liveCount: 0, histo: [] };
1026
+ const stats = {
1027
+ maxHeight: 0,
1028
+ nodeCount: 0,
1029
+ leafCount: 0,
1030
+ removedLeafCount: 0,
1031
+ liveCount: 0,
1032
+ histo: [],
1033
+ };
1020
1034
  for (let k = 0; k < MaxNodesInBlock; k++) {
1021
1035
  stats.histo[k] = 0;
1022
1036
  }
@@ -1127,21 +1141,6 @@ export class MergeTree {
1127
1141
  }
1128
1142
  return totalOffset;
1129
1143
  }
1130
- cloneSegments(refSeq, clientId, start = 0, end) {
1131
- let _end = end;
1132
- const gatherSegment = (segment, pos, refSeq, clientId, start, end, accumSegments) => {
1133
- accumSegments.segments.push(segment.clone());
1134
- return true;
1135
- };
1136
- if (_end === undefined) {
1137
- _end = this.blockLength(this.root, refSeq, clientId);
1138
- }
1139
- const accum = {
1140
- segments: [],
1141
- };
1142
- this.mapRange({ leaf: gatherSegment }, refSeq, clientId, accum, start, _end);
1143
- return accum.segments;
1144
- }
1145
1144
  getContainingSegment(pos, refSeq, clientId) {
1146
1145
  let segment;
1147
1146
  let offset;
@@ -1161,9 +1160,6 @@ export class MergeTree {
1161
1160
  return node.cachedLength;
1162
1161
  }
1163
1162
  }
1164
- getRemovalInfo(segment) {
1165
- return segment;
1166
- }
1167
1163
  nodeLength(node, refSeq, clientId) {
1168
1164
  if ((!this.collabWindow.collaborating) || (this.collabWindow.clientId === clientId)) {
1169
1165
  // Local client sees all segments, even when collaborating
@@ -1181,7 +1177,7 @@ export class MergeTree {
1181
1177
  }
1182
1178
  else {
1183
1179
  const segment = node;
1184
- const removalInfo = this.getRemovalInfo(segment);
1180
+ const removalInfo = segment;
1185
1181
  if (removalInfo.removedSeq !== undefined
1186
1182
  && removalInfo.removedSeq !== UnassignedSequenceNumber
1187
1183
  && removalInfo.removedSeq <= refSeq) {
@@ -1194,8 +1190,9 @@ export class MergeTree {
1194
1190
  ((segment.seq !== UnassignedSequenceNumber) && (segment.seq <= refSeq)))) {
1195
1191
  // Segment happened by reference sequence number or segment from requesting client
1196
1192
  if (removalInfo.removedSeq !== undefined) {
1197
- if ((removalInfo.removedClientId === clientId) ||
1198
- (removalInfo.removedClientOverlap && (removalInfo.removedClientOverlap.includes(clientId)))) {
1193
+ if (removalInfo.removedClientId === clientId
1194
+ || (removalInfo.removedClientOverlap
1195
+ && removalInfo.removedClientOverlap.includes(clientId))) {
1199
1196
  return 0;
1200
1197
  }
1201
1198
  else {
@@ -1236,7 +1233,7 @@ export class MergeTree {
1236
1233
  }
1237
1234
  }
1238
1235
  setMinSeq(minSeq) {
1239
- assert(minSeq <= this.collabWindow.currentSeq, 0x04e /* "Trying to set minSeq above currentSeq of collab window!" */);
1236
+ assert(minSeq <= this.collabWindow.currentSeq, 0x04e);
1240
1237
  // Only move forward
1241
1238
  assert(this.collabWindow.minSeq <= minSeq, 0x04f /* "minSeq of collab window > target minSeq!" */);
1242
1239
  if (minSeq > this.collabWindow.minSeq) {
@@ -1305,7 +1302,8 @@ export class MergeTree {
1305
1302
  for (let childIndex = 0; childIndex < block.childCount; childIndex++) {
1306
1303
  const child = children[childIndex];
1307
1304
  const len = (_a = this.nodeLength(child, refSeq, clientId)) !== null && _a !== void 0 ? _a : 0;
1308
- if (((!contains) && (_pos < len)) || (contains && contains(child, _pos, refSeq, clientId, undefined, undefined, clientData))) {
1305
+ if ((!contains && _pos < len)
1306
+ || (contains && contains(child, _pos, refSeq, clientId, undefined, undefined, clientData))) {
1309
1307
  // Found entry containing pos
1310
1308
  if (!child.isLeaf()) {
1311
1309
  return this.searchBlock(child, _pos, _segpos, refSeq, clientId, actions, clientData);
@@ -1436,7 +1434,7 @@ export class MergeTree {
1436
1434
  }
1437
1435
  // TODO: error checking
1438
1436
  getMarkerFromId(id) {
1439
- return this.idToSegment[id];
1437
+ return this.idToSegment.get(id);
1440
1438
  }
1441
1439
  /**
1442
1440
  * Given a position specified relative to a marker id, lookup the marker
@@ -1628,6 +1626,7 @@ export class MergeTree {
1628
1626
  const checkSegmentIsLocal = (segment, pos, refSeq, clientId) => {
1629
1627
  if (segment.seq === UnassignedSequenceNumber) {
1630
1628
  if (MergeTree.diagInsertTie) {
1629
+ // eslint-disable-next-line max-len
1631
1630
  console.log(`@cli ${glc(this, this.collabWindow.clientId)}: promoting continue due to seq ${segment.seq} text ${segment.toString()} ref ${refSeq}`);
1632
1631
  }
1633
1632
  segIsLocal = true;
@@ -1639,6 +1638,7 @@ export class MergeTree {
1639
1638
  segIsLocal = false;
1640
1639
  this.rightExcursion(node, checkSegmentIsLocal);
1641
1640
  if (MergeTree.diagInsertTie && segIsLocal) {
1641
+ // eslint-disable-next-line max-len
1642
1642
  console.log(`@cli ${glc(this, this.collabWindow.clientId)}: attempting continue with seq ${seq} ref ${refSeq} `);
1643
1643
  }
1644
1644
  return segIsLocal;
@@ -1837,6 +1837,7 @@ export class MergeTree {
1837
1837
  }
1838
1838
  else if (splitNode === MergeTree.theUnfinishedNode) {
1839
1839
  if (MergeTree.traceTraversal) {
1840
+ // eslint-disable-next-line max-len
1840
1841
  console.log(`@cli ${glc(this, this.collabWindow.clientId)} unfinished bus pos ${_pos} len ${len}`);
1841
1842
  }
1842
1843
  _pos -= len; // Act as if shifted segment
@@ -1881,6 +1882,7 @@ export class MergeTree {
1881
1882
  }
1882
1883
  if (MergeTree.traceTraversal) {
1883
1884
  if ((!found) && (_pos > 0)) {
1885
+ // eslint-disable-next-line max-len
1884
1886
  console.log(`inserting walk fell through pos ${_pos} len: ${this.blockLength(this.root, refSeq, clientId)}`);
1885
1887
  }
1886
1888
  }
@@ -1961,6 +1963,7 @@ export class MergeTree {
1961
1963
  if (i > 0) {
1962
1964
  if (child.ordinal <= block.children[i - 1].ordinal) {
1963
1965
  console.log("node sib integrity issue");
1966
+ // eslint-disable-next-line max-len
1964
1967
  console.log(`??: prnt chld prev ${ordinalToArray(block.ordinal)} ${ordinalToArray(child.ordinal)} ${(i > 0) ? ordinalToArray(block.children[i - 1].ordinal) : "NA"}`);
1965
1968
  }
1966
1969
  }
@@ -2061,9 +2064,10 @@ export class MergeTree {
2061
2064
  const savedLocalRefs = [];
2062
2065
  const localSeq = seq === UnassignedSequenceNumber ? ++this.collabWindow.localSeq : undefined;
2063
2066
  const markRemoved = (segment, pos, start, end) => {
2064
- const removalInfo = this.getRemovalInfo(segment);
2067
+ const removalInfo = segment;
2065
2068
  if (removalInfo.removedSeq !== undefined) {
2066
2069
  if (MergeTree.diagOverlappingRemove) {
2070
+ // eslint-disable-next-line max-len
2067
2071
  console.log(`yump @seq ${seq} cli ${glc(this, this.collabWindow.clientId)}: overlaps deleted segment ${removalInfo.removedSeq} text '${segment.toString()}'`);
2068
2072
  }
2069
2073
  _overwrite = true;
@@ -2091,8 +2095,8 @@ export class MergeTree {
2091
2095
  // Save segment so can assign removed sequence number when acked by server
2092
2096
  if (this.collabWindow.collaborating) {
2093
2097
  // Use removal information
2094
- const removalInfo = this.getRemovalInfo(segment);
2095
- if ((removalInfo.removedSeq === UnassignedSequenceNumber) && (clientId === this.collabWindow.clientId)) {
2098
+ const removalInfo = segment;
2099
+ if (removalInfo.removedSeq === UnassignedSequenceNumber && clientId === this.collabWindow.clientId) {
2096
2100
  segmentGroup = this.addToPendingList(segment, segmentGroup, localSeq);
2097
2101
  }
2098
2102
  else {
@@ -2220,8 +2224,12 @@ export class MergeTree {
2220
2224
  }
2221
2225
  blockUpdateLength(node, seq, clientId) {
2222
2226
  this.blockUpdate(node);
2223
- if (this.collabWindow.collaborating && (seq !== UnassignedSequenceNumber) && (seq !== TreeMaintenanceSequenceNumber)) {
2224
- if (node.partialLengths !== undefined && MergeTree.options.incrementalUpdate && clientId !== NonCollabClient) {
2227
+ if (this.collabWindow.collaborating
2228
+ && seq !== UnassignedSequenceNumber
2229
+ && seq !== TreeMaintenanceSequenceNumber) {
2230
+ if (node.partialLengths !== undefined
2231
+ && MergeTree.options.incrementalUpdate
2232
+ && clientId !== NonCollabClient) {
2225
2233
  node.partialLengths.update(this, node, seq, clientId, this.collabWindow);
2226
2234
  }
2227
2235
  else {
@@ -2247,6 +2255,7 @@ export class MergeTree {
2247
2255
  nodeToString(block, strbuf, indentCount = 0) {
2248
2256
  let _strbuf = strbuf;
2249
2257
  _strbuf += internedSpaces(indentCount);
2258
+ // eslint-disable-next-line max-len
2250
2259
  _strbuf += `Node (len ${block.cachedLength}) p len (${block.parent ? block.parent.cachedLength : 0}) ord ${ordinalToArray(block.ordinal)} with ${block.childCount} segs:\n`;
2251
2260
  if (MergeTree.blockUpdateMarkers) {
2252
2261
  _strbuf += internedSpaces(indentCount);
@@ -2265,8 +2274,9 @@ export class MergeTree {
2265
2274
  else {
2266
2275
  const segment = child;
2267
2276
  _strbuf += internedSpaces(indentCount + 4);
2277
+ // eslint-disable-next-line max-len
2268
2278
  _strbuf += `cli: ${glc(this, segment.clientId)} seq: ${segment.seq} ord: ${ordinalToArray(segment.ordinal)}`;
2269
- const removalInfo = this.getRemovalInfo(segment);
2279
+ const removalInfo = segment;
2270
2280
  if (removalInfo.removedSeq !== undefined) {
2271
2281
  _strbuf += ` rcli: ${glc(this, removalInfo.removedClientId)} rseq: ${removalInfo.removedSeq}`;
2272
2282
  }
@@ -2305,7 +2315,7 @@ export class MergeTree {
2305
2315
  const len = (_a = this.nodeLength(child, state.refSeq, state.clientId)) !== null && _a !== void 0 ? _a : 0;
2306
2316
  if (MergeTree.traceIncrTraversal) {
2307
2317
  if (child.isLeaf()) {
2308
- // eslint-disable-next-line @typescript-eslint/dot-notation
2318
+ // eslint-disable-next-line @typescript-eslint/dot-notation, max-len
2309
2319
  console.log(`considering (r ${state.refSeq} c ${glc(this, state.clientId)}) seg with text ${child["text"]} len ${len} seq ${child.seq} rseq ${child.removedSeq} cli ${glc(this, child.clientId)}`);
2310
2320
  }
2311
2321
  }
@@ -2372,6 +2382,7 @@ export class MergeTree {
2372
2382
  segInfo += ` rcli: ${glc(this, segment.removedClientId)} rseq: ${segment.removedSeq}`;
2373
2383
  }
2374
2384
  }
2385
+ // eslint-disable-next-line max-len
2375
2386
  console.log(`@tcli ${glc(this, this.collabWindow.clientId)}: map len: ${len} start: ${_start} end: ${_end} ${segInfo}`);
2376
2387
  }
2377
2388
  if (go && (_end > 0) && (len > 0) && (_start < len)) {
@@ -2442,15 +2453,6 @@ export class MergeTree {
2442
2453
  return go;
2443
2454
  }
2444
2455
  }
2445
- // Maximum length of text segment to be considered to be merged with other segment.
2446
- // Maximum segment length is at least 2x of it (not taking into account initial segment creation).
2447
- // The bigger it is, the more expensive it is to break segment into sub-segments (on edits)
2448
- // The smaller it is, the more segments we have in snapshots (and in memory) - it's more expensive to load snapshots.
2449
- // Small number also makes ReplayTool produce false positives ("same" snapshots have slightly different binary representations).
2450
- // More measurements needs to be done, but it's very likely the right spot is somewhere between 1K-2K mark.
2451
- // That said, we also break segments on newline and there are very few segments that are longer than 256 because of it.
2452
- // must be an even number
2453
- MergeTree.TextSegmentGranularity = 256;
2454
2456
  MergeTree.zamboniSegmentsMaxCount = 2;
2455
2457
  MergeTree.options = {
2456
2458
  incrementalUpdate: true,
@@ -2464,7 +2466,6 @@ MergeTree.traceZRemove = false;
2464
2466
  MergeTree.traceOrdinals = false;
2465
2467
  MergeTree.traceGatherText = false;
2466
2468
  MergeTree.diagInsertTie = false;
2467
- MergeTree.skipLeftShift = true;
2468
2469
  MergeTree.diagOverlappingRemove = false;
2469
2470
  MergeTree.traceTraversal = false;
2470
2471
  MergeTree.traceIncrTraversal = false;