@fluidframework/matrix 2.0.0-internal.7.2.2 → 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 (136) hide show
  1. package/CHANGELOG.md +8 -0
  2. package/README.md +39 -0
  3. package/api-extractor-lint.json +13 -0
  4. package/api-extractor.json +3 -7
  5. package/api-report/matrix.api.md +19 -9
  6. package/dist/{handlecache.js → handlecache.cjs} +3 -3
  7. package/dist/handlecache.cjs.map +1 -0
  8. package/dist/{handletable.js → handletable.cjs} +1 -1
  9. package/dist/handletable.cjs.map +1 -0
  10. package/dist/{index.js → index.cjs} +3 -3
  11. package/dist/index.cjs.map +1 -0
  12. package/dist/index.d.ts +1 -1
  13. package/dist/index.d.ts.map +1 -1
  14. package/dist/matrix-alpha.d.ts +214 -0
  15. package/dist/matrix-beta.d.ts +61 -0
  16. package/dist/matrix-public.d.ts +61 -0
  17. package/dist/matrix-untrimmed.d.ts +214 -0
  18. package/dist/{matrix.js → matrix.cjs} +185 -55
  19. package/dist/matrix.cjs.map +1 -0
  20. package/dist/matrix.d.ts +67 -8
  21. package/dist/matrix.d.ts.map +1 -1
  22. package/dist/{ops.js → ops.cjs} +2 -1
  23. package/dist/ops.cjs.map +1 -0
  24. package/dist/ops.d.ts +5 -1
  25. package/dist/ops.d.ts.map +1 -1
  26. package/dist/{packageVersion.js → packageVersion.cjs} +2 -2
  27. package/dist/packageVersion.cjs.map +1 -0
  28. package/dist/packageVersion.d.ts +1 -1
  29. package/dist/{permutationvector.js → permutationvector.cjs} +5 -4
  30. package/dist/permutationvector.cjs.map +1 -0
  31. package/dist/permutationvector.d.ts.map +1 -1
  32. package/dist/{range.js → range.cjs} +1 -1
  33. package/dist/range.cjs.map +1 -0
  34. package/dist/{runtime.js → runtime.cjs} +7 -3
  35. package/dist/runtime.cjs.map +1 -0
  36. package/dist/runtime.d.ts +4 -0
  37. package/dist/runtime.d.ts.map +1 -1
  38. package/dist/{serialization.js → serialization.cjs} +1 -1
  39. package/dist/serialization.cjs.map +1 -0
  40. package/dist/{sparsearray2d.js → sparsearray2d.cjs} +1 -1
  41. package/dist/sparsearray2d.cjs.map +1 -0
  42. package/dist/tsdoc-metadata.json +1 -1
  43. package/dist/{types.js → types.cjs} +1 -1
  44. package/dist/types.cjs.map +1 -0
  45. package/dist/types.d.ts +6 -0
  46. package/dist/types.d.ts.map +1 -1
  47. package/dist/{undoprovider.js → undoprovider.cjs} +2 -2
  48. package/dist/undoprovider.cjs.map +1 -0
  49. package/lib/handlecache.d.ts +2 -2
  50. package/lib/handlecache.d.ts.map +1 -1
  51. package/lib/{handlecache.js → handlecache.mjs} +3 -4
  52. package/lib/handlecache.mjs.map +1 -0
  53. package/lib/{handletable.js → handletable.mjs} +1 -1
  54. package/lib/handletable.mjs.map +1 -0
  55. package/lib/index.d.ts +3 -3
  56. package/lib/index.d.ts.map +1 -1
  57. package/lib/index.mjs +7 -0
  58. package/lib/index.mjs.map +1 -0
  59. package/lib/matrix-alpha.d.ts +214 -0
  60. package/lib/matrix-beta.d.ts +61 -0
  61. package/lib/matrix-public.d.ts +61 -0
  62. package/lib/matrix-untrimmed.d.ts +214 -0
  63. package/lib/matrix.d.ts +69 -10
  64. package/lib/matrix.d.ts.map +1 -1
  65. package/lib/{matrix.js → matrix.mjs} +184 -55
  66. package/lib/matrix.mjs.map +1 -0
  67. package/lib/ops.d.ts +5 -1
  68. package/lib/ops.d.ts.map +1 -1
  69. package/lib/{ops.js → ops.mjs} +2 -1
  70. package/lib/ops.mjs.map +1 -0
  71. package/lib/packageVersion.d.ts +1 -1
  72. package/lib/{packageVersion.js → packageVersion.mjs} +2 -2
  73. package/lib/packageVersion.mjs.map +1 -0
  74. package/lib/permutationvector.d.ts +3 -3
  75. package/lib/permutationvector.d.ts.map +1 -1
  76. package/lib/{permutationvector.js → permutationvector.mjs} +4 -4
  77. package/lib/permutationvector.mjs.map +1 -0
  78. package/lib/{range.js → range.mjs} +1 -1
  79. package/lib/range.mjs.map +1 -0
  80. package/lib/runtime.d.ts +4 -0
  81. package/lib/runtime.d.ts.map +1 -1
  82. package/lib/{runtime.js → runtime.mjs} +7 -3
  83. package/lib/runtime.mjs.map +1 -0
  84. package/lib/serialization.d.ts.map +1 -1
  85. package/lib/{serialization.js → serialization.mjs} +1 -1
  86. package/lib/serialization.mjs.map +1 -0
  87. package/lib/sparsearray2d.d.ts.map +1 -1
  88. package/lib/{sparsearray2d.js → sparsearray2d.mjs} +1 -1
  89. package/lib/sparsearray2d.mjs.map +1 -0
  90. package/lib/types.d.ts +6 -0
  91. package/lib/types.d.ts.map +1 -1
  92. package/lib/{types.js → types.mjs} +1 -1
  93. package/lib/types.mjs.map +1 -0
  94. package/lib/undoprovider.d.ts +4 -4
  95. package/lib/undoprovider.d.ts.map +1 -1
  96. package/lib/{undoprovider.js → undoprovider.mjs} +2 -2
  97. package/lib/undoprovider.mjs.map +1 -0
  98. package/matrix.test-files.tar +0 -0
  99. package/package.json +59 -35
  100. package/src/index.ts +1 -1
  101. package/src/matrix.ts +284 -59
  102. package/src/ops.ts +5 -0
  103. package/src/packageVersion.ts +1 -1
  104. package/src/permutationvector.ts +2 -0
  105. package/src/runtime.ts +4 -0
  106. package/src/types.ts +6 -0
  107. package/tsc-multi.test.json +4 -0
  108. package/tsconfig.json +6 -4
  109. package/dist/handlecache.js.map +0 -1
  110. package/dist/handletable.js.map +0 -1
  111. package/dist/index.js.map +0 -1
  112. package/dist/matrix.js.map +0 -1
  113. package/dist/ops.js.map +0 -1
  114. package/dist/packageVersion.js.map +0 -1
  115. package/dist/permutationvector.js.map +0 -1
  116. package/dist/range.js.map +0 -1
  117. package/dist/runtime.js.map +0 -1
  118. package/dist/serialization.js.map +0 -1
  119. package/dist/sparsearray2d.js.map +0 -1
  120. package/dist/types.js.map +0 -1
  121. package/dist/undoprovider.js.map +0 -1
  122. package/lib/handlecache.js.map +0 -1
  123. package/lib/handletable.js.map +0 -1
  124. package/lib/index.js +0 -7
  125. package/lib/index.js.map +0 -1
  126. package/lib/matrix.js.map +0 -1
  127. package/lib/ops.js.map +0 -1
  128. package/lib/packageVersion.js.map +0 -1
  129. package/lib/permutationvector.js.map +0 -1
  130. package/lib/range.js.map +0 -1
  131. package/lib/runtime.js.map +0 -1
  132. package/lib/serialization.js.map +0 -1
  133. package/lib/sparsearray2d.js.map +0 -1
  134. package/lib/types.js.map +0 -1
  135. package/lib/undoprovider.js.map +0 -1
  136. package/tsconfig.esnext.json +0 -7
@@ -0,0 +1,214 @@
1
+ import { IChannel } from '@fluidframework/datastore-definitions';
2
+ import { IChannelAttributes } from '@fluidframework/datastore-definitions';
3
+ import { IChannelFactory } from '@fluidframework/datastore-definitions';
4
+ import { IChannelServices } from '@fluidframework/datastore-definitions';
5
+ import { IChannelStorageService } from '@fluidframework/datastore-definitions';
6
+ import { IEventThisPlaceHolder } from '@fluidframework/core-interfaces';
7
+ import { IFluidDataStoreRuntime } from '@fluidframework/datastore-definitions';
8
+ import { IFluidSerializer } from '@fluidframework/shared-object-base';
9
+ import { IJSONSegment } from '@fluidframework/merge-tree';
10
+ import { IMatrixConsumer } from '@tiny-calc/nano';
11
+ import { IMatrixProducer } from '@tiny-calc/nano';
12
+ import { IMatrixReader } from '@tiny-calc/nano';
13
+ import { IMatrixWriter } from '@tiny-calc/nano';
14
+ import { ISequencedDocumentMessage } from '@fluidframework/protocol-definitions';
15
+ import { ISharedObjectEvents } from '@fluidframework/shared-object-base';
16
+ import { ISummaryTreeWithStats } from '@fluidframework/runtime-definitions';
17
+ import { Serializable } from '@fluidframework/datastore-definitions';
18
+ import { SharedObject } from '@fluidframework/shared-object-base';
19
+ import { SummarySerializer } from '@fluidframework/shared-object-base';
20
+
21
+ /**
22
+ * @alpha
23
+ */
24
+ export declare interface IRevertible {
25
+ revert(): any;
26
+ discard(): any;
27
+ }
28
+
29
+ /**
30
+ * Events emitted by Shared Matrix.
31
+ * @alpha
32
+ */
33
+ export declare interface ISharedMatrixEvents<T> extends ISharedObjectEvents {
34
+ /**
35
+ * This event is only emitted when the SetCell Resolution Policy is First Write Win(FWW).
36
+ * This is emitted when two clients race and send changes without observing each other changes,
37
+ * the changes that gets sequenced last would be rejected, and only client who's changes rejected
38
+ * would be notified via this event, with expectation that it will merge its changes back by
39
+ * accounting new information (state from winner of the race).
40
+ *
41
+ * @remarks Listener parameters:
42
+ *
43
+ * - `row` - Row number at which conflict happened.
44
+ *
45
+ * - `col` - Col number at which conflict happened.
46
+ *
47
+ * - `currentValue` - The current value of the cell.
48
+ *
49
+ * - `conflictingValue` - The value that this client tried to set in the cell and got ignored due to conflict.
50
+ *
51
+ * - `target` - The {@link SharedMatrix} itself.
52
+ */
53
+ (event: "conflict", listener: (row: number, col: number, currentValue: MatrixItem<T>, conflictingValue: MatrixItem<T>, target: IEventThisPlaceHolder) => void): any;
54
+ }
55
+
56
+ /**
57
+ * @alpha
58
+ */
59
+ export declare interface IUndoConsumer {
60
+ pushToCurrentOperation(revertible: IRevertible): any;
61
+ }
62
+
63
+ /**
64
+ * A matrix cell value may be undefined (indicating an empty cell) or any serializable type,
65
+ * excluding null. (However, nulls may be embedded inside objects and arrays.)
66
+ * @alpha
67
+ */
68
+ export declare type MatrixItem<T> = Serializable<Exclude<T, null>> | undefined;
69
+
70
+ /**
71
+ * A SharedMatrix holds a rectangular 2D array of values. Supported operations
72
+ * include setting values and inserting/removing rows and columns.
73
+ *
74
+ * Matrix values may be any Fluid serializable type, which is the set of JSON
75
+ * serializable types extended to include IFluidHandles.
76
+ *
77
+ * Fluid's SharedMatrix implementation works equally well for dense and sparse
78
+ * matrix data and physically stores data in Z-order to leverage CPU caches and
79
+ * prefetching when reading in either row or column major order. (See README.md
80
+ * for more details.)
81
+ * @alpha
82
+ */
83
+ export declare class SharedMatrix<T = any> extends SharedObject<ISharedMatrixEvents<T>> implements IMatrixProducer<MatrixItem<T>>, IMatrixReader<MatrixItem<T>>, IMatrixWriter<MatrixItem<T>> {
84
+ id: string;
85
+ private readonly consumers;
86
+ static getFactory(): SharedMatrixFactory;
87
+ private readonly rows;
88
+ private readonly cols;
89
+ private cells;
90
+ private readonly pending;
91
+ private cellLastWriteTracker;
92
+ private setCellLwwToFwwPolicySwitchOpSeqNumber;
93
+ private userSwitchedSetCellPolicy;
94
+ private reentrantCount;
95
+ /**
96
+ * Constructor for the Shared Matrix
97
+ * @param runtime - DataStore runtime.
98
+ * @param id - id of the dds
99
+ * @param attributes - channel attributes
100
+ * @param _isSetCellConflictResolutionPolicyFWW - Conflict resolution for Matrix set op is First Writer Win in case of
101
+ * race condition. Client can still overwrite values in case of no race.
102
+ */
103
+ constructor(runtime: IFluidDataStoreRuntime, id: string, attributes: IChannelAttributes, _isSetCellConflictResolutionPolicyFWW?: boolean);
104
+ private undo?;
105
+ /**
106
+ * Subscribes the given IUndoConsumer to the matrix.
107
+ */
108
+ openUndo(consumer: IUndoConsumer): void;
109
+ private get rowHandles();
110
+ private get colHandles();
111
+ /**
112
+ * {@inheritDoc @fluidframework/datastore-definitions#IChannelFactory.create}
113
+ */
114
+ static create<T>(runtime: IFluidDataStoreRuntime, id?: string): SharedMatrix<T>;
115
+ openMatrix(consumer: IMatrixConsumer<MatrixItem<T>>): IMatrixReader<MatrixItem<T>>;
116
+ closeMatrix(consumer: IMatrixConsumer<MatrixItem<T>>): void;
117
+ get rowCount(): number;
118
+ get colCount(): number;
119
+ isSetCellConflictResolutionPolicyFWW(): boolean;
120
+ getCell(row: number, col: number): MatrixItem<T>;
121
+ get matrixProducer(): IMatrixProducer<MatrixItem<T>>;
122
+ setCell(row: number, col: number, value: MatrixItem<T>): void;
123
+ setCells(rowStart: number, colStart: number, colCount: number, values: readonly MatrixItem<T>[]): void;
124
+ private setCellCore;
125
+ private sendSetCellOp;
126
+ /**
127
+ * This makes sure that the code inside the callback is not reentrant. We need to do that because we raise notifications
128
+ * to the consumers telling about these changes and they can try to change the matrix while listening to those notifications
129
+ * which can make the shared matrix to be in bad state. For example, we are raising notification for a setCell changes and
130
+ * a consumer tries to delete that row/col on receiving that notification which can lead to this matrix trying to setCell in
131
+ * a deleted row/col.
132
+ * @param callback - code that needs to protected against reentrancy.
133
+ */
134
+ private protectAgainstReentrancy;
135
+ private submitVectorMessage;
136
+ private submitColMessage;
137
+ insertCols(colStart: number, count: number): void;
138
+ removeCols(colStart: number, count: number): void;
139
+ private submitRowMessage;
140
+ insertRows(rowStart: number, count: number): void;
141
+ removeRows(rowStart: number, count: number): void;
142
+ /***/ _undoRemoveRows(rowStart: number, spec: IJSONSegment): void;
143
+ /***/ _undoRemoveCols(colStart: number, spec: IJSONSegment): void;
144
+ protected summarizeCore(serializer: IFluidSerializer): ISummaryTreeWithStats;
145
+ /**
146
+ * Runs serializer on the GC data for this SharedMatrix.
147
+ * All the IFluidHandle's stored in the cells represent routes to other objects.
148
+ */
149
+ protected processGCDataCore(serializer: SummarySerializer): void;
150
+ /**
151
+ * Advances the 'localSeq' counter for the cell data operation currently being queued.
152
+ *
153
+ * Do not use with 'submitColMessage()/submitRowMessage()' as these helpers + the MergeTree will
154
+ * automatically advance 'localSeq'.
155
+ */
156
+ private nextLocalSeq;
157
+ protected submitLocalMessage(message: any, localOpMetadata?: any): void;
158
+ protected didAttach(): void;
159
+ protected onConnect(): void;
160
+ private rebasePosition;
161
+ protected reSubmitCore(content: any, localOpMetadata: unknown): void;
162
+ protected onDisconnect(): void;
163
+ /**
164
+ * {@inheritDoc @fluidframework/shared-object-base#SharedObject.loadCore}
165
+ */
166
+ protected loadCore(storage: IChannelStorageService): Promise<void>;
167
+ /**
168
+ * Tells whether the setCell op should be applied or not based on First Write Win policy. It assumes
169
+ * we are in FWW mode.
170
+ */
171
+ private shouldSetCellBasedOnFWW;
172
+ protected processCore(rawMessage: ISequencedDocumentMessage, local: boolean, localOpMetadata: unknown): void;
173
+ private readonly onRowDelta;
174
+ private readonly onColDelta;
175
+ private readonly onRowHandlesRecycled;
176
+ private readonly onColHandlesRecycled;
177
+ /**
178
+ * Api to switch Set Op policy from Last Writer Win to First Writer Win. It only switches from LWW to FWW
179
+ * and not from FWW to LWW. The next SetOp which is sent will communicate this policy to other clients.
180
+ */
181
+ switchSetCellPolicy(): void;
182
+ /**
183
+ * Returns true if the latest pending write to the cell indicated by the given row/col handles
184
+ * matches the given 'localSeq'.
185
+ *
186
+ * A return value of `true` indicates that there are no later local operations queued that will
187
+ * clobber the write op at the given 'localSeq'. This includes later ops that overwrite the cell
188
+ * with a different value as well as row/col removals that might recycled the given row/col handles.
189
+ */
190
+ private isLatestPendingWrite;
191
+ toString(): string;
192
+ /**
193
+ * {@inheritDoc @fluidframework/shared-object-base#SharedObjectCore.applyStashedOp}
194
+ */
195
+ protected applyStashedOp(content: any): unknown;
196
+ }
197
+
198
+ /**
199
+ * {@link @fluidframework/datastore-definitions#IChannelFactory} for {@link SharedMatrix}.
200
+ * @alpha
201
+ */
202
+ export declare class SharedMatrixFactory implements IChannelFactory {
203
+ static Type: string;
204
+ static readonly Attributes: IChannelAttributes;
205
+ get type(): string;
206
+ get attributes(): IChannelAttributes;
207
+ /**
208
+ * {@inheritDoc @fluidframework/datastore-definitions#IChannelFactory.load}
209
+ */
210
+ load(runtime: IFluidDataStoreRuntime, id: string, services: IChannelServices, attributes: IChannelAttributes): Promise<IChannel>;
211
+ create(document: IFluidDataStoreRuntime, id: string): IChannel;
212
+ }
213
+
214
+ export { }
@@ -5,18 +5,19 @@
5
5
  */
6
6
  Object.defineProperty(exports, "__esModule", { value: true });
7
7
  exports.SharedMatrix = void 0;
8
+ /* eslint-disable import/no-deprecated */
8
9
  const core_utils_1 = require("@fluidframework/core-utils");
9
10
  const shared_object_base_1 = require("@fluidframework/shared-object-base");
10
11
  const runtime_utils_1 = require("@fluidframework/runtime-utils");
11
12
  const merge_tree_1 = require("@fluidframework/merge-tree");
12
- const ops_1 = require("./ops");
13
- const permutationvector_1 = require("./permutationvector");
14
- const sparsearray2d_1 = require("./sparsearray2d");
15
- const runtime_1 = require("./runtime");
16
- const handletable_1 = require("./handletable");
17
- const serialization_1 = require("./serialization");
18
- const range_1 = require("./range");
19
- const undoprovider_1 = require("./undoprovider");
13
+ const ops_1 = require("./ops.cjs");
14
+ const permutationvector_1 = require("./permutationvector.cjs");
15
+ const sparsearray2d_1 = require("./sparsearray2d.cjs");
16
+ const runtime_1 = require("./runtime.cjs");
17
+ const handletable_1 = require("./handletable.cjs");
18
+ const serialization_1 = require("./serialization.cjs");
19
+ const range_1 = require("./range.cjs");
20
+ const undoprovider_1 = require("./undoprovider.cjs");
20
21
  /**
21
22
  * A SharedMatrix holds a rectangular 2D array of values. Supported operations
22
23
  * include setting values and inserting/removing rows and columns.
@@ -28,19 +29,30 @@ const undoprovider_1 = require("./undoprovider");
28
29
  * matrix data and physically stores data in Z-order to leverage CPU caches and
29
30
  * prefetching when reading in either row or column major order. (See README.md
30
31
  * for more details.)
31
- *
32
- * @public
32
+ * @alpha
33
33
  */
34
34
  class SharedMatrix extends shared_object_base_1.SharedObject {
35
35
  static getFactory() {
36
36
  return new runtime_1.SharedMatrixFactory();
37
37
  }
38
- 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) {
39
47
  super(id, runtime, attributes, "fluid_matrix_");
40
48
  this.id = id;
41
49
  this.consumers = new Set();
42
50
  this.cells = new sparsearray2d_1.SparseArray2D(); // Stores cell values.
43
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;
44
56
  // Invoked by PermutationVector to notify IMatrixConsumers of row insertion/deletions.
45
57
  this.onRowDelta = (position, removedCount, insertedCount) => {
46
58
  for (const consumer of this.consumers) {
@@ -57,14 +69,18 @@ class SharedMatrix extends shared_object_base_1.SharedObject {
57
69
  for (const rowHandle of rowHandles) {
58
70
  this.cells.clearRows(/* rowStart: */ rowHandle, /* rowCount: */ 1);
59
71
  this.pending.clearRows(/* rowStart: */ rowHandle, /* rowCount: */ 1);
72
+ this.cellLastWriteTracker.clearRows(/* rowStart: */ rowHandle, /* rowCount: */ 1);
60
73
  }
61
74
  };
62
75
  this.onColHandlesRecycled = (colHandles) => {
63
76
  for (const colHandle of colHandles) {
64
77
  this.cells.clearCols(/* colStart: */ colHandle, /* colCount: */ 1);
65
78
  this.pending.clearCols(/* colStart: */ colHandle, /* colCount: */ 1);
79
+ this.cellLastWriteTracker.clearCols(/* colStart: */ colHandle, /* colCount: */ 1);
66
80
  }
67
81
  };
82
+ this.setCellLwwToFwwPolicySwitchOpSeqNumber =
83
+ _isSetCellConflictResolutionPolicyFWW === true ? 0 : -1;
68
84
  this.rows = new permutationvector_1.PermutationVector("rows" /* SnapshotPath.rows */, this.logger, runtime, this.onRowDelta, this.onRowHandlesRecycled);
69
85
  this.cols = new permutationvector_1.PermutationVector("cols" /* SnapshotPath.cols */, this.logger, runtime, this.onColDelta, this.onColHandlesRecycled);
70
86
  }
@@ -104,6 +120,9 @@ class SharedMatrix extends shared_object_base_1.SharedObject {
104
120
  get colCount() {
105
121
  return this.cols.getLength();
106
122
  }
123
+ isSetCellConflictResolutionPolicyFWW() {
124
+ return this.setCellLwwToFwwPolicySwitchOpSeqNumber > -1 || this.userSwitchedSetCellPolicy;
125
+ }
107
126
  getCell(row, col) {
108
127
  // Perf: When possible, bounds checking is performed inside the implementation for
109
128
  // 'getHandle()' so that it can be elided in the case of a cache hit. This
@@ -130,10 +149,6 @@ class SharedMatrix extends shared_object_base_1.SharedObject {
130
149
  setCell(row, col, value) {
131
150
  (0, core_utils_1.assert)(0 <= row && row < this.rowCount && 0 <= col && col < this.colCount, 0x01a /* "Trying to set out-of-bounds cell!" */);
132
151
  this.setCellCore(row, col, value);
133
- // Avoid reentrancy by raising change notifications after the op is queued.
134
- for (const consumer of this.consumers.values()) {
135
- consumer.cellsChanged(row, col, 1, 1, this);
136
- }
137
152
  }
138
153
  setCells(rowStart, colStart, colCount, values) {
139
154
  const rowCount = Math.ceil(values.length / colCount);
@@ -154,23 +169,25 @@ class SharedMatrix extends shared_object_base_1.SharedObject {
154
169
  r++;
155
170
  }
156
171
  }
157
- // Avoid reentrancy by raising change notifications after the op is queued.
158
- for (const consumer of this.consumers.values()) {
159
- consumer.cellsChanged(rowStart, colStart, rowCount, colCount, this);
160
- }
161
172
  }
162
173
  setCellCore(row, col, value, rowHandle = this.rows.getAllocatedHandle(row), colHandle = this.cols.getAllocatedHandle(col)) {
163
- if (this.undo !== undefined) {
164
- let oldValue = this.cells.getCell(rowHandle, colHandle);
165
- if (oldValue === null) {
166
- 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);
167
181
  }
168
- this.undo.cellSet(rowHandle, colHandle, oldValue);
169
- }
170
- this.cells.setCell(rowHandle, colHandle, value);
171
- if (this.isAttached()) {
172
- this.sendSetCellOp(row, col, value, rowHandle, colHandle);
173
- }
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
+ });
174
191
  }
175
192
  sendSetCellOp(row, col, value, rowHandle, colHandle, localSeq = this.nextLocalSeq(), rowsRefSeq = this.rows.getCollabWindow().currentSeq, colsRefSeq = this.cols.getCollabWindow().currentSeq) {
176
193
  (0, core_utils_1.assert)(this.isAttached(), 0x1e2 /* "Caller must ensure 'isAttached()' before calling 'sendSetCellOp'." */);
@@ -179,6 +196,7 @@ class SharedMatrix extends shared_object_base_1.SharedObject {
179
196
  row,
180
197
  col,
181
198
  value,
199
+ fwwMode: this.userSwitchedSetCellPolicy || this.setCellLwwToFwwPolicySwitchOpSeqNumber > -1,
182
200
  };
183
201
  const metadata = {
184
202
  rowHandle,
@@ -186,10 +204,26 @@ class SharedMatrix extends shared_object_base_1.SharedObject {
186
204
  localSeq,
187
205
  rowsRefSeq,
188
206
  colsRefSeq,
207
+ referenceSeqNumber: this.runtime.deltaManager.lastSequenceNumber,
189
208
  };
190
209
  this.submitLocalMessage(op, metadata);
191
210
  this.pending.setCell(rowHandle, colHandle, localSeq);
192
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
+ }
193
227
  submitVectorMessage(currentVector, oppositeVector, dimension, message) {
194
228
  // Ideally, we would have a single 'localSeq' counter that is shared between both PermutationVectors
195
229
  // and the SharedMatrix's cell data. Instead, we externally advance each MergeTree's 'localSeq' counter
@@ -212,21 +246,21 @@ class SharedMatrix extends shared_object_base_1.SharedObject {
212
246
  this.submitVectorMessage(this.cols, this.rows, "cols" /* SnapshotPath.cols */, message);
213
247
  }
214
248
  insertCols(colStart, count) {
215
- this.submitColMessage(this.cols.insert(colStart, count));
249
+ this.protectAgainstReentrancy(() => this.submitColMessage(this.cols.insert(colStart, count)));
216
250
  }
217
251
  removeCols(colStart, count) {
218
- this.submitColMessage(this.cols.remove(colStart, count));
252
+ this.protectAgainstReentrancy(() => this.submitColMessage(this.cols.remove(colStart, count)));
219
253
  }
220
254
  submitRowMessage(message) {
221
255
  this.submitVectorMessage(this.rows, this.cols, "rows" /* SnapshotPath.rows */, message);
222
256
  }
223
257
  insertRows(rowStart, count) {
224
- this.submitRowMessage(this.rows.insert(rowStart, count));
258
+ this.protectAgainstReentrancy(() => this.submitRowMessage(this.rows.insert(rowStart, count)));
225
259
  }
226
260
  removeRows(rowStart, count) {
227
- this.submitRowMessage(this.rows.remove(rowStart, count));
261
+ this.protectAgainstReentrancy(() => this.submitRowMessage(this.rows.remove(rowStart, count)));
228
262
  }
229
- /** @internal */ _undoRemoveRows(rowStart, spec) {
263
+ /***/ _undoRemoveRows(rowStart, spec) {
230
264
  const { op, inserted } = (0, permutationvector_1.reinsertSegmentIntoVector)(this.rows, rowStart, spec);
231
265
  this.submitRowMessage(op);
232
266
  // Generate setCell ops for each populated cell in the reinserted rows.
@@ -246,7 +280,7 @@ class SharedMatrix extends shared_object_base_1.SharedObject {
246
280
  consumer.cellsChanged(rowStart, /* colStart: */ 0, rowCount, this.colCount, this);
247
281
  }
248
282
  }
249
- /** @internal */ _undoRemoveCols(colStart, spec) {
283
+ /***/ _undoRemoveCols(colStart, spec) {
250
284
  const { op, inserted } = (0, permutationvector_1.reinsertSegmentIntoVector)(this.cols, colStart, spec);
251
285
  this.submitColMessage(op);
252
286
  // Generate setCell ops for each populated cell in the reinserted cols.
@@ -270,7 +304,16 @@ class SharedMatrix extends shared_object_base_1.SharedObject {
270
304
  const builder = new runtime_utils_1.SummaryTreeBuilder();
271
305
  builder.addWithStats("rows" /* SnapshotPath.rows */, this.rows.summarize(this.runtime, this.handle, serializer));
272
306
  builder.addWithStats("cols" /* SnapshotPath.cols */, this.cols.summarize(this.runtime, this.handle, serializer));
273
- 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));
274
317
  return builder.getSummaryTree();
275
318
  }
276
319
  /**
@@ -339,16 +382,27 @@ class SharedMatrix extends shared_object_base_1.SharedObject {
339
382
  default: {
340
383
  (0, core_utils_1.assert)(content.type === ops_1.MatrixOp.set, 0x020 /* "Unknown SharedMatrix 'op' type." */);
341
384
  const setOp = content;
342
- const { rowHandle, colHandle, localSeq, rowsRefSeq, colsRefSeq } = localOpMetadata;
343
- // If there are more pending local writes to the same row/col handle, it is important
344
- // to skip resubmitting this op since it is possible the row/col handle has been recycled
345
- // and now refers to a different position than when this op was originally submitted.
346
- if (this.isLatestPendingWrite(rowHandle, colHandle, localSeq)) {
347
- const row = this.rebasePosition(this.rows, setOp.row, rowsRefSeq, localSeq);
348
- const col = this.rebasePosition(this.cols, setOp.col, colsRefSeq, localSeq);
349
- 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) {
350
400
  this.sendSetCellOp(row, col, setOp.value, rowHandle, colHandle, localSeq, rowsRefSeq, colsRefSeq);
351
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
+ }
352
406
  }
353
407
  break;
354
408
  }
@@ -362,14 +416,32 @@ class SharedMatrix extends shared_object_base_1.SharedObject {
362
416
  try {
363
417
  await this.rows.load(this.runtime, new runtime_utils_1.ObjectStoragePartition(storage, "rows" /* SnapshotPath.rows */), this.serializer);
364
418
  await this.cols.load(this.runtime, new runtime_utils_1.ObjectStoragePartition(storage, "cols" /* SnapshotPath.cols */), this.serializer);
365
- 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);
366
420
  this.cells = sparsearray2d_1.SparseArray2D.load(cellData);
367
- 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
+ }
368
426
  }
369
427
  catch (error) {
370
428
  this.logger.sendErrorEvent({ eventName: "MatrixLoadFailed" }, error);
371
429
  }
372
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
+ }
373
445
  processCore(rawMessage, local, localOpMetadata) {
374
446
  const msg = (0, shared_object_base_1.parseHandles)(rawMessage, this.serializer);
375
447
  const contents = msg.contents;
@@ -382,13 +454,28 @@ class SharedMatrix extends shared_object_base_1.SharedObject {
382
454
  break;
383
455
  default: {
384
456
  (0, core_utils_1.assert)(contents.type === ops_1.MatrixOp.set, 0x021 /* "SharedMatrix message contents have unexpected type!" */);
385
- 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!! */);
386
464
  if (local) {
387
465
  // We are receiving the ACK for a local pending set operation.
388
466
  const { rowHandle, colHandle, localSeq } = localOpMetadata;
389
- // If this is the most recent write to the cell by the local client, remove our
390
- // entry from 'pendingCliSeqs' to resume allowing remote writes.
391
- 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) {
392
479
  this.pending.setCell(rowHandle, colHandle, undefined);
393
480
  }
394
481
  }
@@ -400,11 +487,39 @@ class SharedMatrix extends shared_object_base_1.SharedObject {
400
487
  const rowHandle = this.rows.getAllocatedHandle(adjustedRow);
401
488
  const colHandle = this.cols.getAllocatedHandle(adjustedCol);
402
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!" */);
403
- // If there is a pending (unACKed) local write to the same cell, skip the current op
404
- // since it "happened before" the pending write.
405
- if (this.pending.getCell(rowHandle, colHandle) === undefined) {
406
- 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.
407
518
  this.cells.setCell(rowHandle, colHandle, value);
519
+ this.cellLastWriteTracker.setCell(rowHandle, colHandle, {
520
+ seqNum: rawMessage.sequenceNumber,
521
+ clientId: rawMessage.clientId,
522
+ });
408
523
  for (const consumer of this.consumers.values()) {
409
524
  consumer.cellsChanged(adjustedRow, adjustedCol, 1, 1, this);
410
525
  }
@@ -415,6 +530,20 @@ class SharedMatrix extends shared_object_base_1.SharedObject {
415
530
  }
416
531
  }
417
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
+ }
418
547
  /**
419
548
  * Returns true if the latest pending write to the cell indicated by the given row/col handles
420
549
  * matches the given 'localSeq'.
@@ -487,6 +616,7 @@ class SharedMatrix extends shared_object_base_1.SharedObject {
487
616
  localSeq,
488
617
  rowsRefSeq,
489
618
  colsRefSeq,
619
+ referenceSeqNumber: this.runtime.deltaManager.lastSequenceNumber,
490
620
  };
491
621
  this.pending.setCell(rowHandle, colHandle, localSeq);
492
622
  return metadata;
@@ -494,4 +624,4 @@ class SharedMatrix extends shared_object_base_1.SharedObject {
494
624
  }
495
625
  }
496
626
  exports.SharedMatrix = SharedMatrix;
497
- //# sourceMappingURL=matrix.js.map
627
+ //# sourceMappingURL=matrix.cjs.map