@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.
- package/CHANGELOG.md +4 -0
- package/README.md +39 -0
- package/api-extractor-lint.json +13 -0
- package/api-extractor.json +8 -3
- package/api-report/matrix.api.md +19 -9
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/matrix-alpha.d.ts +71 -11
- package/dist/matrix-beta.d.ts +41 -134
- package/dist/matrix-public.d.ts +41 -134
- package/dist/matrix-untrimmed.d.ts +71 -11
- package/dist/matrix.cjs +175 -46
- package/dist/matrix.cjs.map +1 -1
- package/dist/matrix.d.ts +67 -9
- package/dist/matrix.d.ts.map +1 -1
- package/dist/ops.cjs +1 -0
- package/dist/ops.cjs.map +1 -1
- package/dist/ops.d.ts +5 -1
- package/dist/ops.d.ts.map +1 -1
- package/dist/packageVersion.cjs +1 -1
- package/dist/packageVersion.cjs.map +1 -1
- package/dist/packageVersion.d.ts +1 -1
- package/dist/runtime.cjs +1 -1
- package/dist/runtime.cjs.map +1 -1
- package/dist/runtime.d.ts +1 -1
- package/dist/types.cjs.map +1 -1
- package/dist/types.d.ts +2 -2
- package/lib/handlecache.d.ts +2 -2
- package/lib/handlecache.d.ts.map +1 -1
- package/lib/index.d.ts +3 -3
- package/lib/index.d.ts.map +1 -1
- package/lib/index.mjs.map +1 -1
- package/lib/matrix-alpha.d.ts +71 -11
- package/lib/matrix-beta.d.ts +41 -134
- package/lib/matrix-public.d.ts +41 -134
- package/lib/matrix-untrimmed.d.ts +71 -11
- package/lib/matrix.d.ts +69 -11
- package/lib/matrix.d.ts.map +1 -1
- package/lib/matrix.mjs +175 -46
- package/lib/matrix.mjs.map +1 -1
- package/lib/ops.d.ts +5 -1
- package/lib/ops.d.ts.map +1 -1
- package/lib/ops.mjs +1 -0
- package/lib/ops.mjs.map +1 -1
- package/lib/packageVersion.d.ts +1 -1
- package/lib/packageVersion.mjs +1 -1
- package/lib/packageVersion.mjs.map +1 -1
- package/lib/permutationvector.d.ts +3 -3
- package/lib/permutationvector.d.ts.map +1 -1
- package/lib/runtime.d.ts +1 -1
- package/lib/runtime.d.ts.map +1 -1
- package/lib/runtime.mjs +1 -1
- package/lib/runtime.mjs.map +1 -1
- package/lib/serialization.d.ts.map +1 -1
- package/lib/sparsearray2d.d.ts.map +1 -1
- package/lib/types.d.ts +2 -2
- package/lib/types.mjs.map +1 -1
- package/lib/undoprovider.d.ts +4 -4
- package/lib/undoprovider.d.ts.map +1 -1
- package/matrix.test-files.tar +0 -0
- package/package.json +26 -31
- package/src/index.ts +1 -1
- package/src/matrix.ts +282 -60
- package/src/ops.ts +5 -0
- package/src/packageVersion.ts +1 -1
- package/src/runtime.ts +1 -1
- 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
|
-
|
|
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
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
oldValue
|
|
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.
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
345
|
-
//
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
const
|
|
350
|
-
|
|
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,
|
|
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.
|
|
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
|
-
|
|
391
|
-
//
|
|
392
|
-
|
|
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
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
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;
|