@fluidframework/matrix 2.0.0-internal.7.3.0 → 2.0.0-internal.7.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 (68) hide show
  1. package/CHANGELOG.md +4 -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 +19 -9
  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 +71 -11
  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 +71 -11
  13. package/dist/matrix.cjs +175 -46
  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 +5 -1
  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/runtime.cjs +1 -1
  25. package/dist/runtime.cjs.map +1 -1
  26. package/dist/runtime.d.ts +1 -1
  27. package/dist/types.cjs.map +1 -1
  28. package/dist/types.d.ts +2 -2
  29. package/lib/handlecache.d.ts +2 -2
  30. package/lib/handlecache.d.ts.map +1 -1
  31. package/lib/index.d.ts +3 -3
  32. package/lib/index.d.ts.map +1 -1
  33. package/lib/index.mjs.map +1 -1
  34. package/lib/matrix-alpha.d.ts +71 -11
  35. package/lib/matrix-beta.d.ts +41 -134
  36. package/lib/matrix-public.d.ts +41 -134
  37. package/lib/matrix-untrimmed.d.ts +71 -11
  38. package/lib/matrix.d.ts +69 -11
  39. package/lib/matrix.d.ts.map +1 -1
  40. package/lib/matrix.mjs +175 -46
  41. package/lib/matrix.mjs.map +1 -1
  42. package/lib/ops.d.ts +5 -1
  43. package/lib/ops.d.ts.map +1 -1
  44. package/lib/ops.mjs +1 -0
  45. package/lib/ops.mjs.map +1 -1
  46. package/lib/packageVersion.d.ts +1 -1
  47. package/lib/packageVersion.mjs +1 -1
  48. package/lib/packageVersion.mjs.map +1 -1
  49. package/lib/permutationvector.d.ts +3 -3
  50. package/lib/permutationvector.d.ts.map +1 -1
  51. package/lib/runtime.d.ts +1 -1
  52. package/lib/runtime.d.ts.map +1 -1
  53. package/lib/runtime.mjs +1 -1
  54. package/lib/runtime.mjs.map +1 -1
  55. package/lib/serialization.d.ts.map +1 -1
  56. package/lib/sparsearray2d.d.ts.map +1 -1
  57. package/lib/types.d.ts +2 -2
  58. package/lib/types.mjs.map +1 -1
  59. package/lib/undoprovider.d.ts +4 -4
  60. package/lib/undoprovider.d.ts.map +1 -1
  61. package/matrix.test-files.tar +0 -0
  62. package/package.json +26 -31
  63. package/src/index.ts +1 -1
  64. package/src/matrix.ts +282 -60
  65. package/src/ops.ts +5 -0
  66. package/src/packageVersion.ts +1 -1
  67. package/src/runtime.ts +1 -1
  68. package/src/types.ts +2 -2
package/dist/matrix.cjs CHANGED
@@ -29,19 +29,30 @@ const undoprovider_1 = require("./undoprovider.cjs");
29
29
  * matrix data and physically stores data in Z-order to leverage CPU caches and
30
30
  * prefetching when reading in either row or column major order. (See README.md
31
31
  * for more details.)
32
- *
33
- * @public
32
+ * @alpha
34
33
  */
35
34
  class SharedMatrix extends shared_object_base_1.SharedObject {
36
35
  static getFactory() {
37
36
  return new runtime_1.SharedMatrixFactory();
38
37
  }
39
- constructor(runtime, id, attributes) {
38
+ /**
39
+ * Constructor for the Shared Matrix
40
+ * @param runtime - DataStore runtime.
41
+ * @param id - id of the dds
42
+ * @param attributes - channel attributes
43
+ * @param _isSetCellConflictResolutionPolicyFWW - Conflict resolution for Matrix set op is First Writer Win in case of
44
+ * race condition. Client can still overwrite values in case of no race.
45
+ */
46
+ constructor(runtime, id, attributes, _isSetCellConflictResolutionPolicyFWW) {
40
47
  super(id, runtime, attributes, "fluid_matrix_");
41
48
  this.id = id;
42
49
  this.consumers = new Set();
43
50
  this.cells = new sparsearray2d_1.SparseArray2D(); // Stores cell values.
44
51
  this.pending = new sparsearray2d_1.SparseArray2D(); // Tracks pending writes.
52
+ this.cellLastWriteTracker = new sparsearray2d_1.SparseArray2D(); // Tracks last writes sequence number and clientId in a cell.
53
+ this.userSwitchedSetCellPolicy = false; // Set to true when the user calls switchPolicy.
54
+ // Used to track if there is any reentrancy in setCell code.
55
+ this.reentrantCount = 0;
45
56
  // Invoked by PermutationVector to notify IMatrixConsumers of row insertion/deletions.
46
57
  this.onRowDelta = (position, removedCount, insertedCount) => {
47
58
  for (const consumer of this.consumers) {
@@ -58,14 +69,18 @@ class SharedMatrix extends shared_object_base_1.SharedObject {
58
69
  for (const rowHandle of rowHandles) {
59
70
  this.cells.clearRows(/* rowStart: */ rowHandle, /* rowCount: */ 1);
60
71
  this.pending.clearRows(/* rowStart: */ rowHandle, /* rowCount: */ 1);
72
+ this.cellLastWriteTracker.clearRows(/* rowStart: */ rowHandle, /* rowCount: */ 1);
61
73
  }
62
74
  };
63
75
  this.onColHandlesRecycled = (colHandles) => {
64
76
  for (const colHandle of colHandles) {
65
77
  this.cells.clearCols(/* colStart: */ colHandle, /* colCount: */ 1);
66
78
  this.pending.clearCols(/* colStart: */ colHandle, /* colCount: */ 1);
79
+ this.cellLastWriteTracker.clearCols(/* colStart: */ colHandle, /* colCount: */ 1);
67
80
  }
68
81
  };
82
+ this.setCellLwwToFwwPolicySwitchOpSeqNumber =
83
+ _isSetCellConflictResolutionPolicyFWW === true ? 0 : -1;
69
84
  this.rows = new permutationvector_1.PermutationVector("rows" /* SnapshotPath.rows */, this.logger, runtime, this.onRowDelta, this.onRowHandlesRecycled);
70
85
  this.cols = new permutationvector_1.PermutationVector("cols" /* SnapshotPath.cols */, this.logger, runtime, this.onColDelta, this.onColHandlesRecycled);
71
86
  }
@@ -105,6 +120,9 @@ class SharedMatrix extends shared_object_base_1.SharedObject {
105
120
  get colCount() {
106
121
  return this.cols.getLength();
107
122
  }
123
+ isSetCellConflictResolutionPolicyFWW() {
124
+ return this.setCellLwwToFwwPolicySwitchOpSeqNumber > -1 || this.userSwitchedSetCellPolicy;
125
+ }
108
126
  getCell(row, col) {
109
127
  // Perf: When possible, bounds checking is performed inside the implementation for
110
128
  // 'getHandle()' so that it can be elided in the case of a cache hit. This
@@ -131,10 +149,6 @@ class SharedMatrix extends shared_object_base_1.SharedObject {
131
149
  setCell(row, col, value) {
132
150
  (0, core_utils_1.assert)(0 <= row && row < this.rowCount && 0 <= col && col < this.colCount, 0x01a /* "Trying to set out-of-bounds cell!" */);
133
151
  this.setCellCore(row, col, value);
134
- // Avoid reentrancy by raising change notifications after the op is queued.
135
- for (const consumer of this.consumers.values()) {
136
- consumer.cellsChanged(row, col, 1, 1, this);
137
- }
138
152
  }
139
153
  setCells(rowStart, colStart, colCount, values) {
140
154
  const rowCount = Math.ceil(values.length / colCount);
@@ -155,23 +169,25 @@ class SharedMatrix extends shared_object_base_1.SharedObject {
155
169
  r++;
156
170
  }
157
171
  }
158
- // Avoid reentrancy by raising change notifications after the op is queued.
159
- for (const consumer of this.consumers.values()) {
160
- consumer.cellsChanged(rowStart, colStart, rowCount, colCount, this);
161
- }
162
172
  }
163
173
  setCellCore(row, col, value, rowHandle = this.rows.getAllocatedHandle(row), colHandle = this.cols.getAllocatedHandle(col)) {
164
- if (this.undo !== undefined) {
165
- let oldValue = this.cells.getCell(rowHandle, colHandle);
166
- if (oldValue === null) {
167
- oldValue = undefined;
174
+ this.protectAgainstReentrancy(() => {
175
+ if (this.undo !== undefined) {
176
+ let oldValue = this.cells.getCell(rowHandle, colHandle);
177
+ if (oldValue === null) {
178
+ oldValue = undefined;
179
+ }
180
+ this.undo.cellSet(rowHandle, colHandle, oldValue);
168
181
  }
169
- this.undo.cellSet(rowHandle, colHandle, oldValue);
170
- }
171
- this.cells.setCell(rowHandle, colHandle, value);
172
- if (this.isAttached()) {
173
- this.sendSetCellOp(row, col, value, rowHandle, colHandle);
174
- }
182
+ this.cells.setCell(rowHandle, colHandle, value);
183
+ if (this.isAttached()) {
184
+ this.sendSetCellOp(row, col, value, rowHandle, colHandle);
185
+ }
186
+ // Avoid reentrancy by raising change notifications after the op is queued.
187
+ for (const consumer of this.consumers.values()) {
188
+ consumer.cellsChanged(row, col, 1, 1, this);
189
+ }
190
+ });
175
191
  }
176
192
  sendSetCellOp(row, col, value, rowHandle, colHandle, localSeq = this.nextLocalSeq(), rowsRefSeq = this.rows.getCollabWindow().currentSeq, colsRefSeq = this.cols.getCollabWindow().currentSeq) {
177
193
  (0, core_utils_1.assert)(this.isAttached(), 0x1e2 /* "Caller must ensure 'isAttached()' before calling 'sendSetCellOp'." */);
@@ -180,6 +196,7 @@ class SharedMatrix extends shared_object_base_1.SharedObject {
180
196
  row,
181
197
  col,
182
198
  value,
199
+ fwwMode: this.userSwitchedSetCellPolicy || this.setCellLwwToFwwPolicySwitchOpSeqNumber > -1,
183
200
  };
184
201
  const metadata = {
185
202
  rowHandle,
@@ -187,10 +204,26 @@ class SharedMatrix extends shared_object_base_1.SharedObject {
187
204
  localSeq,
188
205
  rowsRefSeq,
189
206
  colsRefSeq,
207
+ referenceSeqNumber: this.runtime.deltaManager.lastSequenceNumber,
190
208
  };
191
209
  this.submitLocalMessage(op, metadata);
192
210
  this.pending.setCell(rowHandle, colHandle, localSeq);
193
211
  }
212
+ /**
213
+ * This makes sure that the code inside the callback is not reentrant. We need to do that because we raise notifications
214
+ * to the consumers telling about these changes and they can try to change the matrix while listening to those notifications
215
+ * which can make the shared matrix to be in bad state. For example, we are raising notification for a setCell changes and
216
+ * a consumer tries to delete that row/col on receiving that notification which can lead to this matrix trying to setCell in
217
+ * a deleted row/col.
218
+ * @param callback - code that needs to protected against reentrancy.
219
+ */
220
+ protectAgainstReentrancy(callback) {
221
+ (0, core_utils_1.assert)(this.reentrantCount === 0, 0x85d /* reentrant code */);
222
+ this.reentrantCount++;
223
+ callback();
224
+ this.reentrantCount--;
225
+ (0, core_utils_1.assert)(this.reentrantCount === 0, 0x85e /* reentrant code on exit */);
226
+ }
194
227
  submitVectorMessage(currentVector, oppositeVector, dimension, message) {
195
228
  // Ideally, we would have a single 'localSeq' counter that is shared between both PermutationVectors
196
229
  // and the SharedMatrix's cell data. Instead, we externally advance each MergeTree's 'localSeq' counter
@@ -213,21 +246,21 @@ class SharedMatrix extends shared_object_base_1.SharedObject {
213
246
  this.submitVectorMessage(this.cols, this.rows, "cols" /* SnapshotPath.cols */, message);
214
247
  }
215
248
  insertCols(colStart, count) {
216
- this.submitColMessage(this.cols.insert(colStart, count));
249
+ this.protectAgainstReentrancy(() => this.submitColMessage(this.cols.insert(colStart, count)));
217
250
  }
218
251
  removeCols(colStart, count) {
219
- this.submitColMessage(this.cols.remove(colStart, count));
252
+ this.protectAgainstReentrancy(() => this.submitColMessage(this.cols.remove(colStart, count)));
220
253
  }
221
254
  submitRowMessage(message) {
222
255
  this.submitVectorMessage(this.rows, this.cols, "rows" /* SnapshotPath.rows */, message);
223
256
  }
224
257
  insertRows(rowStart, count) {
225
- this.submitRowMessage(this.rows.insert(rowStart, count));
258
+ this.protectAgainstReentrancy(() => this.submitRowMessage(this.rows.insert(rowStart, count)));
226
259
  }
227
260
  removeRows(rowStart, count) {
228
- this.submitRowMessage(this.rows.remove(rowStart, count));
261
+ this.protectAgainstReentrancy(() => this.submitRowMessage(this.rows.remove(rowStart, count)));
229
262
  }
230
- /** @internal */ _undoRemoveRows(rowStart, spec) {
263
+ /***/ _undoRemoveRows(rowStart, spec) {
231
264
  const { op, inserted } = (0, permutationvector_1.reinsertSegmentIntoVector)(this.rows, rowStart, spec);
232
265
  this.submitRowMessage(op);
233
266
  // Generate setCell ops for each populated cell in the reinserted rows.
@@ -247,7 +280,7 @@ class SharedMatrix extends shared_object_base_1.SharedObject {
247
280
  consumer.cellsChanged(rowStart, /* colStart: */ 0, rowCount, this.colCount, this);
248
281
  }
249
282
  }
250
- /** @internal */ _undoRemoveCols(colStart, spec) {
283
+ /***/ _undoRemoveCols(colStart, spec) {
251
284
  const { op, inserted } = (0, permutationvector_1.reinsertSegmentIntoVector)(this.cols, colStart, spec);
252
285
  this.submitColMessage(op);
253
286
  // Generate setCell ops for each populated cell in the reinserted cols.
@@ -271,7 +304,16 @@ class SharedMatrix extends shared_object_base_1.SharedObject {
271
304
  const builder = new runtime_utils_1.SummaryTreeBuilder();
272
305
  builder.addWithStats("rows" /* SnapshotPath.rows */, this.rows.summarize(this.runtime, this.handle, serializer));
273
306
  builder.addWithStats("cols" /* SnapshotPath.cols */, this.cols.summarize(this.runtime, this.handle, serializer));
274
- builder.addBlob("cells" /* SnapshotPath.cells */, serializer.stringify([this.cells.snapshot(), this.pending.snapshot()], this.handle));
307
+ const artifactsToSummarize = [
308
+ this.cells.snapshot(),
309
+ this.pending.snapshot(),
310
+ this.setCellLwwToFwwPolicySwitchOpSeqNumber,
311
+ ];
312
+ // Only need to store it in the snapshot if we have switched the policy already.
313
+ if (this.setCellLwwToFwwPolicySwitchOpSeqNumber > -1) {
314
+ artifactsToSummarize.push(this.cellLastWriteTracker.snapshot());
315
+ }
316
+ builder.addBlob("cells" /* SnapshotPath.cells */, serializer.stringify(artifactsToSummarize, this.handle));
275
317
  return builder.getSummaryTree();
276
318
  }
277
319
  /**
@@ -340,16 +382,27 @@ class SharedMatrix extends shared_object_base_1.SharedObject {
340
382
  default: {
341
383
  (0, core_utils_1.assert)(content.type === ops_1.MatrixOp.set, 0x020 /* "Unknown SharedMatrix 'op' type." */);
342
384
  const setOp = content;
343
- const { rowHandle, colHandle, localSeq, rowsRefSeq, colsRefSeq } = localOpMetadata;
344
- // If there are more pending local writes to the same row/col handle, it is important
345
- // to skip resubmitting this op since it is possible the row/col handle has been recycled
346
- // and now refers to a different position than when this op was originally submitted.
347
- if (this.isLatestPendingWrite(rowHandle, colHandle, localSeq)) {
348
- const row = this.rebasePosition(this.rows, setOp.row, rowsRefSeq, localSeq);
349
- const col = this.rebasePosition(this.cols, setOp.col, colsRefSeq, localSeq);
350
- if (row !== undefined && col !== undefined && row >= 0 && col >= 0) {
385
+ const { rowHandle, colHandle, localSeq, rowsRefSeq, colsRefSeq, referenceSeqNumber, } = localOpMetadata;
386
+ // If after rebasing the op, we get a valid row/col number, that means the row/col
387
+ // handles have not been recycled and we can safely use them.
388
+ const row = this.rebasePosition(this.rows, setOp.row, rowsRefSeq, localSeq);
389
+ const col = this.rebasePosition(this.cols, setOp.col, colsRefSeq, localSeq);
390
+ if (row !== undefined && col !== undefined && row >= 0 && col >= 0) {
391
+ const lastCellModificationDetails = this.cellLastWriteTracker.getCell(rowHandle, colHandle);
392
+ // If the mode is LWW, then send the op.
393
+ // Otherwise if the current mode is FWW and if we generated this op, after seeing the
394
+ // last set op, or it is the first set op for the cell, then regenerate the op,
395
+ // otherwise raise conflict. We want to check the current mode here and not that
396
+ // whether op was made in FWW or not.
397
+ if (this.setCellLwwToFwwPolicySwitchOpSeqNumber === -1 ||
398
+ lastCellModificationDetails === undefined ||
399
+ referenceSeqNumber >= lastCellModificationDetails.seqNum) {
351
400
  this.sendSetCellOp(row, col, setOp.value, rowHandle, colHandle, localSeq, rowsRefSeq, colsRefSeq);
352
401
  }
402
+ else if (this.pending.getCell(rowHandle, colHandle) !== undefined) {
403
+ // Clear the pending changes if any as we are not sending the op.
404
+ this.pending.setCell(rowHandle, colHandle, undefined);
405
+ }
353
406
  }
354
407
  break;
355
408
  }
@@ -363,14 +416,32 @@ class SharedMatrix extends shared_object_base_1.SharedObject {
363
416
  try {
364
417
  await this.rows.load(this.runtime, new runtime_utils_1.ObjectStoragePartition(storage, "rows" /* SnapshotPath.rows */), this.serializer);
365
418
  await this.cols.load(this.runtime, new runtime_utils_1.ObjectStoragePartition(storage, "cols" /* SnapshotPath.cols */), this.serializer);
366
- const [cellData, pendingCliSeqData] = await (0, serialization_1.deserializeBlob)(storage, "cells" /* SnapshotPath.cells */, this.serializer);
419
+ const [cellData, _pendingCliSeqData, setCellLwwToFwwPolicySwitchOpSeqNumber, cellLastWriteTracker,] = await (0, serialization_1.deserializeBlob)(storage, "cells" /* SnapshotPath.cells */, this.serializer);
367
420
  this.cells = sparsearray2d_1.SparseArray2D.load(cellData);
368
- this.pending = sparsearray2d_1.SparseArray2D.load(pendingCliSeqData);
421
+ this.setCellLwwToFwwPolicySwitchOpSeqNumber =
422
+ setCellLwwToFwwPolicySwitchOpSeqNumber ?? -1;
423
+ if (cellLastWriteTracker !== undefined) {
424
+ this.cellLastWriteTracker = sparsearray2d_1.SparseArray2D.load(cellLastWriteTracker);
425
+ }
369
426
  }
370
427
  catch (error) {
371
428
  this.logger.sendErrorEvent({ eventName: "MatrixLoadFailed" }, error);
372
429
  }
373
430
  }
431
+ /**
432
+ * Tells whether the setCell op should be applied or not based on First Write Win policy. It assumes
433
+ * we are in FWW mode.
434
+ */
435
+ shouldSetCellBasedOnFWW(rowHandle, colHandle, message) {
436
+ (0, core_utils_1.assert)(this.setCellLwwToFwwPolicySwitchOpSeqNumber > -1, 0x85f /* should be in Fww mode when calling this method */);
437
+ (0, core_utils_1.assert)(message.clientId !== null, 0x860 /* clientId should not be null */);
438
+ const lastCellModificationDetails = this.cellLastWriteTracker.getCell(rowHandle, colHandle);
439
+ // If someone tried to Overwrite the cell value or first write on this cell or
440
+ // same client tried to modify the cell.
441
+ return (lastCellModificationDetails === undefined ||
442
+ lastCellModificationDetails.clientId === message.clientId ||
443
+ message.referenceSequenceNumber >= lastCellModificationDetails.seqNum);
444
+ }
374
445
  processCore(rawMessage, local, localOpMetadata) {
375
446
  const msg = (0, shared_object_base_1.parseHandles)(rawMessage, this.serializer);
376
447
  const contents = msg.contents;
@@ -383,13 +454,28 @@ class SharedMatrix extends shared_object_base_1.SharedObject {
383
454
  break;
384
455
  default: {
385
456
  (0, core_utils_1.assert)(contents.type === ops_1.MatrixOp.set, 0x021 /* "SharedMatrix message contents have unexpected type!" */);
386
- const { row, col } = contents;
457
+ const { row, col, value, fwwMode } = contents;
458
+ const isPreviousSetCellPolicyModeFWW = this.setCellLwwToFwwPolicySwitchOpSeqNumber > -1;
459
+ // If this is the first op notifying us of the policy change, then set the policy change seq number.
460
+ if (this.setCellLwwToFwwPolicySwitchOpSeqNumber === -1 && fwwMode === true) {
461
+ this.setCellLwwToFwwPolicySwitchOpSeqNumber = rawMessage.sequenceNumber;
462
+ }
463
+ (0, core_utils_1.assert)(rawMessage.clientId !== null, 0x861 /* clientId should not be null!! */);
387
464
  if (local) {
388
465
  // We are receiving the ACK for a local pending set operation.
389
466
  const { rowHandle, colHandle, localSeq } = localOpMetadata;
390
- // If this is the most recent write to the cell by the local client, remove our
391
- // entry from 'pendingCliSeqs' to resume allowing remote writes.
392
- if (this.isLatestPendingWrite(rowHandle, colHandle, localSeq)) {
467
+ const isLatestPendingOp = this.isLatestPendingWrite(rowHandle, colHandle, localSeq);
468
+ // If policy is switched and cell should be modified too based on policy, then update the tracker.
469
+ // If policy is not switched, then also update the tracker in case it is the latest.
470
+ if ((this.setCellLwwToFwwPolicySwitchOpSeqNumber > -1 &&
471
+ this.shouldSetCellBasedOnFWW(rowHandle, colHandle, rawMessage)) ||
472
+ (this.setCellLwwToFwwPolicySwitchOpSeqNumber === -1 && isLatestPendingOp)) {
473
+ this.cellLastWriteTracker.setCell(rowHandle, colHandle, {
474
+ seqNum: rawMessage.sequenceNumber,
475
+ clientId: rawMessage.clientId,
476
+ });
477
+ }
478
+ if (isLatestPendingOp) {
393
479
  this.pending.setCell(rowHandle, colHandle, undefined);
394
480
  }
395
481
  }
@@ -401,11 +487,39 @@ class SharedMatrix extends shared_object_base_1.SharedObject {
401
487
  const rowHandle = this.rows.getAllocatedHandle(adjustedRow);
402
488
  const colHandle = this.cols.getAllocatedHandle(adjustedCol);
403
489
  (0, core_utils_1.assert)((0, handletable_1.isHandleValid)(rowHandle) && (0, handletable_1.isHandleValid)(colHandle), 0x022 /* "SharedMatrix row and/or col handles are invalid!" */);
404
- // If there is a pending (unACKed) local write to the same cell, skip the current op
405
- // since it "happened before" the pending write.
406
- if (this.pending.getCell(rowHandle, colHandle) === undefined) {
407
- const { value } = contents;
490
+ if (this.setCellLwwToFwwPolicySwitchOpSeqNumber > -1) {
491
+ // If someone tried to Overwrite the cell value or first write on this cell or
492
+ // same client tried to modify the cell or if the previous mode was LWW, then we need to still
493
+ // overwrite the cell and raise conflict if we have pending changes as our change is going to be lost.
494
+ if (!isPreviousSetCellPolicyModeFWW ||
495
+ this.shouldSetCellBasedOnFWW(rowHandle, colHandle, rawMessage)) {
496
+ const previousValue = this.cells.getCell(rowHandle, colHandle);
497
+ this.cells.setCell(rowHandle, colHandle, value);
498
+ this.cellLastWriteTracker.setCell(rowHandle, colHandle, {
499
+ seqNum: rawMessage.sequenceNumber,
500
+ clientId: rawMessage.clientId,
501
+ });
502
+ for (const consumer of this.consumers.values()) {
503
+ consumer.cellsChanged(adjustedRow, adjustedCol, 1, 1, this);
504
+ }
505
+ // Check is there are any pending changes, which will be rejected. If so raise conflict.
506
+ if (this.pending.getCell(rowHandle, colHandle) !== undefined) {
507
+ // Don't reset the pending value yet, as there maybe more fww op from same client, so we want
508
+ // to raise conflict event for that op also.
509
+ this.emit("conflict", row, col, value, // Current value
510
+ previousValue, // Ignored local value
511
+ this);
512
+ }
513
+ }
514
+ }
515
+ else if (this.pending.getCell(rowHandle, colHandle) === undefined) {
516
+ // If there is a pending (unACKed) local write to the same cell, skip the current op
517
+ // since it "happened before" the pending write.
408
518
  this.cells.setCell(rowHandle, colHandle, value);
519
+ this.cellLastWriteTracker.setCell(rowHandle, colHandle, {
520
+ seqNum: rawMessage.sequenceNumber,
521
+ clientId: rawMessage.clientId,
522
+ });
409
523
  for (const consumer of this.consumers.values()) {
410
524
  consumer.cellsChanged(adjustedRow, adjustedCol, 1, 1, this);
411
525
  }
@@ -416,6 +530,20 @@ class SharedMatrix extends shared_object_base_1.SharedObject {
416
530
  }
417
531
  }
418
532
  }
533
+ /**
534
+ * Api to switch Set Op policy from Last Writer Win to First Writer Win. It only switches from LWW to FWW
535
+ * and not from FWW to LWW. The next SetOp which is sent will communicate this policy to other clients.
536
+ */
537
+ switchSetCellPolicy() {
538
+ if (this.setCellLwwToFwwPolicySwitchOpSeqNumber === -1) {
539
+ if (this.isAttached()) {
540
+ this.userSwitchedSetCellPolicy = true;
541
+ }
542
+ else {
543
+ this.setCellLwwToFwwPolicySwitchOpSeqNumber = 0;
544
+ }
545
+ }
546
+ }
419
547
  /**
420
548
  * Returns true if the latest pending write to the cell indicated by the given row/col handles
421
549
  * matches the given 'localSeq'.
@@ -488,6 +616,7 @@ class SharedMatrix extends shared_object_base_1.SharedObject {
488
616
  localSeq,
489
617
  rowsRefSeq,
490
618
  colsRefSeq,
619
+ referenceSeqNumber: this.runtime.deltaManager.lastSequenceNumber,
491
620
  };
492
621
  this.pending.setCell(rowHandle, colHandle, localSeq);
493
622
  return metadata;