articulated 0.2.0 → 0.4.0

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.
Files changed (31) hide show
  1. package/README.md +18 -1
  2. package/build/commonjs/id_list.d.ts +56 -20
  3. package/build/commonjs/id_list.js +207 -107
  4. package/build/commonjs/id_list.js.map +1 -1
  5. package/build/commonjs/internal/leaf_map.d.ts +25 -0
  6. package/build/commonjs/internal/leaf_map.js +54 -0
  7. package/build/commonjs/internal/leaf_map.js.map +1 -0
  8. package/build/commonjs/internal/seq_map.d.ts +20 -0
  9. package/build/commonjs/internal/seq_map.js +40 -0
  10. package/build/commonjs/internal/seq_map.js.map +1 -0
  11. package/build/commonjs/vendor/functional-red-black-tree.d.ts +8 -0
  12. package/build/commonjs/vendor/functional-red-black-tree.js +911 -0
  13. package/build/commonjs/vendor/functional-red-black-tree.js.map +1 -0
  14. package/build/esm/id_list.d.ts +56 -20
  15. package/build/esm/id_list.js +206 -105
  16. package/build/esm/id_list.js.map +1 -1
  17. package/build/esm/internal/leaf_map.d.ts +25 -0
  18. package/build/esm/internal/leaf_map.js +47 -0
  19. package/build/esm/internal/leaf_map.js.map +1 -0
  20. package/build/esm/internal/seq_map.d.ts +20 -0
  21. package/build/esm/internal/seq_map.js +32 -0
  22. package/build/esm/internal/seq_map.js.map +1 -0
  23. package/build/esm/vendor/functional-red-black-tree.d.ts +8 -0
  24. package/build/esm/vendor/functional-red-black-tree.js +911 -0
  25. package/build/esm/vendor/functional-red-black-tree.js.map +1 -0
  26. package/package.json +10 -5
  27. package/src/id_list.ts +306 -109
  28. package/src/internal/leaf_map.ts +57 -0
  29. package/src/internal/seq_map.ts +48 -0
  30. package/src/vendor/functional-red-black-tree.d.ts +177 -0
  31. package/src/vendor/functional-red-black-tree.js +938 -0
package/README.md CHANGED
@@ -109,7 +109,24 @@ let newList = IdList.load(savedState);
109
109
 
110
110
  IdList stores its state as a modified [B+Tree](https://en.wikipedia.org/wiki/B%2B_tree), described at the top of [its source code](./src/id_list.ts). Each leaf in the B+Tree represents multiple ElementIds (sharing a bunchId and sequential counters) in a compressed way; for normal collaborative text editing, expect 10-20 ElementIds per leaf.
111
111
 
112
- In terms of the number of leaves `L`, mutating an IdList with insertAfter/insertBefore/delete/undelete will only create `O(log(L))` new tree nodes, reusing the rest. However, most methods currently take `O(L)` total time because they search the whole tree for a given ElementId, which has not yet been optimized (it uses a simple depth-first search). Exception: `IdList.at(index)` takes only `O(log(L))` time.
112
+ To speed up searches, we also maintain a "bottom-up" tree that maps from each node to a sequence number identifying its parent. (Using sequence numbers instead of pointers is necessary for persistence.) The map is implemented using persistent balanced trees from [functional-red-black-tree](https://www.npmjs.com/package/functional-red-black-tree).
113
+
114
+ Asymptotic runtimes are given in terms of the number of leaves `L` and the maximum "fragmentation" of a leaf `F`, which is the number of times its ElementIds alternate between deleted vs present.
115
+
116
+ - insertAfter, insertBefore: `O(log^2(L) + F)`.
117
+ - The bottleneck is finding the B+Tree path of the before/after ElementId. This requires `O(log(L))` lookups in the bottom-up tree's map, each of which takes `O(log(L))` time. See the implementation of `IdList.locate`.
118
+ - delete, undelete: `O(log^2(L) + F)`.
119
+ - indexOf: `O(log^2(L) + F)`.
120
+ - Bottleneck is locating the id.
121
+ - at: `O(log(L) + F)`.
122
+ - Simple B+Tree search.
123
+ - has, isKnown: `O(log(L) + F)`
124
+ - Part of the bottom-up tree is a sorted map with leaf keys; due to the sort, we can also use that map to look up the leaf corresponding to an ElementId, in `O(log(L))` time.
125
+ - length: `O(1)`.
126
+ - Cached.
127
+ - save: `O(S + L)`, where `S <= L * F` is the saved state's length.
128
+ - load: `O(S * log(S))`
129
+ - The bottleneck is constructing the bottom-up tree: specifically, the map from each leaf to its parent's sequence number (`leafMap`). That map is itself a sorted tree, hence takes `O(L * log(L))` time to construct, and `L <= S`.
113
130
 
114
131
  If you want to get a sense of what IdList is or how to implement your own version, consider reading the source code for [IdListSimple](./test/id_list_simple.ts), which behaves identically to IdList. It is short (<300 SLOC) and direct, using an array and `Array.splice`. The downside is that IdListSimple does not compress ElementIds, and all of its operations take `O(# ids)` time. We use it as a known-good implementation in our fuzz tests.
115
132
 
@@ -1,5 +1,7 @@
1
1
  import { SparseIndices } from "sparse-array-rled";
2
2
  import { ElementId } from "./id";
3
+ import { MutableLeafMap } from "./internal/leaf_map";
4
+ import { MutableSeqMap } from "./internal/seq_map";
3
5
  import { SavedIdList } from "./saved_id_list";
4
6
  export interface LeafNode {
5
7
  readonly bunchId: string;
@@ -16,19 +18,48 @@ export interface LeafNode {
16
18
  * An inner node with inner-node children.
17
19
  */
18
20
  export declare class InnerNodeInner {
21
+ /**
22
+ * A unique identifer for this node within its IdTree.
23
+ */
24
+ readonly seq: number;
19
25
  readonly children: readonly InnerNode[];
20
26
  readonly size: number;
21
27
  readonly knownSize: number;
22
- constructor(children: readonly InnerNode[]);
28
+ constructor(
29
+ /**
30
+ * A unique identifer for this node within its IdTree.
31
+ */
32
+ seq: number, children: readonly InnerNode[],
33
+ /**
34
+ * We add entries for the children to this map, overwriting any existing parentSeqs.
35
+ *
36
+ * Pass null to skip when you are doing it yourself. Regardless, you need to
37
+ * delete any outdated entries yourself.
38
+ */
39
+ parentSeqsMut: MutableSeqMap | null);
23
40
  }
24
41
  /**
25
42
  * An inner node with leaf children.
26
43
  */
27
44
  export declare class InnerNodeLeaf {
45
+ /**
46
+ * A unique identifer for this node within its IdTree.
47
+ */
48
+ readonly seq: number;
28
49
  readonly children: readonly LeafNode[];
29
50
  readonly size: number;
30
51
  readonly knownSize: number;
31
- constructor(children: readonly LeafNode[]);
52
+ constructor(
53
+ /**
54
+ * A unique identifer for this node within its IdTree.
55
+ */
56
+ seq: number, children: readonly LeafNode[],
57
+ /**
58
+ * We add entries for the children to this map, overwriting any existing parentSeqs.
59
+ *
60
+ * Pass null to skip when you are doing it yourself.
61
+ */
62
+ leafMapMut: MutableLeafMap | null);
32
63
  }
33
64
  export type InnerNode = InnerNodeInner | InnerNodeLeaf;
34
65
  /**
@@ -67,6 +98,18 @@ export declare const M = 8;
67
98
  */
68
99
  export declare class IdList {
69
100
  private readonly root;
101
+ /**
102
+ * A persistent sorted map from each leaf to its parent node's seq.
103
+ *
104
+ * Besides parentSeqs, we also use this to lookup leaves by ElementId.
105
+ */
106
+ private readonly leafMap;
107
+ /**
108
+ * A persistent map from each InnerNode's seq to its parent node's seq.
109
+ *
110
+ * We map the root's seq to 0 (in our constructor).
111
+ */
112
+ private readonly parentSeqs;
70
113
  /**
71
114
  * Internal - construct an IdList using a static method (e.g. `IdList.new`).
72
115
  */
@@ -150,6 +193,13 @@ export declare class IdList {
150
193
  * @throws If `id` is not known.
151
194
  */
152
195
  undelete(id: ElementId): IdList;
196
+ /**
197
+ * Returns the path from id's leaf node to the root, or null if id is not found.
198
+ *
199
+ * The path contains each node and its index in its parent's node, starting with id's
200
+ * LeafNode and ending at a child of the root.
201
+ */
202
+ private locate;
153
203
  /**
154
204
  * Replaces the leaf at the given path with newLeaves.
155
205
  * Returns a proper (sufficiently balanced) B+Tree with updated sizes.
@@ -171,6 +221,10 @@ export declare class IdList {
171
221
  * Compare to {@link has}.
172
222
  */
173
223
  isKnown(id: ElementId): boolean;
224
+ /**
225
+ * Returns true if any of the given bulk ids are known.
226
+ */
227
+ private isAnyKnown;
174
228
  /**
175
229
  * The length of the list, counting only present ids.
176
230
  *
@@ -270,21 +324,3 @@ export declare class KnownIdView {
270
324
  */
271
325
  values(): IterableIterator<ElementId>;
272
326
  }
273
- type Located = [
274
- {
275
- node: LeafNode;
276
- indexInParent: number;
277
- },
278
- ...{
279
- node: InnerNode;
280
- indexInParent: number;
281
- }[]
282
- ];
283
- /**
284
- * Returns the path from id's leaf node to the root, or null if id is not found.
285
- *
286
- * The path contains each node and its index in its parent's node, starting with id's
287
- * LeafNode and ending at a child of the root.
288
- */
289
- export declare function locate(id: ElementId, node: InnerNode): Located | null;
290
- export {};
@@ -1,18 +1,35 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.locate = exports.KnownIdView = exports.IdList = exports.M = exports.InnerNodeLeaf = exports.InnerNodeInner = void 0;
3
+ exports.KnownIdView = exports.IdList = exports.M = exports.InnerNodeLeaf = exports.InnerNodeInner = void 0;
4
4
  const sparse_array_rled_1 = require("sparse-array-rled");
5
+ const leaf_map_1 = require("./internal/leaf_map");
6
+ const seq_map_1 = require("./internal/seq_map");
5
7
  /**
6
8
  * An inner node with inner-node children.
7
9
  */
8
10
  class InnerNodeInner {
9
- constructor(children) {
11
+ constructor(
12
+ /**
13
+ * A unique identifer for this node within its IdTree.
14
+ */
15
+ seq, children,
16
+ /**
17
+ * We add entries for the children to this map, overwriting any existing parentSeqs.
18
+ *
19
+ * Pass null to skip when you are doing it yourself. Regardless, you need to
20
+ * delete any outdated entries yourself.
21
+ */
22
+ parentSeqsMut) {
23
+ this.seq = seq;
10
24
  this.children = children;
11
25
  let size = 0;
12
26
  let knownSize = 0;
13
27
  for (const child of children) {
14
28
  size += child.size;
15
29
  knownSize += child.knownSize;
30
+ if (parentSeqsMut) {
31
+ parentSeqsMut.value = parentSeqsMut.value.set(child.seq, seq);
32
+ }
16
33
  }
17
34
  this.size = size;
18
35
  this.knownSize = knownSize;
@@ -23,13 +40,27 @@ exports.InnerNodeInner = InnerNodeInner;
23
40
  * An inner node with leaf children.
24
41
  */
25
42
  class InnerNodeLeaf {
26
- constructor(children) {
43
+ constructor(
44
+ /**
45
+ * A unique identifer for this node within its IdTree.
46
+ */
47
+ seq, children,
48
+ /**
49
+ * We add entries for the children to this map, overwriting any existing parentSeqs.
50
+ *
51
+ * Pass null to skip when you are doing it yourself.
52
+ */
53
+ leafMapMut) {
54
+ this.seq = seq;
27
55
  this.children = children;
28
56
  let size = 0;
29
57
  let knownSize = 0;
30
58
  for (const child of children) {
31
59
  size += child.present.count();
32
60
  knownSize += child.count;
61
+ if (leafMapMut) {
62
+ leafMapMut.value = leafMapMut.value.set(child, seq);
63
+ }
33
64
  }
34
65
  this.size = size;
35
66
  this.knownSize = knownSize;
@@ -74,8 +105,16 @@ class IdList {
74
105
  /**
75
106
  * Internal - construct an IdList using a static method (e.g. `IdList.new`).
76
107
  */
77
- constructor(root) {
108
+ constructor(root,
109
+ /**
110
+ * A persistent sorted map from each leaf to its parent node's seq.
111
+ *
112
+ * Besides parentSeqs, we also use this to lookup leaves by ElementId.
113
+ */
114
+ leafMap, parentSeqs) {
78
115
  this.root = root;
116
+ this.leafMap = leafMap;
117
+ this.parentSeqs = parentSeqs.set(root.seq, 0);
79
118
  }
80
119
  /**
81
120
  * Constructs an empty list.
@@ -84,7 +123,9 @@ class IdList {
84
123
  * or {@link IdList.load}.
85
124
  */
86
125
  static new() {
87
- return new this(new InnerNodeLeaf([]));
126
+ const leafMapMut = { value: leaf_map_1.LeafMap.new() };
127
+ const parentSeqsMut = { value: seq_map_1.SeqMap.new() };
128
+ return new this(new InnerNodeLeaf((0, seq_map_1.getAndBumpNextSeq)(parentSeqsMut), [], leafMapMut), leafMapMut.value, parentSeqsMut.value);
88
129
  }
89
130
  /**
90
131
  * Constructs a list with the given known ids and their isDeleted status, in list order.
@@ -147,7 +188,7 @@ class IdList {
147
188
  if (!(Number.isSafeInteger(count) && count >= 0)) {
148
189
  throw new Error(`Invalid count: ${count}`);
149
190
  }
150
- if (count !== 0 && isAnyKnown(newId, count, this.root)) {
191
+ if (this.isAnyKnown(newId, count)) {
151
192
  throw new Error("An inserted id is already known");
152
193
  }
153
194
  if (before === null) {
@@ -157,21 +198,21 @@ class IdList {
157
198
  // Insert the first leaf as a child of root.
158
199
  const present = sparse_array_rled_1.SparseIndices.new();
159
200
  present.set(newId.counter, count);
160
- return new IdList(new InnerNodeLeaf([
161
- {
162
- bunchId: newId.bunchId,
163
- startCounter: newId.counter,
164
- count,
165
- present,
166
- },
167
- ]));
201
+ const leaf = {
202
+ bunchId: newId.bunchId,
203
+ startCounter: newId.counter,
204
+ count,
205
+ present,
206
+ };
207
+ const leafMapMut = { value: this.leafMap };
208
+ return new IdList(new InnerNodeLeaf(this.root.seq, [leaf], leafMapMut), leafMapMut.value, this.parentSeqs);
168
209
  }
169
210
  else {
170
211
  // Insert before the first known id.
171
212
  return this.insertBefore(firstId(this.root), newId, count);
172
213
  }
173
214
  }
174
- const located = locate(before, this.root);
215
+ const located = this.locate(before);
175
216
  if (located === null) {
176
217
  throw new Error("before is not known");
177
218
  }
@@ -247,7 +288,7 @@ class IdList {
247
288
  if (!(Number.isSafeInteger(count) && count >= 0)) {
248
289
  throw new Error(`Invalid count: ${count}`);
249
290
  }
250
- if (count !== 0 && isAnyKnown(newId, count, this.root)) {
291
+ if (this.isAnyKnown(newId, count)) {
251
292
  throw new Error("An inserted id is already known");
252
293
  }
253
294
  if (after === null) {
@@ -256,7 +297,7 @@ class IdList {
256
297
  // Insert after the last known id, or at the beginning if empty.
257
298
  return this.insertAfter(this.root.knownSize === 0 ? null : lastId(this.root), newId, count);
258
299
  }
259
- const located = locate(after, this.root);
300
+ const located = this.locate(after);
260
301
  if (located === null) {
261
302
  throw new Error("after is not known");
262
303
  }
@@ -323,7 +364,7 @@ class IdList {
323
364
  * If `id` is already deleted or is not known, this method does nothing.
324
365
  */
325
366
  delete(id) {
326
- const located = locate(id, this.root);
367
+ const located = this.locate(id);
327
368
  if (located === null)
328
369
  return this;
329
370
  const leaf = located[0].node;
@@ -344,7 +385,7 @@ class IdList {
344
385
  * @throws If `id` is not known.
345
386
  */
346
387
  undelete(id) {
347
- const located = locate(id, this.root);
388
+ const located = this.locate(id);
348
389
  if (located === null) {
349
390
  throw new Error("id is not known");
350
391
  }
@@ -355,6 +396,52 @@ class IdList {
355
396
  newPresent.set(id.counter);
356
397
  return this.replaceLeaf(located, { ...leaf, present: newPresent });
357
398
  }
399
+ /**
400
+ * Returns the path from id's leaf node to the root, or null if id is not found.
401
+ *
402
+ * The path contains each node and its index in its parent's node, starting with id's
403
+ * LeafNode and ending at a child of the root.
404
+ */
405
+ locate(id) {
406
+ // Find the leaf containing id, if any.
407
+ const [leaf, parentSeq] = this.leafMap.getLeaf(id.bunchId, id.counter);
408
+ if (leaf === undefined)
409
+ return null;
410
+ if (!(leaf.bunchId === id.bunchId &&
411
+ leaf.startCounter <= id.counter &&
412
+ id.counter < leaf.startCounter + leaf.count)) {
413
+ return null;
414
+ }
415
+ // Find the seqs on the path (leaf, root].
416
+ const innerSeqs = [];
417
+ let curSeq = parentSeq;
418
+ while (curSeq !== 0) {
419
+ innerSeqs.push(curSeq);
420
+ curSeq = this.parentSeqs.get(curSeq);
421
+ }
422
+ // Find the nodes and indexInParent's on the path (root, leaf),
423
+ // using seqs to find the appropriate child of each node.
424
+ const innerNodes = [];
425
+ let curParent = this.root;
426
+ // Start at the root child's seq and proceed to the leaf parent's seq.
427
+ for (let i = innerSeqs.length - 2; i >= 0; i--) {
428
+ const children = curParent.children;
429
+ const childIndex = children.findIndex((child) => child.seq === innerSeqs[i]);
430
+ if (childIndex === -1)
431
+ throw new Error("Internal error");
432
+ const child = children[childIndex];
433
+ innerNodes.push({ node: child, indexInParent: childIndex });
434
+ curParent = child;
435
+ }
436
+ // Now curParent is the leaf's parent. Find leaf in its children and return.
437
+ const leafChildIndex = curParent.children.indexOf(leaf);
438
+ if (leafChildIndex === -1)
439
+ throw new Error("Internal error");
440
+ return [
441
+ { node: leaf, indexInParent: leafChildIndex },
442
+ ...innerNodes.reverse(),
443
+ ];
444
+ }
358
445
  /**
359
446
  * Replaces the leaf at the given path with newLeaves.
360
447
  * Returns a proper (sufficiently balanced) B+Tree with updated sizes.
@@ -362,7 +449,10 @@ class IdList {
362
449
  * newLeaves.length must be in [1, M].
363
450
  */
364
451
  replaceLeaf(located, ...newLeaves) {
365
- return new IdList(replaceNode(located, this.root, newLeaves, 0));
452
+ const leafMapMut = { value: this.leafMap };
453
+ const parentSeqsMut = { value: this.parentSeqs };
454
+ const newRoot = replaceNode(located, this.root, leafMapMut, parentSeqsMut, newLeaves, 0);
455
+ return new IdList(newRoot, leafMapMut.value, parentSeqsMut.value);
366
456
  }
367
457
  // Accessors
368
458
  /**
@@ -373,10 +463,12 @@ class IdList {
373
463
  * Compare to {@link isKnown}.
374
464
  */
375
465
  has(id) {
376
- const located = locate(id, this.root);
377
- if (located === null)
378
- return false;
379
- return located[0].node.present.has(id.counter);
466
+ // Find the LeafNode that would contain id if known.
467
+ const [leaf] = this.leafMap.getLeaf(id.bunchId, id.counter);
468
+ if (leaf && leaf.bunchId === id.bunchId) {
469
+ return leaf.present.has(id.counter);
470
+ }
471
+ return false;
380
472
  }
381
473
  /**
382
474
  * Returns whether id is known to this list.
@@ -384,7 +476,34 @@ class IdList {
384
476
  * Compare to {@link has}.
385
477
  */
386
478
  isKnown(id) {
387
- return locate(id, this.root) !== null;
479
+ // Find the LeafNode that would contain id if known.
480
+ const [leaf] = this.leafMap.getLeaf(id.bunchId, id.counter);
481
+ if (leaf && leaf.bunchId === id.bunchId) {
482
+ return (leaf.startCounter <= id.counter &&
483
+ id.counter < leaf.startCounter + leaf.count);
484
+ }
485
+ return false;
486
+ }
487
+ // TODO: Make public?
488
+ /**
489
+ * Returns true if any of the given bulk ids are known.
490
+ */
491
+ isAnyKnown(id, count) {
492
+ if (count === 0)
493
+ return false;
494
+ // Find the leaf containing the last id, or the previous leaf.
495
+ // If any leaf knows any of the ids, this leaf must know an id too.
496
+ const [leaf] = this.leafMap.getLeaf(id.bunchId, id.counter + count - 1);
497
+ if (leaf && leaf.bunchId === id.bunchId) {
498
+ // Test if there is any overlap between the leaf's counter range [a, b]
499
+ // and the bulk ids' counter range [c, d].
500
+ const a = leaf.startCounter;
501
+ const b = leaf.startCounter + leaf.count - 1;
502
+ const c = id.counter;
503
+ const d = id.counter + count - 1;
504
+ return a <= d && c <= b;
505
+ }
506
+ return false;
388
507
  }
389
508
  /**
390
509
  * The length of the list, counting only present ids.
@@ -449,7 +568,7 @@ class IdList {
449
568
  * @throws If `id` is not known.
450
569
  */
451
570
  indexOf(id, bias = "none") {
452
- const located = locate(id, this.root);
571
+ const located = this.locate(id);
453
572
  if (located === null)
454
573
  throw new Error("id is not known");
455
574
  /**
@@ -530,7 +649,7 @@ class IdList {
530
649
  * Loads a saved state returned by {@link save}.
531
650
  */
532
651
  static load(savedState) {
533
- // 1. Determine the leaves.
652
+ // 1. Determine the leaves in list order.
534
653
  const leaves = [];
535
654
  for (let i = 0; i < savedState.length; i++) {
536
655
  const item = savedState[i];
@@ -568,16 +687,23 @@ class IdList {
568
687
  });
569
688
  }
570
689
  // 2. Create a B+Tree with the given leaves.
571
- // We do a "direct" balanced construction that takes O(n) time, instead of inserting
572
- // leaves one-by-one, which would take O(n log(n)) time.
690
+ // We do a "direct" balanced construction that takes O(L) time, instead of inserting
691
+ // leaves one-by-one, which would take O(L log(L)) time.
692
+ // However, constructing the sorted leafMap brings the overall runtime to O(L log(L)).
573
693
  if (leaves.length === 0)
574
694
  return IdList.new();
695
+ // TODO: Test the aux data structures after loading.
696
+ // E.g. reload and then call checkAll again.
697
+ // Also should do insertions to test splitting of the full tree.
698
+ const leafMapMut = { value: leaf_map_1.LeafMap.new() };
699
+ const parentSeqsMut = { value: seq_map_1.SeqMap.new() };
575
700
  // Depth of the B+Tree (number of non-root nodes on any path from a leaf to the root).
576
- // A fully balanced B+Tree of depth d has between [M^{d-1} + 1, M^d] leaves.
701
+ // A full B+Tree of depth d has between [M^{d-1} + 1, M^d] leaves.
577
702
  const depth = leaves.length === 1
578
703
  ? 1
579
704
  : Math.ceil(Math.log(leaves.length) / Math.log(exports.M));
580
- return new IdList(buildTree(leaves, 0, depth));
705
+ const root = buildTree(leaves, leafMapMut, parentSeqsMut, 0, depth);
706
+ return new IdList(root, leafMapMut.value, parentSeqsMut.value);
581
707
  }
582
708
  }
583
709
  exports.IdList = IdList;
@@ -647,7 +773,8 @@ class KnownIdView {
647
773
  * Returns the index of `id` in this view, or -1 if it is not known.
648
774
  */
649
775
  indexOf(id) {
650
- const located = locate(id, this.root);
776
+ // @ts-expect-error Ignore private
777
+ const located = this.list.locate(id);
651
778
  if (located === null)
652
779
  throw new Error("id is not known");
653
780
  /**
@@ -722,70 +849,15 @@ function lastId(node) {
722
849
  counter: lastLeaf.startCounter + lastLeaf.count - 1,
723
850
  };
724
851
  }
725
- /**
726
- * Returns the path from id's leaf node to the root, or null if id is not found.
727
- *
728
- * The path contains each node and its index in its parent's node, starting with id's
729
- * LeafNode and ending at a child of the root.
730
- */
731
- function locate(id, node) {
732
- if (node instanceof InnerNodeInner) {
733
- for (let i = 0; i < node.children.length; i++) {
734
- const child = node.children[i];
735
- const childLocated = locate(id, child);
736
- if (childLocated !== null) {
737
- childLocated.push({ node: child, indexInParent: i });
738
- return childLocated;
739
- }
740
- }
741
- }
742
- else {
743
- for (let i = 0; i < node.children.length; i++) {
744
- const child = node.children[i];
745
- if (child.bunchId === id.bunchId &&
746
- child.startCounter <= id.counter &&
747
- id.counter < child.startCounter + child.count) {
748
- return [{ node: child, indexInParent: i }];
749
- }
750
- }
751
- }
752
- return null;
753
- }
754
- exports.locate = locate;
755
- /**
756
- * Returns true if any of the given bulk ids are known within node's subtree.
757
- *
758
- * Assumes count > 0.
759
- */
760
- function isAnyKnown(id, count, node) {
761
- if (node instanceof InnerNodeInner) {
762
- for (const child of node.children) {
763
- if (isAnyKnown(id, count, child))
764
- return true;
765
- }
766
- }
767
- else {
768
- for (const child of node.children) {
769
- if (child.bunchId === id.bunchId) {
770
- // Test if there is any overlap between the child's counter range [a, b]
771
- // and the bulk id's counter range [c, d].
772
- const a = child.startCounter;
773
- const b = child.startCounter + child.count - 1;
774
- const c = id.counter;
775
- const d = id.counter + count - 1;
776
- if (a <= d && c <= b)
777
- return true;
778
- }
779
- }
780
- }
781
- return false;
782
- }
783
852
  /**
784
853
  * Replace located[i].node with newNodes.
785
854
  *
786
855
  * newNodes.length must be in [1, M].
856
+ *
857
+ * The returned node's descendants are recorded in leafMapMut and parentSeqsMut,
858
+ * but the node itself is not (since we don't know its parent here).
787
859
  */
788
- function replaceNode(located, root, newNodes, i) {
860
+ function replaceNode(located, root, leafMapMut, parentSeqsMut, newNodes, i) {
789
861
  const parent = i === located.length - 1 ? root : located[i + 1].node;
790
862
  const indexInParent = located[i].indexInParent;
791
863
  // Copy-on-write version of parent.children.splice(indexInParent, 1, ...newNodes)
@@ -794,31 +866,54 @@ function replaceNode(located, root, newNodes, i) {
794
866
  .concat(newNodes, parent.children.slice(indexInParent + 1));
795
867
  if (newChildren.length > exports.M) {
796
868
  // Split the parent to maintain BTree property (# children <= M).
869
+ // Treat the right parent as "new", getting a new seq.
797
870
  const split = Math.ceil(newChildren.length / 2);
871
+ const seqs = [parent.seq, (0, seq_map_1.getAndBumpNextSeq)(parentSeqsMut)];
798
872
  const newParents = [
799
873
  newChildren.slice(0, split),
800
874
  newChildren.slice(split),
801
- ].map((children) => i === 0
802
- ? new InnerNodeLeaf(children)
803
- : new InnerNodeInner(children));
875
+ ].map((children, j) => i === 0
876
+ ? new InnerNodeLeaf(seqs[j], children, leafMapMut)
877
+ : new InnerNodeInner(seqs[j], children, parentSeqsMut));
804
878
  if (i === located.length - 1) {
805
879
  // newParents replace root. We need a new root to hold them.
806
- return new InnerNodeInner(newParents);
880
+ return new InnerNodeInner((0, seq_map_1.getAndBumpNextSeq)(parentSeqsMut), newParents, parentSeqsMut);
807
881
  }
808
882
  else {
809
- return replaceNode(located, root, newParents, i + 1);
883
+ return replaceNode(located, root, leafMapMut, parentSeqsMut, newParents, i + 1);
810
884
  }
811
885
  }
812
886
  else {
813
- const newParent = i === 0
814
- ? new InnerNodeLeaf(newChildren)
815
- : new InnerNodeInner(newChildren);
887
+ // "Replace" parent, reusing its seq.
888
+ // To avoid doing newChildren.length sets every time (which makes replaceLeaf
889
+ // do >=(M/2)*log(L) total sets, even when none were necessary),
890
+ // we bypass the InnerNode constructors' leafMap/parentSeq operations,
891
+ // instead doing them ourselves only on the changed children.
892
+ let newParent;
893
+ if (i === 0) {
894
+ newParent = new InnerNodeLeaf(parent.seq, newChildren, null);
895
+ // Important to delete the replaced leaf's entry, so that it doesn't corrupt by-ElementId searches.
896
+ leafMapMut.value = leafMapMut.value.delete(located[0].node);
897
+ for (const newNode of newNodes) {
898
+ leafMapMut.value = leafMapMut.value.set(newNode, parent.seq);
899
+ }
900
+ }
901
+ else {
902
+ newParent = new InnerNodeInner(parent.seq, newChildren, null);
903
+ for (const newNode of newNodes) {
904
+ if (newNode.seq !== located[i].node.seq) {
905
+ parentSeqsMut.value = parentSeqsMut.value.set(newNode.seq, parent.seq);
906
+ }
907
+ }
908
+ // If the replaced node isn't represented in newNodes (i.e., same seq is not reused),
909
+ // we could delete its entry to save memory, but it is not necessary.
910
+ }
816
911
  if (i === located.length - 1) {
817
912
  // Replaces root.
818
913
  return newParent;
819
914
  }
820
915
  else {
821
- return replaceNode(located, root, [newParent], i + 1);
916
+ return replaceNode(located, root, leafMapMut, parentSeqsMut, [newParent], i + 1);
822
917
  }
823
918
  }
824
919
  }
@@ -962,13 +1057,18 @@ function pushSaveItem(acc, item) {
962
1057
  /**
963
1058
  * Builds a tree with the given leaves. Used by IdList.load.
964
1059
  *
965
- * In contrast to inserting the leaves one-by-one, this function balances the
966
- * tree, with full inner nodes (M children) whenever possible,
967
- * and runs in O(L) time instead of O(L log(L)).
1060
+ * The returned node's descendants are recorded in leafMapMut and parentSeqsMut,
1061
+ * but not the node itself (since we don't know its parent here).
1062
+ *
1063
+ * In contrast to inserting the leaves one-by-one, this function fills nodes
1064
+ * with M children whenever possible,
1065
+ * and the B+Tree parts run in O(L) time instead of O(L log(L)).
1066
+ * However, the overall runtime is O(L log(L)) from constructing the sorted leafMap.
968
1067
  */
969
- function buildTree(leaves, startIndex, depthRemaining) {
1068
+ function buildTree(leaves, leafMapMut, parentSeqsMut, startIndex, depthRemaining) {
1069
+ const parentSeq = (0, seq_map_1.getAndBumpNextSeq)(parentSeqsMut);
970
1070
  if (depthRemaining === 1) {
971
- return new InnerNodeLeaf(leaves.slice(startIndex, startIndex + exports.M));
1071
+ return new InnerNodeLeaf(parentSeq, leaves.slice(startIndex, startIndex + exports.M), leafMapMut);
972
1072
  }
973
1073
  else {
974
1074
  const children = [];
@@ -977,9 +1077,9 @@ function buildTree(leaves, startIndex, depthRemaining) {
977
1077
  const childStartIndex = startIndex + i * childLeafCount;
978
1078
  if (childStartIndex >= leaves.length)
979
1079
  break;
980
- children.push(buildTree(leaves, childStartIndex, depthRemaining - 1));
1080
+ children.push(buildTree(leaves, leafMapMut, parentSeqsMut, childStartIndex, depthRemaining - 1));
981
1081
  }
982
- return new InnerNodeInner(children);
1082
+ return new InnerNodeInner(parentSeq, children, parentSeqsMut);
983
1083
  }
984
1084
  }
985
1085
  //# sourceMappingURL=id_list.js.map