@fluidframework/matrix 2.0.0-internal.7.3.0 → 2.0.0-internal.8.0.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 (83) hide show
  1. package/CHANGELOG.md +32 -0
  2. package/README.md +39 -0
  3. package/api-extractor-lint.json +13 -0
  4. package/api-extractor.json +8 -3
  5. package/api-report/matrix.api.md +22 -12
  6. package/dist/index.cjs.map +1 -1
  7. package/dist/index.d.ts +1 -1
  8. package/dist/index.d.ts.map +1 -1
  9. package/dist/matrix-alpha.d.ts +74 -14
  10. package/dist/matrix-beta.d.ts +41 -134
  11. package/dist/matrix-public.d.ts +41 -134
  12. package/dist/matrix-untrimmed.d.ts +74 -14
  13. package/dist/matrix.cjs +178 -48
  14. package/dist/matrix.cjs.map +1 -1
  15. package/dist/matrix.d.ts +67 -9
  16. package/dist/matrix.d.ts.map +1 -1
  17. package/dist/ops.cjs +1 -0
  18. package/dist/ops.cjs.map +1 -1
  19. package/dist/ops.d.ts +6 -2
  20. package/dist/ops.d.ts.map +1 -1
  21. package/dist/packageVersion.cjs +1 -1
  22. package/dist/packageVersion.cjs.map +1 -1
  23. package/dist/packageVersion.d.ts +1 -1
  24. package/dist/permutationvector.cjs +1 -1
  25. package/dist/permutationvector.cjs.map +1 -1
  26. package/dist/permutationvector.d.ts.map +1 -1
  27. package/dist/runtime.cjs +1 -1
  28. package/dist/runtime.cjs.map +1 -1
  29. package/dist/runtime.d.ts +1 -1
  30. package/dist/serialization.cjs.map +1 -1
  31. package/dist/serialization.d.ts +1 -1
  32. package/dist/serialization.d.ts.map +1 -1
  33. package/dist/types.cjs.map +1 -1
  34. package/dist/types.d.ts +5 -5
  35. package/dist/types.d.ts.map +1 -1
  36. package/lib/handlecache.d.ts +2 -2
  37. package/lib/handlecache.d.ts.map +1 -1
  38. package/lib/index.d.ts +3 -3
  39. package/lib/index.d.ts.map +1 -1
  40. package/lib/index.mjs.map +1 -1
  41. package/lib/matrix-alpha.d.ts +74 -14
  42. package/lib/matrix-beta.d.ts +41 -134
  43. package/lib/matrix-public.d.ts +41 -134
  44. package/lib/matrix-untrimmed.d.ts +74 -14
  45. package/lib/matrix.d.ts +69 -11
  46. package/lib/matrix.d.ts.map +1 -1
  47. package/lib/matrix.mjs +178 -47
  48. package/lib/matrix.mjs.map +1 -1
  49. package/lib/ops.d.ts +6 -2
  50. package/lib/ops.d.ts.map +1 -1
  51. package/lib/ops.mjs +1 -0
  52. package/lib/ops.mjs.map +1 -1
  53. package/lib/packageVersion.d.ts +1 -1
  54. package/lib/packageVersion.mjs +1 -1
  55. package/lib/packageVersion.mjs.map +1 -1
  56. package/lib/permutationvector.d.ts +3 -3
  57. package/lib/permutationvector.d.ts.map +1 -1
  58. package/lib/permutationvector.mjs +4 -1
  59. package/lib/permutationvector.mjs.map +1 -1
  60. package/lib/runtime.d.ts +1 -1
  61. package/lib/runtime.d.ts.map +1 -1
  62. package/lib/runtime.mjs +1 -1
  63. package/lib/runtime.mjs.map +1 -1
  64. package/lib/serialization.d.ts +1 -1
  65. package/lib/serialization.d.ts.map +1 -1
  66. package/lib/serialization.mjs.map +1 -1
  67. package/lib/sparsearray2d.d.ts.map +1 -1
  68. package/lib/types.d.ts +5 -5
  69. package/lib/types.d.ts.map +1 -1
  70. package/lib/types.mjs.map +1 -1
  71. package/lib/undoprovider.d.ts +4 -4
  72. package/lib/undoprovider.d.ts.map +1 -1
  73. package/matrix.test-files.tar +0 -0
  74. package/package.json +26 -31
  75. package/src/index.ts +1 -1
  76. package/src/matrix.ts +284 -62
  77. package/src/ops.ts +6 -1
  78. package/src/packageVersion.ts +1 -1
  79. package/src/permutationvector.ts +2 -2
  80. package/src/runtime.ts +1 -1
  81. package/src/serialization.ts +2 -2
  82. package/src/types.ts +5 -5
  83. package/tsconfig.json +1 -0
package/lib/matrix.d.ts CHANGED
@@ -2,18 +2,45 @@
2
2
  * Copyright (c) Microsoft Corporation and contributors. All rights reserved.
3
3
  * Licensed under the MIT License.
4
4
  */
5
+ import { IEventThisPlaceHolder } from "@fluidframework/core-interfaces";
5
6
  import { ISequencedDocumentMessage } from "@fluidframework/protocol-definitions";
6
7
  import { IFluidDataStoreRuntime, IChannelStorageService, Serializable, IChannelAttributes } from "@fluidframework/datastore-definitions";
7
- import { IFluidSerializer, SharedObject, SummarySerializer } from "@fluidframework/shared-object-base";
8
+ import { IFluidSerializer, ISharedObjectEvents, SharedObject, SummarySerializer } from "@fluidframework/shared-object-base";
8
9
  import { ISummaryTreeWithStats } from "@fluidframework/runtime-definitions";
9
10
  import { IMatrixProducer, IMatrixConsumer, IMatrixReader, IMatrixWriter } from "@tiny-calc/nano";
10
11
  import { IJSONSegment } from "@fluidframework/merge-tree";
11
- import { SharedMatrixFactory } from "./runtime";
12
- import { IUndoConsumer } from "./types";
12
+ import { SharedMatrixFactory } from "./runtime.mjs";
13
+ import { IUndoConsumer } from "./types.mjs";
14
+ /**
15
+ * Events emitted by Shared Matrix.
16
+ * @alpha
17
+ */
18
+ export interface ISharedMatrixEvents<T> extends ISharedObjectEvents {
19
+ /**
20
+ * This event is only emitted when the SetCell Resolution Policy is First Write Win(FWW).
21
+ * This is emitted when two clients race and send changes without observing each other changes,
22
+ * the changes that gets sequenced last would be rejected, and only client who's changes rejected
23
+ * would be notified via this event, with expectation that it will merge its changes back by
24
+ * accounting new information (state from winner of the race).
25
+ *
26
+ * @remarks Listener parameters:
27
+ *
28
+ * - `row` - Row number at which conflict happened.
29
+ *
30
+ * - `col` - Col number at which conflict happened.
31
+ *
32
+ * - `currentValue` - The current value of the cell.
33
+ *
34
+ * - `conflictingValue` - The value that this client tried to set in the cell and got ignored due to conflict.
35
+ *
36
+ * - `target` - The {@link SharedMatrix} itself.
37
+ */
38
+ (event: "conflict", listener: (row: number, col: number, currentValue: MatrixItem<T>, conflictingValue: MatrixItem<T>, target: IEventThisPlaceHolder) => void): void;
39
+ }
13
40
  /**
14
41
  * A matrix cell value may be undefined (indicating an empty cell) or any serializable type,
15
42
  * excluding null. (However, nulls may be embedded inside objects and arrays.)
16
- * @public
43
+ * @alpha
17
44
  */
18
45
  export type MatrixItem<T> = Serializable<Exclude<T, null>> | undefined;
19
46
  /**
@@ -27,18 +54,29 @@ export type MatrixItem<T> = Serializable<Exclude<T, null>> | undefined;
27
54
  * matrix data and physically stores data in Z-order to leverage CPU caches and
28
55
  * prefetching when reading in either row or column major order. (See README.md
29
56
  * for more details.)
30
- *
31
- * @public
57
+ * @alpha
32
58
  */
33
- export declare class SharedMatrix<T = any> extends SharedObject implements IMatrixProducer<MatrixItem<T>>, IMatrixReader<MatrixItem<T>>, IMatrixWriter<MatrixItem<T>> {
59
+ export declare class SharedMatrix<T = any> extends SharedObject<ISharedMatrixEvents<T>> implements IMatrixProducer<MatrixItem<T>>, IMatrixReader<MatrixItem<T>>, IMatrixWriter<MatrixItem<T>> {
34
60
  id: string;
35
61
  private readonly consumers;
36
62
  static getFactory(): SharedMatrixFactory;
37
63
  private readonly rows;
38
64
  private readonly cols;
39
65
  private cells;
40
- private pending;
41
- constructor(runtime: IFluidDataStoreRuntime, id: string, attributes: IChannelAttributes);
66
+ private readonly pending;
67
+ private cellLastWriteTracker;
68
+ private setCellLwwToFwwPolicySwitchOpSeqNumber;
69
+ private userSwitchedSetCellPolicy;
70
+ private reentrantCount;
71
+ /**
72
+ * Constructor for the Shared Matrix
73
+ * @param runtime - DataStore runtime.
74
+ * @param id - id of the dds
75
+ * @param attributes - channel attributes
76
+ * @param _isSetCellConflictResolutionPolicyFWW - Conflict resolution for Matrix set op is First Writer Win in case of
77
+ * race condition. Client can still overwrite values in case of no race.
78
+ */
79
+ constructor(runtime: IFluidDataStoreRuntime, id: string, attributes: IChannelAttributes, _isSetCellConflictResolutionPolicyFWW?: boolean);
42
80
  private undo?;
43
81
  /**
44
82
  * Subscribes the given IUndoConsumer to the matrix.
@@ -54,12 +92,22 @@ export declare class SharedMatrix<T = any> extends SharedObject implements IMatr
54
92
  closeMatrix(consumer: IMatrixConsumer<MatrixItem<T>>): void;
55
93
  get rowCount(): number;
56
94
  get colCount(): number;
95
+ isSetCellConflictResolutionPolicyFWW(): boolean;
57
96
  getCell(row: number, col: number): MatrixItem<T>;
58
97
  get matrixProducer(): IMatrixProducer<MatrixItem<T>>;
59
98
  setCell(row: number, col: number, value: MatrixItem<T>): void;
60
99
  setCells(rowStart: number, colStart: number, colCount: number, values: readonly MatrixItem<T>[]): void;
61
100
  private setCellCore;
62
101
  private sendSetCellOp;
102
+ /**
103
+ * This makes sure that the code inside the callback is not reentrant. We need to do that because we raise notifications
104
+ * to the consumers telling about these changes and they can try to change the matrix while listening to those notifications
105
+ * which can make the shared matrix to be in bad state. For example, we are raising notification for a setCell changes and
106
+ * a consumer tries to delete that row/col on receiving that notification which can lead to this matrix trying to setCell in
107
+ * a deleted row/col.
108
+ * @param callback - code that needs to protected against reentrancy.
109
+ */
110
+ private protectAgainstReentrancy;
63
111
  private submitVectorMessage;
64
112
  private submitColMessage;
65
113
  insertCols(colStart: number, count: number): void;
@@ -67,8 +115,8 @@ export declare class SharedMatrix<T = any> extends SharedObject implements IMatr
67
115
  private submitRowMessage;
68
116
  insertRows(rowStart: number, count: number): void;
69
117
  removeRows(rowStart: number, count: number): void;
70
- /** @internal */ _undoRemoveRows(rowStart: number, spec: IJSONSegment): void;
71
- /** @internal */ _undoRemoveCols(colStart: number, spec: IJSONSegment): void;
118
+ /***/ _undoRemoveRows(rowStart: number, spec: IJSONSegment): void;
119
+ /***/ _undoRemoveCols(colStart: number, spec: IJSONSegment): void;
72
120
  protected summarizeCore(serializer: IFluidSerializer): ISummaryTreeWithStats;
73
121
  /**
74
122
  * Runs serializer on the GC data for this SharedMatrix.
@@ -92,11 +140,21 @@ export declare class SharedMatrix<T = any> extends SharedObject implements IMatr
92
140
  * {@inheritDoc @fluidframework/shared-object-base#SharedObject.loadCore}
93
141
  */
94
142
  protected loadCore(storage: IChannelStorageService): Promise<void>;
143
+ /**
144
+ * Tells whether the setCell op should be applied or not based on First Write Win policy. It assumes
145
+ * we are in FWW mode.
146
+ */
147
+ private shouldSetCellBasedOnFWW;
95
148
  protected processCore(rawMessage: ISequencedDocumentMessage, local: boolean, localOpMetadata: unknown): void;
96
149
  private readonly onRowDelta;
97
150
  private readonly onColDelta;
98
151
  private readonly onRowHandlesRecycled;
99
152
  private readonly onColHandlesRecycled;
153
+ /**
154
+ * Api to switch Set Op policy from Last Writer Win to First Writer Win. It only switches from LWW to FWW
155
+ * and not from FWW to LWW. The next SetOp which is sent will communicate this policy to other clients.
156
+ */
157
+ switchSetCellPolicy(): void;
100
158
  /**
101
159
  * Returns true if the latest pending write to the cell indicated by the given row/col handles
102
160
  * matches the given 'localSeq'.
@@ -1 +1 @@
1
- {"version":3,"file":"matrix.d.ts","sourceRoot":"","sources":["../src/matrix.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"matrix.d.ts","sourceRoot":"","sources":["../src/matrix.ts"],"names":[],"mappings":"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"}
package/lib/matrix.mjs CHANGED
@@ -25,19 +25,30 @@ import { MatrixUndoProvider } from "./undoprovider.mjs";
25
25
  * matrix data and physically stores data in Z-order to leverage CPU caches and
26
26
  * prefetching when reading in either row or column major order. (See README.md
27
27
  * for more details.)
28
- *
29
- * @public
28
+ * @alpha
30
29
  */
31
30
  export class SharedMatrix extends SharedObject {
32
31
  static getFactory() {
33
32
  return new SharedMatrixFactory();
34
33
  }
35
- constructor(runtime, id, attributes) {
34
+ /**
35
+ * Constructor for the Shared Matrix
36
+ * @param runtime - DataStore runtime.
37
+ * @param id - id of the dds
38
+ * @param attributes - channel attributes
39
+ * @param _isSetCellConflictResolutionPolicyFWW - Conflict resolution for Matrix set op is First Writer Win in case of
40
+ * race condition. Client can still overwrite values in case of no race.
41
+ */
42
+ constructor(runtime, id, attributes, _isSetCellConflictResolutionPolicyFWW) {
36
43
  super(id, runtime, attributes, "fluid_matrix_");
37
44
  this.id = id;
38
45
  this.consumers = new Set();
39
46
  this.cells = new SparseArray2D(); // Stores cell values.
40
47
  this.pending = new SparseArray2D(); // Tracks pending writes.
48
+ this.cellLastWriteTracker = new SparseArray2D(); // Tracks last writes sequence number and clientId in a cell.
49
+ this.userSwitchedSetCellPolicy = false; // Set to true when the user calls switchPolicy.
50
+ // Used to track if there is any reentrancy in setCell code.
51
+ this.reentrantCount = 0;
41
52
  // Invoked by PermutationVector to notify IMatrixConsumers of row insertion/deletions.
42
53
  this.onRowDelta = (position, removedCount, insertedCount) => {
43
54
  for (const consumer of this.consumers) {
@@ -54,14 +65,18 @@ export class SharedMatrix extends SharedObject {
54
65
  for (const rowHandle of rowHandles) {
55
66
  this.cells.clearRows(/* rowStart: */ rowHandle, /* rowCount: */ 1);
56
67
  this.pending.clearRows(/* rowStart: */ rowHandle, /* rowCount: */ 1);
68
+ this.cellLastWriteTracker.clearRows(/* rowStart: */ rowHandle, /* rowCount: */ 1);
57
69
  }
58
70
  };
59
71
  this.onColHandlesRecycled = (colHandles) => {
60
72
  for (const colHandle of colHandles) {
61
73
  this.cells.clearCols(/* colStart: */ colHandle, /* colCount: */ 1);
62
74
  this.pending.clearCols(/* colStart: */ colHandle, /* colCount: */ 1);
75
+ this.cellLastWriteTracker.clearCols(/* colStart: */ colHandle, /* colCount: */ 1);
63
76
  }
64
77
  };
78
+ this.setCellLwwToFwwPolicySwitchOpSeqNumber =
79
+ _isSetCellConflictResolutionPolicyFWW === true ? 0 : -1;
65
80
  this.rows = new PermutationVector("rows" /* SnapshotPath.rows */, this.logger, runtime, this.onRowDelta, this.onRowHandlesRecycled);
66
81
  this.cols = new PermutationVector("cols" /* SnapshotPath.cols */, this.logger, runtime, this.onColDelta, this.onColHandlesRecycled);
67
82
  }
@@ -101,6 +116,9 @@ export class SharedMatrix extends SharedObject {
101
116
  get colCount() {
102
117
  return this.cols.getLength();
103
118
  }
119
+ isSetCellConflictResolutionPolicyFWW() {
120
+ return this.setCellLwwToFwwPolicySwitchOpSeqNumber > -1 || this.userSwitchedSetCellPolicy;
121
+ }
104
122
  getCell(row, col) {
105
123
  // Perf: When possible, bounds checking is performed inside the implementation for
106
124
  // 'getHandle()' so that it can be elided in the case of a cache hit. This
@@ -127,10 +145,6 @@ export class SharedMatrix extends SharedObject {
127
145
  setCell(row, col, value) {
128
146
  assert(0 <= row && row < this.rowCount && 0 <= col && col < this.colCount, 0x01a /* "Trying to set out-of-bounds cell!" */);
129
147
  this.setCellCore(row, col, value);
130
- // Avoid reentrancy by raising change notifications after the op is queued.
131
- for (const consumer of this.consumers.values()) {
132
- consumer.cellsChanged(row, col, 1, 1, this);
133
- }
134
148
  }
135
149
  setCells(rowStart, colStart, colCount, values) {
136
150
  const rowCount = Math.ceil(values.length / colCount);
@@ -151,23 +165,25 @@ export class SharedMatrix extends SharedObject {
151
165
  r++;
152
166
  }
153
167
  }
154
- // Avoid reentrancy by raising change notifications after the op is queued.
155
- for (const consumer of this.consumers.values()) {
156
- consumer.cellsChanged(rowStart, colStart, rowCount, colCount, this);
157
- }
158
168
  }
159
169
  setCellCore(row, col, value, rowHandle = this.rows.getAllocatedHandle(row), colHandle = this.cols.getAllocatedHandle(col)) {
160
- if (this.undo !== undefined) {
161
- let oldValue = this.cells.getCell(rowHandle, colHandle);
162
- if (oldValue === null) {
163
- oldValue = undefined;
170
+ this.protectAgainstReentrancy(() => {
171
+ if (this.undo !== undefined) {
172
+ let oldValue = this.cells.getCell(rowHandle, colHandle);
173
+ if (oldValue === null) {
174
+ oldValue = undefined;
175
+ }
176
+ this.undo.cellSet(rowHandle, colHandle, oldValue);
164
177
  }
165
- this.undo.cellSet(rowHandle, colHandle, oldValue);
166
- }
167
- this.cells.setCell(rowHandle, colHandle, value);
168
- if (this.isAttached()) {
169
- this.sendSetCellOp(row, col, value, rowHandle, colHandle);
170
- }
178
+ this.cells.setCell(rowHandle, colHandle, value);
179
+ if (this.isAttached()) {
180
+ this.sendSetCellOp(row, col, value, rowHandle, colHandle);
181
+ }
182
+ // Avoid reentrancy by raising change notifications after the op is queued.
183
+ for (const consumer of this.consumers.values()) {
184
+ consumer.cellsChanged(row, col, 1, 1, this);
185
+ }
186
+ });
171
187
  }
172
188
  sendSetCellOp(row, col, value, rowHandle, colHandle, localSeq = this.nextLocalSeq(), rowsRefSeq = this.rows.getCollabWindow().currentSeq, colsRefSeq = this.cols.getCollabWindow().currentSeq) {
173
189
  assert(this.isAttached(), 0x1e2 /* "Caller must ensure 'isAttached()' before calling 'sendSetCellOp'." */);
@@ -176,6 +192,7 @@ export class SharedMatrix extends SharedObject {
176
192
  row,
177
193
  col,
178
194
  value,
195
+ fwwMode: this.userSwitchedSetCellPolicy || this.setCellLwwToFwwPolicySwitchOpSeqNumber > -1,
179
196
  };
180
197
  const metadata = {
181
198
  rowHandle,
@@ -183,10 +200,26 @@ export class SharedMatrix extends SharedObject {
183
200
  localSeq,
184
201
  rowsRefSeq,
185
202
  colsRefSeq,
203
+ referenceSeqNumber: this.runtime.deltaManager.lastSequenceNumber,
186
204
  };
187
205
  this.submitLocalMessage(op, metadata);
188
206
  this.pending.setCell(rowHandle, colHandle, localSeq);
189
207
  }
208
+ /**
209
+ * This makes sure that the code inside the callback is not reentrant. We need to do that because we raise notifications
210
+ * to the consumers telling about these changes and they can try to change the matrix while listening to those notifications
211
+ * which can make the shared matrix to be in bad state. For example, we are raising notification for a setCell changes and
212
+ * a consumer tries to delete that row/col on receiving that notification which can lead to this matrix trying to setCell in
213
+ * a deleted row/col.
214
+ * @param callback - code that needs to protected against reentrancy.
215
+ */
216
+ protectAgainstReentrancy(callback) {
217
+ assert(this.reentrantCount === 0, 0x85d /* reentrant code */);
218
+ this.reentrantCount++;
219
+ callback();
220
+ this.reentrantCount--;
221
+ assert(this.reentrantCount === 0, 0x85e /* reentrant code on exit */);
222
+ }
190
223
  submitVectorMessage(currentVector, oppositeVector, dimension, message) {
191
224
  // Ideally, we would have a single 'localSeq' counter that is shared between both PermutationVectors
192
225
  // and the SharedMatrix's cell data. Instead, we externally advance each MergeTree's 'localSeq' counter
@@ -209,21 +242,21 @@ export class SharedMatrix extends SharedObject {
209
242
  this.submitVectorMessage(this.cols, this.rows, "cols" /* SnapshotPath.cols */, message);
210
243
  }
211
244
  insertCols(colStart, count) {
212
- this.submitColMessage(this.cols.insert(colStart, count));
245
+ this.protectAgainstReentrancy(() => this.submitColMessage(this.cols.insert(colStart, count)));
213
246
  }
214
247
  removeCols(colStart, count) {
215
- this.submitColMessage(this.cols.remove(colStart, count));
248
+ this.protectAgainstReentrancy(() => this.submitColMessage(this.cols.remove(colStart, count)));
216
249
  }
217
250
  submitRowMessage(message) {
218
251
  this.submitVectorMessage(this.rows, this.cols, "rows" /* SnapshotPath.rows */, message);
219
252
  }
220
253
  insertRows(rowStart, count) {
221
- this.submitRowMessage(this.rows.insert(rowStart, count));
254
+ this.protectAgainstReentrancy(() => this.submitRowMessage(this.rows.insert(rowStart, count)));
222
255
  }
223
256
  removeRows(rowStart, count) {
224
- this.submitRowMessage(this.rows.remove(rowStart, count));
257
+ this.protectAgainstReentrancy(() => this.submitRowMessage(this.rows.remove(rowStart, count)));
225
258
  }
226
- /** @internal */ _undoRemoveRows(rowStart, spec) {
259
+ /***/ _undoRemoveRows(rowStart, spec) {
227
260
  const { op, inserted } = reinsertSegmentIntoVector(this.rows, rowStart, spec);
228
261
  this.submitRowMessage(op);
229
262
  // Generate setCell ops for each populated cell in the reinserted rows.
@@ -243,7 +276,7 @@ export class SharedMatrix extends SharedObject {
243
276
  consumer.cellsChanged(rowStart, /* colStart: */ 0, rowCount, this.colCount, this);
244
277
  }
245
278
  }
246
- /** @internal */ _undoRemoveCols(colStart, spec) {
279
+ /***/ _undoRemoveCols(colStart, spec) {
247
280
  const { op, inserted } = reinsertSegmentIntoVector(this.cols, colStart, spec);
248
281
  this.submitColMessage(op);
249
282
  // Generate setCell ops for each populated cell in the reinserted cols.
@@ -267,7 +300,16 @@ export class SharedMatrix extends SharedObject {
267
300
  const builder = new SummaryTreeBuilder();
268
301
  builder.addWithStats("rows" /* SnapshotPath.rows */, this.rows.summarize(this.runtime, this.handle, serializer));
269
302
  builder.addWithStats("cols" /* SnapshotPath.cols */, this.cols.summarize(this.runtime, this.handle, serializer));
270
- builder.addBlob("cells" /* SnapshotPath.cells */, serializer.stringify([this.cells.snapshot(), this.pending.snapshot()], this.handle));
303
+ const artifactsToSummarize = [
304
+ this.cells.snapshot(),
305
+ this.pending.snapshot(),
306
+ this.setCellLwwToFwwPolicySwitchOpSeqNumber,
307
+ ];
308
+ // Only need to store it in the snapshot if we have switched the policy already.
309
+ if (this.setCellLwwToFwwPolicySwitchOpSeqNumber > -1) {
310
+ artifactsToSummarize.push(this.cellLastWriteTracker.snapshot());
311
+ }
312
+ builder.addBlob("cells" /* SnapshotPath.cells */, serializer.stringify(artifactsToSummarize, this.handle));
271
313
  return builder.getSummaryTree();
272
314
  }
273
315
  /**
@@ -317,7 +359,9 @@ export class SharedMatrix extends SharedObject {
317
359
  this.rows.startOrUpdateCollaboration(this.runtime.clientId);
318
360
  this.cols.startOrUpdateCollaboration(this.runtime.clientId);
319
361
  }
320
- rebasePosition(client, pos, referenceSequenceNumber, localSeq) {
362
+ rebasePosition(
363
+ // eslint-disable-next-line import/no-deprecated
364
+ client, pos, referenceSequenceNumber, localSeq) {
321
365
  const { clientId } = client.getCollabWindow();
322
366
  const { segment, offset } = client.getContainingSegment(pos, { referenceSequenceNumber, clientId: client.getLongClientId(clientId) }, localSeq);
323
367
  if (segment === undefined || offset === undefined) {
@@ -336,16 +380,27 @@ export class SharedMatrix extends SharedObject {
336
380
  default: {
337
381
  assert(content.type === MatrixOp.set, 0x020 /* "Unknown SharedMatrix 'op' type." */);
338
382
  const setOp = content;
339
- const { rowHandle, colHandle, localSeq, rowsRefSeq, colsRefSeq } = localOpMetadata;
340
- // If there are more pending local writes to the same row/col handle, it is important
341
- // to skip resubmitting this op since it is possible the row/col handle has been recycled
342
- // and now refers to a different position than when this op was originally submitted.
343
- if (this.isLatestPendingWrite(rowHandle, colHandle, localSeq)) {
344
- const row = this.rebasePosition(this.rows, setOp.row, rowsRefSeq, localSeq);
345
- const col = this.rebasePosition(this.cols, setOp.col, colsRefSeq, localSeq);
346
- if (row !== undefined && col !== undefined && row >= 0 && col >= 0) {
383
+ const { rowHandle, colHandle, localSeq, rowsRefSeq, colsRefSeq, referenceSeqNumber, } = localOpMetadata;
384
+ // If after rebasing the op, we get a valid row/col number, that means the row/col
385
+ // handles have not been recycled and we can safely use them.
386
+ const row = this.rebasePosition(this.rows, setOp.row, rowsRefSeq, localSeq);
387
+ const col = this.rebasePosition(this.cols, setOp.col, colsRefSeq, localSeq);
388
+ if (row !== undefined && col !== undefined && row >= 0 && col >= 0) {
389
+ const lastCellModificationDetails = this.cellLastWriteTracker.getCell(rowHandle, colHandle);
390
+ // If the mode is LWW, then send the op.
391
+ // Otherwise if the current mode is FWW and if we generated this op, after seeing the
392
+ // last set op, or it is the first set op for the cell, then regenerate the op,
393
+ // otherwise raise conflict. We want to check the current mode here and not that
394
+ // whether op was made in FWW or not.
395
+ if (this.setCellLwwToFwwPolicySwitchOpSeqNumber === -1 ||
396
+ lastCellModificationDetails === undefined ||
397
+ referenceSeqNumber >= lastCellModificationDetails.seqNum) {
347
398
  this.sendSetCellOp(row, col, setOp.value, rowHandle, colHandle, localSeq, rowsRefSeq, colsRefSeq);
348
399
  }
400
+ else if (this.pending.getCell(rowHandle, colHandle) !== undefined) {
401
+ // Clear the pending changes if any as we are not sending the op.
402
+ this.pending.setCell(rowHandle, colHandle, undefined);
403
+ }
349
404
  }
350
405
  break;
351
406
  }
@@ -359,14 +414,32 @@ export class SharedMatrix extends SharedObject {
359
414
  try {
360
415
  await this.rows.load(this.runtime, new ObjectStoragePartition(storage, "rows" /* SnapshotPath.rows */), this.serializer);
361
416
  await this.cols.load(this.runtime, new ObjectStoragePartition(storage, "cols" /* SnapshotPath.cols */), this.serializer);
362
- const [cellData, pendingCliSeqData] = await deserializeBlob(storage, "cells" /* SnapshotPath.cells */, this.serializer);
417
+ const [cellData, _pendingCliSeqData, setCellLwwToFwwPolicySwitchOpSeqNumber, cellLastWriteTracker,] = await deserializeBlob(storage, "cells" /* SnapshotPath.cells */, this.serializer);
363
418
  this.cells = SparseArray2D.load(cellData);
364
- this.pending = SparseArray2D.load(pendingCliSeqData);
419
+ this.setCellLwwToFwwPolicySwitchOpSeqNumber =
420
+ setCellLwwToFwwPolicySwitchOpSeqNumber ?? -1;
421
+ if (cellLastWriteTracker !== undefined) {
422
+ this.cellLastWriteTracker = SparseArray2D.load(cellLastWriteTracker);
423
+ }
365
424
  }
366
425
  catch (error) {
367
426
  this.logger.sendErrorEvent({ eventName: "MatrixLoadFailed" }, error);
368
427
  }
369
428
  }
429
+ /**
430
+ * Tells whether the setCell op should be applied or not based on First Write Win policy. It assumes
431
+ * we are in FWW mode.
432
+ */
433
+ shouldSetCellBasedOnFWW(rowHandle, colHandle, message) {
434
+ assert(this.setCellLwwToFwwPolicySwitchOpSeqNumber > -1, 0x85f /* should be in Fww mode when calling this method */);
435
+ assert(message.clientId !== null, 0x860 /* clientId should not be null */);
436
+ const lastCellModificationDetails = this.cellLastWriteTracker.getCell(rowHandle, colHandle);
437
+ // If someone tried to Overwrite the cell value or first write on this cell or
438
+ // same client tried to modify the cell.
439
+ return (lastCellModificationDetails === undefined ||
440
+ lastCellModificationDetails.clientId === message.clientId ||
441
+ message.referenceSequenceNumber >= lastCellModificationDetails.seqNum);
442
+ }
370
443
  processCore(rawMessage, local, localOpMetadata) {
371
444
  const msg = parseHandles(rawMessage, this.serializer);
372
445
  const contents = msg.contents;
@@ -379,13 +452,28 @@ export class SharedMatrix extends SharedObject {
379
452
  break;
380
453
  default: {
381
454
  assert(contents.type === MatrixOp.set, 0x021 /* "SharedMatrix message contents have unexpected type!" */);
382
- const { row, col } = contents;
455
+ const { row, col, value, fwwMode } = contents;
456
+ const isPreviousSetCellPolicyModeFWW = this.setCellLwwToFwwPolicySwitchOpSeqNumber > -1;
457
+ // If this is the first op notifying us of the policy change, then set the policy change seq number.
458
+ if (this.setCellLwwToFwwPolicySwitchOpSeqNumber === -1 && fwwMode === true) {
459
+ this.setCellLwwToFwwPolicySwitchOpSeqNumber = rawMessage.sequenceNumber;
460
+ }
461
+ assert(rawMessage.clientId !== null, 0x861 /* clientId should not be null!! */);
383
462
  if (local) {
384
463
  // We are receiving the ACK for a local pending set operation.
385
464
  const { rowHandle, colHandle, localSeq } = localOpMetadata;
386
- // If this is the most recent write to the cell by the local client, remove our
387
- // entry from 'pendingCliSeqs' to resume allowing remote writes.
388
- if (this.isLatestPendingWrite(rowHandle, colHandle, localSeq)) {
465
+ const isLatestPendingOp = this.isLatestPendingWrite(rowHandle, colHandle, localSeq);
466
+ // If policy is switched and cell should be modified too based on policy, then update the tracker.
467
+ // If policy is not switched, then also update the tracker in case it is the latest.
468
+ if ((this.setCellLwwToFwwPolicySwitchOpSeqNumber > -1 &&
469
+ this.shouldSetCellBasedOnFWW(rowHandle, colHandle, rawMessage)) ||
470
+ (this.setCellLwwToFwwPolicySwitchOpSeqNumber === -1 && isLatestPendingOp)) {
471
+ this.cellLastWriteTracker.setCell(rowHandle, colHandle, {
472
+ seqNum: rawMessage.sequenceNumber,
473
+ clientId: rawMessage.clientId,
474
+ });
475
+ }
476
+ if (isLatestPendingOp) {
389
477
  this.pending.setCell(rowHandle, colHandle, undefined);
390
478
  }
391
479
  }
@@ -397,11 +485,39 @@ export class SharedMatrix extends SharedObject {
397
485
  const rowHandle = this.rows.getAllocatedHandle(adjustedRow);
398
486
  const colHandle = this.cols.getAllocatedHandle(adjustedCol);
399
487
  assert(isHandleValid(rowHandle) && isHandleValid(colHandle), 0x022 /* "SharedMatrix row and/or col handles are invalid!" */);
400
- // If there is a pending (unACKed) local write to the same cell, skip the current op
401
- // since it "happened before" the pending write.
402
- if (this.pending.getCell(rowHandle, colHandle) === undefined) {
403
- const { value } = contents;
488
+ if (this.setCellLwwToFwwPolicySwitchOpSeqNumber > -1) {
489
+ // If someone tried to Overwrite the cell value or first write on this cell or
490
+ // same client tried to modify the cell or if the previous mode was LWW, then we need to still
491
+ // overwrite the cell and raise conflict if we have pending changes as our change is going to be lost.
492
+ if (!isPreviousSetCellPolicyModeFWW ||
493
+ this.shouldSetCellBasedOnFWW(rowHandle, colHandle, rawMessage)) {
494
+ const previousValue = this.cells.getCell(rowHandle, colHandle);
495
+ this.cells.setCell(rowHandle, colHandle, value);
496
+ this.cellLastWriteTracker.setCell(rowHandle, colHandle, {
497
+ seqNum: rawMessage.sequenceNumber,
498
+ clientId: rawMessage.clientId,
499
+ });
500
+ for (const consumer of this.consumers.values()) {
501
+ consumer.cellsChanged(adjustedRow, adjustedCol, 1, 1, this);
502
+ }
503
+ // Check is there are any pending changes, which will be rejected. If so raise conflict.
504
+ if (this.pending.getCell(rowHandle, colHandle) !== undefined) {
505
+ // Don't reset the pending value yet, as there maybe more fww op from same client, so we want
506
+ // to raise conflict event for that op also.
507
+ this.emit("conflict", row, col, value, // Current value
508
+ previousValue, // Ignored local value
509
+ this);
510
+ }
511
+ }
512
+ }
513
+ else if (this.pending.getCell(rowHandle, colHandle) === undefined) {
514
+ // If there is a pending (unACKed) local write to the same cell, skip the current op
515
+ // since it "happened before" the pending write.
404
516
  this.cells.setCell(rowHandle, colHandle, value);
517
+ this.cellLastWriteTracker.setCell(rowHandle, colHandle, {
518
+ seqNum: rawMessage.sequenceNumber,
519
+ clientId: rawMessage.clientId,
520
+ });
405
521
  for (const consumer of this.consumers.values()) {
406
522
  consumer.cellsChanged(adjustedRow, adjustedCol, 1, 1, this);
407
523
  }
@@ -412,6 +528,20 @@ export class SharedMatrix extends SharedObject {
412
528
  }
413
529
  }
414
530
  }
531
+ /**
532
+ * Api to switch Set Op policy from Last Writer Win to First Writer Win. It only switches from LWW to FWW
533
+ * and not from FWW to LWW. The next SetOp which is sent will communicate this policy to other clients.
534
+ */
535
+ switchSetCellPolicy() {
536
+ if (this.setCellLwwToFwwPolicySwitchOpSeqNumber === -1) {
537
+ if (this.isAttached()) {
538
+ this.userSwitchedSetCellPolicy = true;
539
+ }
540
+ else {
541
+ this.setCellLwwToFwwPolicySwitchOpSeqNumber = 0;
542
+ }
543
+ }
544
+ }
415
545
  /**
416
546
  * Returns true if the latest pending write to the cell indicated by the given row/col handles
417
547
  * matches the given 'localSeq'.
@@ -484,6 +614,7 @@ export class SharedMatrix extends SharedObject {
484
614
  localSeq,
485
615
  rowsRefSeq,
486
616
  colsRefSeq,
617
+ referenceSeqNumber: this.runtime.deltaManager.lastSequenceNumber,
487
618
  };
488
619
  this.pending.setCell(rowHandle, colHandle, localSeq);
489
620
  return metadata;