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