@fluidframework/matrix 2.0.0-internal.3.0.0 → 2.0.0-internal.3.1.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 (110) hide show
  1. package/.eslintrc.js +20 -21
  2. package/.mocharc.js +2 -2
  3. package/README.md +21 -21
  4. package/api-extractor.json +2 -2
  5. package/bench/bsp-set-optimizations.md +5 -5
  6. package/bench/src/index.ts +38 -20
  7. package/bench/src/read/map.ts +16 -10
  8. package/bench/src/read/nativearray.ts +16 -10
  9. package/bench/src/read/test.ts +17 -19
  10. package/bench/src/read/tiled.ts +240 -181
  11. package/bench/src/util.ts +19 -18
  12. package/bench/tsconfig.json +8 -13
  13. package/dist/bspSet.d.ts.map +1 -1
  14. package/dist/bspSet.js.map +1 -1
  15. package/dist/handlecache.d.ts.map +1 -1
  16. package/dist/handlecache.js +1 -3
  17. package/dist/handlecache.js.map +1 -1
  18. package/dist/handletable.d.ts.map +1 -1
  19. package/dist/handletable.js +7 -3
  20. package/dist/handletable.js.map +1 -1
  21. package/dist/matrix.d.ts +1 -1
  22. package/dist/matrix.d.ts.map +1 -1
  23. package/dist/matrix.js +28 -19
  24. package/dist/matrix.js.map +1 -1
  25. package/dist/ops.d.ts.map +1 -1
  26. package/dist/ops.js.map +1 -1
  27. package/dist/packageVersion.d.ts +1 -1
  28. package/dist/packageVersion.js +1 -1
  29. package/dist/packageVersion.js.map +1 -1
  30. package/dist/permutationvector.d.ts.map +1 -1
  31. package/dist/permutationvector.js +15 -8
  32. package/dist/permutationvector.js.map +1 -1
  33. package/dist/productSet.d.ts.map +1 -1
  34. package/dist/productSet.js +6 -3
  35. package/dist/productSet.js.map +1 -1
  36. package/dist/range.d.ts.map +1 -1
  37. package/dist/range.js.map +1 -1
  38. package/dist/runtime.d.ts.map +1 -1
  39. package/dist/runtime.js.map +1 -1
  40. package/dist/serialization.d.ts.map +1 -1
  41. package/dist/serialization.js.map +1 -1
  42. package/dist/sparsearray2d.d.ts.map +1 -1
  43. package/dist/sparsearray2d.js +12 -12
  44. package/dist/sparsearray2d.js.map +1 -1
  45. package/dist/split.d.ts.map +1 -1
  46. package/dist/split.js +5 -3
  47. package/dist/split.js.map +1 -1
  48. package/dist/types.d.ts.map +1 -1
  49. package/dist/types.js.map +1 -1
  50. package/dist/undoprovider.d.ts.map +1 -1
  51. package/dist/undoprovider.js.map +1 -1
  52. package/lib/bspSet.d.ts.map +1 -1
  53. package/lib/bspSet.js.map +1 -1
  54. package/lib/handlecache.d.ts.map +1 -1
  55. package/lib/handlecache.js +1 -3
  56. package/lib/handlecache.js.map +1 -1
  57. package/lib/handletable.d.ts.map +1 -1
  58. package/lib/handletable.js +7 -3
  59. package/lib/handletable.js.map +1 -1
  60. package/lib/matrix.d.ts +1 -1
  61. package/lib/matrix.d.ts.map +1 -1
  62. package/lib/matrix.js +29 -20
  63. package/lib/matrix.js.map +1 -1
  64. package/lib/ops.d.ts.map +1 -1
  65. package/lib/ops.js.map +1 -1
  66. package/lib/packageVersion.d.ts +1 -1
  67. package/lib/packageVersion.js +1 -1
  68. package/lib/packageVersion.js.map +1 -1
  69. package/lib/permutationvector.d.ts.map +1 -1
  70. package/lib/permutationvector.js +15 -8
  71. package/lib/permutationvector.js.map +1 -1
  72. package/lib/productSet.d.ts.map +1 -1
  73. package/lib/productSet.js +6 -3
  74. package/lib/productSet.js.map +1 -1
  75. package/lib/range.d.ts.map +1 -1
  76. package/lib/range.js.map +1 -1
  77. package/lib/runtime.d.ts.map +1 -1
  78. package/lib/runtime.js.map +1 -1
  79. package/lib/serialization.d.ts.map +1 -1
  80. package/lib/serialization.js.map +1 -1
  81. package/lib/sparsearray2d.d.ts.map +1 -1
  82. package/lib/sparsearray2d.js +12 -12
  83. package/lib/sparsearray2d.js.map +1 -1
  84. package/lib/split.d.ts.map +1 -1
  85. package/lib/split.js +5 -3
  86. package/lib/split.js.map +1 -1
  87. package/lib/types.d.ts.map +1 -1
  88. package/lib/types.js.map +1 -1
  89. package/lib/undoprovider.d.ts.map +1 -1
  90. package/lib/undoprovider.js +1 -1
  91. package/lib/undoprovider.js.map +1 -1
  92. package/package.json +117 -116
  93. package/prettier.config.cjs +1 -1
  94. package/src/bspSet.ts +507 -434
  95. package/src/handlecache.ts +114 -112
  96. package/src/handletable.ts +66 -62
  97. package/src/matrix.ts +781 -710
  98. package/src/ops.ts +11 -11
  99. package/src/packageVersion.ts +1 -1
  100. package/src/permutationvector.ts +425 -368
  101. package/src/productSet.ts +852 -788
  102. package/src/range.ts +8 -8
  103. package/src/runtime.ts +35 -35
  104. package/src/serialization.ts +13 -9
  105. package/src/sparsearray2d.ts +196 -192
  106. package/src/split.ts +111 -90
  107. package/src/types.ts +3 -3
  108. package/src/undoprovider.ts +161 -144
  109. package/tsconfig.esnext.json +6 -6
  110. package/tsconfig.json +8 -12
package/src/split.ts CHANGED
@@ -9,59 +9,75 @@ import { SetOperations, Pair } from "./bspSet";
9
9
  export type Ivl<Index extends number = number> = Pair<Index>;
10
10
 
11
11
  /** A much faster version of `Math.max` specialized to two numeric arguments. */
12
- const fastMax = <Index extends number>(x1: Index, x2: Index): Index => x1 < x2 ? x2 : x1;
12
+ const fastMax = <Index extends number>(x1: Index, x2: Index): Index => (x1 < x2 ? x2 : x1);
13
13
 
14
14
  /** A much faster version of `Math.min` specialized to two numeric arguments. */
15
- const fastMin = <Index extends number>(x1: Index, x2: Index): Index => x1 < x2 ? x1 : x2;
15
+ const fastMin = <Index extends number>(x1: Index, x2: Index): Index => (x1 < x2 ? x1 : x2);
16
16
 
17
17
  export function ivlJoin<Index extends number>(ivl1: Ivl<Index>, ivl2: Ivl<Index>): Ivl<Index> {
18
- const [x1a, x1b] = ivl1;
19
- const [x2a, x2b] = ivl2;
20
- return [fastMin(x1a, x2a), fastMax(x1b, x2b)];
18
+ const [x1a, x1b] = ivl1;
19
+ const [x2a, x2b] = ivl2;
20
+ return [fastMin(x1a, x2a), fastMax(x1b, x2b)];
21
21
  }
22
22
 
23
23
  export function ivlMeets(ivl1: Ivl, ivl2: Ivl): boolean {
24
- const [x1a, x1b] = ivl1;
25
- const [x2a, x2b] = ivl2;
26
- return fastMax(x1a, x2a) < fastMin(x1b, x2b);
24
+ const [x1a, x1b] = ivl1;
25
+ const [x2a, x2b] = ivl2;
26
+ return fastMax(x1a, x2a) < fastMin(x1b, x2b);
27
27
  }
28
28
 
29
29
  export function ivlMeetsOrTouches(ivl1: Ivl, ivl2: Ivl): boolean {
30
- const [x1a, x1b] = ivl1;
31
- const [x2a, x2b] = ivl2;
32
- return fastMax(x1a, x2a) <= fastMin(x1b, x2b);
30
+ const [x1a, x1b] = ivl1;
31
+ const [x2a, x2b] = ivl2;
32
+ return fastMax(x1a, x2a) <= fastMin(x1b, x2b);
33
33
  }
34
34
 
35
35
  /** computes the set difference on intervals. Precondition: they meet */
36
- export function ivlExcept<Index extends number>(ivl1: Ivl<Index>, ivl2: Ivl<Index>): Ivl<Index> | undefined {
37
- const [x1a, x1b] = ivl1;
38
- const [x2a, x2b] = ivl2;
39
- if (x1a < x2a && x2b >= x1b) { return [x1a, x2a]; }
40
- if (x1a >= x2a && x2b < x1b) { return [x2b, x1b]; }
41
- return undefined;
36
+ export function ivlExcept<Index extends number>(
37
+ ivl1: Ivl<Index>,
38
+ ivl2: Ivl<Index>,
39
+ ): Ivl<Index> | undefined {
40
+ const [x1a, x1b] = ivl1;
41
+ const [x2a, x2b] = ivl2;
42
+ if (x1a < x2a && x2b >= x1b) {
43
+ return [x1a, x2a];
44
+ }
45
+ if (x1a >= x2a && x2b < x1b) {
46
+ return [x2b, x1b];
47
+ }
48
+ return undefined;
42
49
  }
43
50
 
44
51
  function ivlMeet<Index extends number>(ivl1: Ivl<Index>, ivl2: Ivl<Index>): Ivl<Index> {
45
- const [x1a, x1b] = ivl1;
46
- const [x2a, x2b] = ivl2;
47
- return [fastMax(x1a, x2a), fastMin(x1b, x2b)];
52
+ const [x1a, x1b] = ivl1;
53
+ const [x2a, x2b] = ivl2;
54
+ return [fastMax(x1a, x2a), fastMin(x1b, x2b)];
48
55
  }
49
56
 
50
- export function ivlCompare<Index extends number>(ivl1: Ivl<Index>, ivl2: Ivl<Index>): -1 | 0 | 1 | undefined {
51
- const [x1a, x1b] = ivl1;
52
- const [x2a, x2b] = ivl2;
53
- if (x1a === x2a && x1b === x2b) { return 0; }
54
- if (x1a >= x2a && x1b <= x2b) { return -1; }
55
- if (x1a <= x2a && x1b >= x2b) { return 1; }
56
- return undefined;
57
+ export function ivlCompare<Index extends number>(
58
+ ivl1: Ivl<Index>,
59
+ ivl2: Ivl<Index>,
60
+ ): -1 | 0 | 1 | undefined {
61
+ const [x1a, x1b] = ivl1;
62
+ const [x2a, x2b] = ivl2;
63
+ if (x1a === x2a && x1b === x2b) {
64
+ return 0;
65
+ }
66
+ if (x1a >= x2a && x1b <= x2b) {
67
+ return -1;
68
+ }
69
+ if (x1a <= x2a && x1b >= x2b) {
70
+ return 1;
71
+ }
72
+ return undefined;
57
73
  }
58
74
 
59
75
  export interface Distribution {
60
- /** The cummulative distribution function. This is used to compute the probabilty mass of a given interval. */
61
- readonly cdf: (x: number) => number;
76
+ /** The cummulative distribution function. This is used to compute the probabilty mass of a given interval. */
77
+ readonly cdf: (x: number) => number;
62
78
 
63
- /** The inverse cummulative distribution function. This is used to estimate a point given a quantile. */
64
- readonly invCdf: (x: number) => number;
79
+ /** The inverse cummulative distribution function. This is used to estimate a point given a quantile. */
80
+ readonly invCdf: (x: number) => number;
65
81
  }
66
82
 
67
83
  /** This is a bounded pareto distribution with a shape parameter `alpha`, a lower bound `L` and an upper bound `H`.
@@ -75,76 +91,81 @@ export interface Distribution {
75
91
  * to cut next.
76
92
  */
77
93
  export function boundedPareto(alpha: number, L: number, H: number): Distribution {
78
- const lAlpha = L ** alpha;
79
- const cdfDenom = 1 - (L / H) ** alpha;
80
- const hAlpha = H ** alpha;
81
- const hlAlpha = hAlpha * lAlpha;
82
- const hAlphaSubLAlpha = hAlpha - lAlpha;
83
- const negAlphaInv = -1 / alpha;
84
-
85
- const cdf =
86
- alpha === 1 ? (x: number) => (1 - lAlpha / x) / cdfDenom : (x: number) => (1 - lAlpha * x ** -alpha) / cdfDenom;
87
- const invCdf =
88
- alpha === 1
89
- ? (y: number) => 1 / ((hAlpha - y * hAlphaSubLAlpha) / hlAlpha)
90
- : (y: number) => ((hAlpha - y * hAlphaSubLAlpha) / hlAlpha) ** negAlphaInv;
91
-
92
- return { cdf, invCdf };
94
+ const lAlpha = L ** alpha;
95
+ const cdfDenom = 1 - (L / H) ** alpha;
96
+ const hAlpha = H ** alpha;
97
+ const hlAlpha = hAlpha * lAlpha;
98
+ const hAlphaSubLAlpha = hAlpha - lAlpha;
99
+ const negAlphaInv = -1 / alpha;
100
+
101
+ const cdf =
102
+ alpha === 1
103
+ ? (x: number) => (1 - lAlpha / x) / cdfDenom
104
+ : (x: number) => (1 - lAlpha * x ** -alpha) / cdfDenom;
105
+ const invCdf =
106
+ alpha === 1
107
+ ? (y: number) => 1 / ((hAlpha - y * hAlphaSubLAlpha) / hlAlpha)
108
+ : (y: number) => ((hAlpha - y * hAlphaSubLAlpha) / hlAlpha) ** negAlphaInv;
109
+
110
+ return { cdf, invCdf };
93
111
  }
94
112
 
95
113
  /** Creates a dimension splitter that operates on integer interval values and is based on a bounded Pareto
96
114
  * distribution. */
97
115
  export function boundedParetoSplitter<Index extends number>(
98
- alpha: number,
99
- L: number,
100
- H: number,
116
+ alpha: number,
117
+ L: number,
118
+ H: number,
101
119
  ): DimensionSplitter<Pair<Index>> {
102
- const distribution = boundedPareto(alpha, L, H);
103
- return {
104
- canSplit: ([keyLb, keyUb]) => keyUb - keyLb > 1,
105
- split([keyLb, keyUb]: Pair<Index>) {
106
- const ubCdf = distribution.cdf(keyUb);
107
- const lbCdf = distribution.cdf(keyLb + 1);
108
- const cuttingPoint = distribution.invCdf((ubCdf + lbCdf) / 2);
109
- // pick a cutting point, but making sure either side has at least one element in it.
110
- const discreteCuttingPoint = Math.min(Math.max(Math.round(cuttingPoint), keyLb + 1), keyUb - 1) as Index;
111
-
112
- const leftProb = distribution.cdf(discreteCuttingPoint) - distribution.cdf(keyLb + 1);
113
- const rightProb = distribution.cdf(keyUb) - distribution.cdf(discreteCuttingPoint);
114
-
115
- const result: Pair<Pair<Pair<Index>, number>> = [
116
- [[keyLb, discreteCuttingPoint], leftProb],
117
- [[discreteCuttingPoint, keyUb], rightProb],
118
- ];
119
- return result;
120
- },
121
- };
120
+ const distribution = boundedPareto(alpha, L, H);
121
+ return {
122
+ canSplit: ([keyLb, keyUb]) => keyUb - keyLb > 1,
123
+ split([keyLb, keyUb]: Pair<Index>) {
124
+ const ubCdf = distribution.cdf(keyUb);
125
+ const lbCdf = distribution.cdf(keyLb + 1);
126
+ const cuttingPoint = distribution.invCdf((ubCdf + lbCdf) / 2);
127
+ // pick a cutting point, but making sure either side has at least one element in it.
128
+ const discreteCuttingPoint = Math.min(
129
+ Math.max(Math.round(cuttingPoint), keyLb + 1),
130
+ keyUb - 1,
131
+ ) as Index;
132
+
133
+ const leftProb = distribution.cdf(discreteCuttingPoint) - distribution.cdf(keyLb + 1);
134
+ const rightProb = distribution.cdf(keyUb) - distribution.cdf(discreteCuttingPoint);
135
+
136
+ const result: Pair<Pair<Pair<Index>, number>> = [
137
+ [[keyLb, discreteCuttingPoint], leftProb],
138
+ [[discreteCuttingPoint, keyUb], rightProb],
139
+ ];
140
+ return result;
141
+ },
142
+ };
122
143
  }
123
144
 
124
145
  export function boundedParetoSetOperations<Index extends number, Id>(
125
- alpha: number,
126
- L: number,
127
- H: number,
128
- top: Pair<Index>,
129
- id: Id,
146
+ alpha: number,
147
+ L: number,
148
+ H: number,
149
+ top: Pair<Index>,
150
+ id: Id,
130
151
  ): SetOperations<Pair<Index>, Id> {
131
- const splitter = boundedParetoSplitter<Index>(alpha, L, H);
132
- return {
133
- id,
134
- split: (key) => splitter.split(key),
135
- canSplit: (key) => splitter.canSplit(key),
136
- meets: ivlMeets,
137
- intersect: ivlMeet,
138
- union: (x, y) => (ivlMeetsOrTouches(x, y) ? ivlJoin(x, y) : undefined),
139
- except: ivlExcept,
140
- compare: ivlCompare,
141
- top,
142
- };
152
+ const splitter = boundedParetoSplitter<Index>(alpha, L, H);
153
+ return {
154
+ id,
155
+ split: (key) => splitter.split(key),
156
+ canSplit: (key) => splitter.canSplit(key),
157
+ meets: ivlMeets,
158
+ intersect: ivlMeet,
159
+ union: (x, y) => (ivlMeetsOrTouches(x, y) ? ivlJoin(x, y) : undefined),
160
+ except: ivlExcept,
161
+ compare: ivlCompare,
162
+ top,
163
+ };
143
164
  }
144
165
 
145
166
  export interface DimensionSplitter<Key> {
146
- /** For a given key, returns if the key can be further sub-divided. */
147
- canSplit(key: Key): boolean;
148
- /** Splits a key and returns the probability mass for either half. */
149
- split(key: Key): Pair<Pair<Key, number>>;
167
+ /** For a given key, returns if the key can be further sub-divided. */
168
+ canSplit(key: Key): boolean;
169
+ /** Splits a key and returns the probability mass for either half. */
170
+ split(key: Key): Pair<Pair<Key, number>>;
150
171
  }
package/src/types.ts CHANGED
@@ -7,10 +7,10 @@
7
7
  // of SharedMatrix undo while we decide on the correct layering for undo.
8
8
 
9
9
  export interface IRevertible {
10
- revert();
11
- discard();
10
+ revert();
11
+ discard();
12
12
  }
13
13
 
14
14
  export interface IUndoConsumer {
15
- pushToCurrentOperation(revertible: IRevertible);
15
+ pushToCurrentOperation(revertible: IRevertible);
16
16
  }
@@ -4,157 +4,174 @@
4
4
  */
5
5
 
6
6
  import { assert } from "@fluidframework/common-utils";
7
- import { TrackingGroup, MergeTreeDeltaOperationType, MergeTreeDeltaType } from "@fluidframework/merge-tree";
7
+ import {
8
+ TrackingGroup,
9
+ MergeTreeDeltaOperationType,
10
+ MergeTreeDeltaType,
11
+ } from "@fluidframework/merge-tree";
8
12
  import { MatrixItem, SharedMatrix } from "./matrix";
9
13
  import { Handle, isHandleValid } from "./handletable";
10
14
  import { PermutationSegment, PermutationVector } from "./permutationvector";
11
15
  import { IUndoConsumer } from "./types";
12
16
 
13
17
  export class VectorUndoProvider {
14
- // 'currentGroup' and 'currentOp' are used while applying an IRevertable.revert() to coalesce
15
- // the recorded into a single IRevertable / tracking group as they move between the undo <->
16
- // redo stacks.
17
- private currentGroup?: TrackingGroup;
18
- private currentOp?: MergeTreeDeltaType;
19
-
20
- constructor(
21
- private readonly manager: IUndoConsumer,
22
- private readonly undoInsert: (segment: PermutationSegment) => void,
23
- private readonly undoRemove: (segment: PermutationSegment) => void,
24
- ) { }
25
-
26
- public record(operation: MergeTreeDeltaOperationType, ranges: { segment: PermutationSegment; }[]) {
27
- if (ranges.length > 0) {
28
- // Link each segment to a new TrackingGroup. A TrackingGroup keeps track of the original
29
- // set of linked segments, including any fragmentatiton that occurs due to future splitting.
30
- //
31
- // A TrackingGroup also prevents removed segments from being unlinked from the tree during
32
- // Zamboni and guarantees segments will not be merged/coalesced with segments outside of the
33
- // current tracking group.
34
- //
35
- // These properties allow us to rely on MergeTree.getPosition() to find the locations/lengths
36
- // of all content contained within the tracking group in the future.
37
-
38
- // If we are in the process of reverting, the `IRevertible.revert()` will provide the tracking
39
- // group so that we can preserve the original segment ranges as a single op/group as we move
40
- // ops between the undo <-> redo stacks.
41
- const trackingGroup = this.currentGroup ?? new TrackingGroup();
42
- for (const range of ranges) {
43
- trackingGroup.link(range.segment);
44
- }
45
-
46
- // For SharedMatrix, each IRevertibles always holds a single row/col operation.
47
- // Therefore, 'currentOp' must either be undefined or equal to the current op.
48
- assert(this.currentOp === undefined || this.currentOp === operation,
49
- 0x02a /* "On vector undo, unexpected 'currentOp' type/state!" */);
50
-
51
- switch (operation) {
52
- case MergeTreeDeltaType.INSERT:
53
- if (this.currentOp !== MergeTreeDeltaType.INSERT) {
54
- this.pushRevertible(trackingGroup, this.undoInsert);
55
- }
56
- break;
57
-
58
- case MergeTreeDeltaType.REMOVE: {
59
- if (this.currentOp !== MergeTreeDeltaType.REMOVE) {
60
- this.pushRevertible(trackingGroup, this.undoRemove);
61
- }
62
- break;
63
- }
64
-
65
- default:
66
- throw new Error("operation type not revertible");
67
- }
68
-
69
- // If we are in the process of reverting, set 'currentOp' to remind ourselves not to push
70
- // another revertible until `IRevertable.revert()` finishes the current op and clears this
71
- // field.
72
- if (this.currentGroup !== undefined) {
73
- this.currentOp = operation;
74
- }
75
- }
76
- }
77
-
78
- private pushRevertible(trackingGroup: TrackingGroup, callback: (segment: PermutationSegment) => void) {
79
- const revertible = {
80
- revert: () => {
81
- assert(this.currentGroup === undefined && this.currentOp === undefined,
82
- 0x02b /* "Must not nest calls to IRevertible.revert()" */);
83
-
84
- this.currentGroup = new TrackingGroup();
85
-
86
- try {
87
- while (trackingGroup.size > 0) {
88
- const segment = trackingGroup.segments[0] as PermutationSegment;
89
-
90
- // Unlink 'segment' from the current tracking group before invoking the callback
91
- // to exclude the current undo/redo segment from those copied to the replacement
92
- // segment (if any). (See 'PermutationSegment.transferToReplacement()')
93
- segment.trackingCollection.unlink(trackingGroup);
94
-
95
- callback(segment);
96
- }
97
- } finally {
98
- this.currentOp = undefined;
99
- this.currentGroup = undefined;
100
- }
101
- },
102
- discard: () => {
103
- while (trackingGroup.size > 0) {
104
- trackingGroup.unlink(trackingGroup.segments[0]);
105
- }
106
- },
107
- };
108
-
109
- this.manager.pushToCurrentOperation(revertible);
110
-
111
- return revertible;
112
- }
18
+ // 'currentGroup' and 'currentOp' are used while applying an IRevertable.revert() to coalesce
19
+ // the recorded into a single IRevertable / tracking group as they move between the undo <->
20
+ // redo stacks.
21
+ private currentGroup?: TrackingGroup;
22
+ private currentOp?: MergeTreeDeltaType;
23
+
24
+ constructor(
25
+ private readonly manager: IUndoConsumer,
26
+ private readonly undoInsert: (segment: PermutationSegment) => void,
27
+ private readonly undoRemove: (segment: PermutationSegment) => void,
28
+ ) {}
29
+
30
+ public record(
31
+ operation: MergeTreeDeltaOperationType,
32
+ ranges: { segment: PermutationSegment }[],
33
+ ) {
34
+ if (ranges.length > 0) {
35
+ // Link each segment to a new TrackingGroup. A TrackingGroup keeps track of the original
36
+ // set of linked segments, including any fragmentatiton that occurs due to future splitting.
37
+ //
38
+ // A TrackingGroup also prevents removed segments from being unlinked from the tree during
39
+ // Zamboni and guarantees segments will not be merged/coalesced with segments outside of the
40
+ // current tracking group.
41
+ //
42
+ // These properties allow us to rely on MergeTree.getPosition() to find the locations/lengths
43
+ // of all content contained within the tracking group in the future.
44
+
45
+ // If we are in the process of reverting, the `IRevertible.revert()` will provide the tracking
46
+ // group so that we can preserve the original segment ranges as a single op/group as we move
47
+ // ops between the undo <-> redo stacks.
48
+ const trackingGroup = this.currentGroup ?? new TrackingGroup();
49
+ for (const range of ranges) {
50
+ trackingGroup.link(range.segment);
51
+ }
52
+
53
+ // For SharedMatrix, each IRevertibles always holds a single row/col operation.
54
+ // Therefore, 'currentOp' must either be undefined or equal to the current op.
55
+ assert(
56
+ this.currentOp === undefined || this.currentOp === operation,
57
+ 0x02a /* "On vector undo, unexpected 'currentOp' type/state!" */,
58
+ );
59
+
60
+ switch (operation) {
61
+ case MergeTreeDeltaType.INSERT:
62
+ if (this.currentOp !== MergeTreeDeltaType.INSERT) {
63
+ this.pushRevertible(trackingGroup, this.undoInsert);
64
+ }
65
+ break;
66
+
67
+ case MergeTreeDeltaType.REMOVE: {
68
+ if (this.currentOp !== MergeTreeDeltaType.REMOVE) {
69
+ this.pushRevertible(trackingGroup, this.undoRemove);
70
+ }
71
+ break;
72
+ }
73
+
74
+ default:
75
+ throw new Error("operation type not revertible");
76
+ }
77
+
78
+ // If we are in the process of reverting, set 'currentOp' to remind ourselves not to push
79
+ // another revertible until `IRevertable.revert()` finishes the current op and clears this
80
+ // field.
81
+ if (this.currentGroup !== undefined) {
82
+ this.currentOp = operation;
83
+ }
84
+ }
85
+ }
86
+
87
+ private pushRevertible(
88
+ trackingGroup: TrackingGroup,
89
+ callback: (segment: PermutationSegment) => void,
90
+ ) {
91
+ const revertible = {
92
+ revert: () => {
93
+ assert(
94
+ this.currentGroup === undefined && this.currentOp === undefined,
95
+ 0x02b /* "Must not nest calls to IRevertible.revert()" */,
96
+ );
97
+
98
+ this.currentGroup = new TrackingGroup();
99
+
100
+ try {
101
+ while (trackingGroup.size > 0) {
102
+ const segment = trackingGroup.segments[0] as PermutationSegment;
103
+
104
+ // Unlink 'segment' from the current tracking group before invoking the callback
105
+ // to exclude the current undo/redo segment from those copied to the replacement
106
+ // segment (if any). (See 'PermutationSegment.transferToReplacement()')
107
+ segment.trackingCollection.unlink(trackingGroup);
108
+
109
+ callback(segment);
110
+ }
111
+ } finally {
112
+ this.currentOp = undefined;
113
+ this.currentGroup = undefined;
114
+ }
115
+ },
116
+ discard: () => {
117
+ while (trackingGroup.size > 0) {
118
+ trackingGroup.unlink(trackingGroup.segments[0]);
119
+ }
120
+ },
121
+ };
122
+
123
+ this.manager.pushToCurrentOperation(revertible);
124
+
125
+ return revertible;
126
+ }
113
127
  }
114
128
 
115
129
  export class MatrixUndoProvider<T> {
116
- constructor(
117
- private readonly consumer: IUndoConsumer,
118
- private readonly matrix: SharedMatrix<T>,
119
- private readonly rows: PermutationVector,
120
- private readonly cols: PermutationVector,
121
- ) {
122
- rows.undo = new VectorUndoProvider(
123
- consumer,
124
- /* undoInsert: */ (segment: PermutationSegment) => {
125
- const start = this.rows.getPosition(segment);
126
- this.matrix.removeRows(start, segment.cachedLength);
127
- },
128
- /* undoRemove: */ (segment: PermutationSegment) => {
129
- this.matrix._undoRemoveRows(segment);
130
- },
131
- );
132
- cols.undo = new VectorUndoProvider(
133
- consumer,
134
- /* undoInsert: */ (segment: PermutationSegment) => {
135
- const start = this.cols.getPosition(segment);
136
- this.matrix.removeCols(start, segment.cachedLength);
137
- },
138
- /* undoRemove: */ (segment: PermutationSegment) => {
139
- this.matrix._undoRemoveCols(segment);
140
- },
141
- );
142
- }
143
-
144
- cellSet(rowHandle: Handle, colHandle: Handle, oldValue: MatrixItem<T>) {
145
- assert(isHandleValid(rowHandle) && isHandleValid(colHandle),
146
- 0x02c /* "On cellSet(), invalid row and/or column handles!" */);
147
-
148
- if (this.consumer !== undefined) {
149
- this.consumer.pushToCurrentOperation({
150
- revert: () => {
151
- this.matrix.setCell(
152
- this.rows.handleToPosition(rowHandle),
153
- this.cols.handleToPosition(colHandle),
154
- oldValue);
155
- },
156
- discard: () => {},
157
- });
158
- }
159
- }
130
+ constructor(
131
+ private readonly consumer: IUndoConsumer,
132
+ private readonly matrix: SharedMatrix<T>,
133
+ private readonly rows: PermutationVector,
134
+ private readonly cols: PermutationVector,
135
+ ) {
136
+ rows.undo = new VectorUndoProvider(
137
+ consumer,
138
+ /* undoInsert: */ (segment: PermutationSegment) => {
139
+ const start = this.rows.getPosition(segment);
140
+ this.matrix.removeRows(start, segment.cachedLength);
141
+ },
142
+ /* undoRemove: */ (segment: PermutationSegment) => {
143
+ this.matrix._undoRemoveRows(segment);
144
+ },
145
+ );
146
+ cols.undo = new VectorUndoProvider(
147
+ consumer,
148
+ /* undoInsert: */ (segment: PermutationSegment) => {
149
+ const start = this.cols.getPosition(segment);
150
+ this.matrix.removeCols(start, segment.cachedLength);
151
+ },
152
+ /* undoRemove: */ (segment: PermutationSegment) => {
153
+ this.matrix._undoRemoveCols(segment);
154
+ },
155
+ );
156
+ }
157
+
158
+ cellSet(rowHandle: Handle, colHandle: Handle, oldValue: MatrixItem<T>) {
159
+ assert(
160
+ isHandleValid(rowHandle) && isHandleValid(colHandle),
161
+ 0x02c /* "On cellSet(), invalid row and/or column handles!" */,
162
+ );
163
+
164
+ if (this.consumer !== undefined) {
165
+ this.consumer.pushToCurrentOperation({
166
+ revert: () => {
167
+ this.matrix.setCell(
168
+ this.rows.handleToPosition(rowHandle),
169
+ this.cols.handleToPosition(colHandle),
170
+ oldValue,
171
+ );
172
+ },
173
+ discard: () => {},
174
+ });
175
+ }
176
+ }
160
177
  }
@@ -1,7 +1,7 @@
1
1
  {
2
- "extends": "./tsconfig.json",
3
- "compilerOptions": {
4
- "outDir": "./lib",
5
- "module": "esnext"
6
- },
7
- }
2
+ "extends": "./tsconfig.json",
3
+ "compilerOptions": {
4
+ "outDir": "./lib",
5
+ "module": "esnext",
6
+ },
7
+ }
package/tsconfig.json CHANGED
@@ -1,14 +1,10 @@
1
1
  {
2
- "extends": "@fluidframework/build-common/ts-common-config.json",
3
- "compilerOptions": {
4
- "rootDir": "./src",
5
- "outDir": "./dist",
6
- "composite": true,
7
- },
8
- "include": [
9
- "src/**/*"
10
- ],
11
- "exclude": [
12
- "src/test/**/*"
13
- ]
2
+ "extends": "@fluidframework/build-common/ts-common-config.json",
3
+ "compilerOptions": {
4
+ "rootDir": "./src",
5
+ "outDir": "./dist",
6
+ "composite": true,
7
+ },
8
+ "include": ["src/**/*"],
9
+ "exclude": ["src/test/**/*"],
14
10
  }