@fluid-experimental/tree 0.59.3003 → 0.59.4000-71128
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/dist/Forest.js +1 -1
- package/dist/Forest.js.map +1 -1
- package/dist/SharedTree.d.ts +89 -30
- package/dist/SharedTree.d.ts.map +1 -1
- package/dist/SharedTree.js +93 -58
- package/dist/SharedTree.js.map +1 -1
- package/dist/SharedTreeEncoder.d.ts +1 -1
- package/dist/SharedTreeEncoder.d.ts.map +1 -1
- package/dist/SharedTreeEncoder.js.map +1 -1
- package/dist/id-compressor/IdCompressor.d.ts +19 -45
- package/dist/id-compressor/IdCompressor.d.ts.map +1 -1
- package/dist/id-compressor/IdCompressor.js +151 -151
- package/dist/id-compressor/IdCompressor.js.map +1 -1
- package/dist/id-compressor/SessionIdNormalizer.d.ts +1 -16
- package/dist/id-compressor/SessionIdNormalizer.d.ts.map +1 -1
- package/dist/id-compressor/SessionIdNormalizer.js +23 -21
- package/dist/id-compressor/SessionIdNormalizer.js.map +1 -1
- package/dist/id-compressor/persisted-types/0.0.1.d.ts +23 -4
- package/dist/id-compressor/persisted-types/0.0.1.d.ts.map +1 -1
- package/dist/id-compressor/persisted-types/0.0.1.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js.map +1 -1
- package/lib/Forest.js +1 -1
- package/lib/Forest.js.map +1 -1
- package/lib/SharedTree.d.ts +89 -30
- package/lib/SharedTree.d.ts.map +1 -1
- package/lib/SharedTree.js +94 -59
- package/lib/SharedTree.js.map +1 -1
- package/lib/SharedTreeEncoder.d.ts +1 -1
- package/lib/SharedTreeEncoder.d.ts.map +1 -1
- package/lib/SharedTreeEncoder.js.map +1 -1
- package/lib/id-compressor/IdCompressor.d.ts +19 -45
- package/lib/id-compressor/IdCompressor.d.ts.map +1 -1
- package/lib/id-compressor/IdCompressor.js +152 -152
- package/lib/id-compressor/IdCompressor.js.map +1 -1
- package/lib/id-compressor/SessionIdNormalizer.d.ts +1 -16
- package/lib/id-compressor/SessionIdNormalizer.d.ts.map +1 -1
- package/lib/id-compressor/SessionIdNormalizer.js +23 -21
- package/lib/id-compressor/SessionIdNormalizer.js.map +1 -1
- package/lib/id-compressor/persisted-types/0.0.1.d.ts +23 -4
- package/lib/id-compressor/persisted-types/0.0.1.d.ts.map +1 -1
- package/lib/id-compressor/persisted-types/0.0.1.js.map +1 -1
- package/lib/index.d.ts +1 -1
- package/lib/index.d.ts.map +1 -1
- package/lib/index.js.map +1 -1
- package/lib/test/IdCompressor.perf.tests.js +47 -67
- package/lib/test/IdCompressor.perf.tests.js.map +1 -1
- package/lib/test/IdCompressor.tests.js +196 -101
- package/lib/test/IdCompressor.tests.js.map +1 -1
- package/lib/test/Summary.tests.d.ts +0 -1
- package/lib/test/Summary.tests.d.ts.map +1 -1
- package/lib/test/Summary.tests.js +0 -3
- package/lib/test/Summary.tests.js.map +1 -1
- package/lib/test/Virtualization.tests.js +0 -4
- package/lib/test/Virtualization.tests.js.map +1 -1
- package/lib/test/fuzz/SharedTreeFuzzTests.d.ts.map +1 -1
- package/lib/test/fuzz/SharedTreeFuzzTests.js +3 -1
- package/lib/test/fuzz/SharedTreeFuzzTests.js.map +1 -1
- package/lib/test/utilities/IdCompressorTestUtilities.d.ts +14 -7
- package/lib/test/utilities/IdCompressorTestUtilities.d.ts.map +1 -1
- package/lib/test/utilities/IdCompressorTestUtilities.js +40 -20
- package/lib/test/utilities/IdCompressorTestUtilities.js.map +1 -1
- package/lib/test/utilities/SharedTreeTests.d.ts.map +1 -1
- package/lib/test/utilities/SharedTreeTests.js +27 -51
- package/lib/test/utilities/SharedTreeTests.js.map +1 -1
- package/lib/test/utilities/SharedTreeVersioningTests.d.ts.map +1 -1
- package/lib/test/utilities/SharedTreeVersioningTests.js +19 -19
- package/lib/test/utilities/SharedTreeVersioningTests.js.map +1 -1
- package/lib/test/utilities/SummaryLoadPerfTests.js +1 -1
- package/lib/test/utilities/SummaryLoadPerfTests.js.map +1 -1
- package/lib/test/utilities/TestCommon.d.ts +4 -0
- package/lib/test/utilities/TestCommon.d.ts.map +1 -1
- package/lib/test/utilities/TestCommon.js +6 -0
- package/lib/test/utilities/TestCommon.js.map +1 -1
- package/lib/test/utilities/TestUtilities.d.ts.map +1 -1
- package/lib/test/utilities/TestUtilities.js +4 -6
- package/lib/test/utilities/TestUtilities.js.map +1 -1
- package/package.json +24 -19
- package/src/Forest.ts +1 -1
- package/src/SharedTree.ts +195 -46
- package/src/SharedTreeEncoder.ts +1 -1
- package/src/id-compressor/IdCompressor.ts +171 -198
- package/src/id-compressor/SessionIdNormalizer.ts +29 -41
- package/src/id-compressor/persisted-types/0.0.1.ts +25 -4
- package/src/index.ts +4 -0
|
@@ -6,9 +6,10 @@
|
|
|
6
6
|
import BTree from 'sorted-btree';
|
|
7
7
|
import { assert, hasLength, assertNotUndefined, compareFiniteNumbers, compareFiniteNumbersReversed, compareMaps, compareStrings, fail, getOrCreate, setPropertyIfDefined, } from '../Common';
|
|
8
8
|
import { assertIsStableId, assertIsUuidString, isStableId } from '../UuidUtilities';
|
|
9
|
-
import {
|
|
9
|
+
import { AppendOnlySortedMap } from './AppendOnlySortedMap';
|
|
10
10
|
import { getIds } from './IdRange';
|
|
11
11
|
import { numericUuidEquals, getPositiveDelta, incrementUuid, numericUuidFromStableId, stableIdFromNumericUuid, ensureSessionUuid, } from './NumericUuid';
|
|
12
|
+
import { SessionIdNormalizer } from './SessionIdNormalizer';
|
|
12
13
|
/**
|
|
13
14
|
* Roughly equates to a minimum of 1M sessions before we start allocating 64 bit IDs.
|
|
14
15
|
* This value must *NOT* change without careful consideration to compatibility.
|
|
@@ -136,11 +137,21 @@ export class IdCompressor {
|
|
|
136
137
|
*/
|
|
137
138
|
this.localOverrides = new AppendOnlySortedMap(compareFiniteNumbersReversed);
|
|
138
139
|
/**
|
|
139
|
-
* Maps local IDs to the
|
|
140
|
-
*
|
|
141
|
-
*
|
|
140
|
+
* Maps local IDs to the final ID they are associated with (if any), and maps final IDs to the corresponding local ID (if any).
|
|
141
|
+
* This is used to efficiently compute normalization. This map can be thought of as mapping ranges of "optimistic uncertainty"
|
|
142
|
+
* (local IDs) to the result of consensus (reserved ranges of final IDs, a.k.a. clusters). Any given range of local IDs
|
|
143
|
+
* does not necessarily span an entire cluster, as some session-space IDs may be allocated *after* a cluster has been allocated
|
|
144
|
+
* but before it is full. In this case, there is no uncertainty, as the range of final IDs was reserved when the cluster was created.
|
|
145
|
+
* However, there is always a range of local IDs with size \>= 1 associated with the beginning of every cluster, as clusters are only
|
|
146
|
+
* created *after* they are needed and thus there is some period of uncertainty after local IDs have been handed out but before the
|
|
147
|
+
* range containing them has been finalized. There may also be ranges of local IDs that do not start at the beginning of a
|
|
148
|
+
* cluster; this happens when a cluster is expanded instead of allocating a new one.
|
|
149
|
+
* Additionally, session space IDs associated with an override string will also always be local IDs, because there is uncertainty as
|
|
150
|
+
* to whether another client simultaneously allocated the same override and could get sequenced first (a.k.a. unification) and its
|
|
151
|
+
* final ID would be associated with that override.
|
|
152
|
+
* See `SessionIdNormalizer` for more.
|
|
142
153
|
*/
|
|
143
|
-
this.
|
|
154
|
+
this.sessionIdNormalizer = new SessionIdNormalizer();
|
|
144
155
|
/**
|
|
145
156
|
* Contains entries for cluster base UUIDs and override strings (both local and final).
|
|
146
157
|
* As a performance optimization, entries for finalized strings also include the containing cluster object.
|
|
@@ -196,12 +207,6 @@ export class IdCompressor {
|
|
|
196
207
|
get attributionId() {
|
|
197
208
|
return this.localSession.attributionId;
|
|
198
209
|
}
|
|
199
|
-
/**
|
|
200
|
-
* Helper comparator for searching append-only sorted maps.
|
|
201
|
-
*/
|
|
202
|
-
static overrideComparator(search, element) {
|
|
203
|
-
return compareFiniteNumbers(search, element[0]);
|
|
204
|
-
}
|
|
205
210
|
/**
|
|
206
211
|
* Creates a session object for the supplied ID.
|
|
207
212
|
* Must only be called once per ID.
|
|
@@ -239,77 +244,28 @@ export class IdCompressor {
|
|
|
239
244
|
/**
|
|
240
245
|
* Returns an iterable of all IDs created by this compressor.
|
|
241
246
|
*/
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
for (let i = 1; i <= this.localIdCount; i++) {
|
|
245
|
-
yield -i;
|
|
246
|
-
}
|
|
247
|
+
getAllIdsFromLocalSession() {
|
|
248
|
+
return this.sessionIdNormalizer[Symbol.iterator]();
|
|
247
249
|
}
|
|
248
250
|
/**
|
|
249
251
|
* Returns the attribution ID associated with the compressor that created the ID
|
|
250
252
|
*/
|
|
251
253
|
attributeId(id) {
|
|
252
|
-
var _a;
|
|
253
254
|
const opSpaceNormalizedId = this.normalizeToOpSpace(id);
|
|
254
255
|
if (isLocalId(opSpaceNormalizedId)) {
|
|
255
256
|
return this.attributionId;
|
|
256
257
|
}
|
|
257
|
-
const
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
getIdsFromRange(rangeDescriptor, sessionId) {
|
|
265
|
-
var _a, _b, _c;
|
|
266
|
-
const { first, count } = rangeDescriptor;
|
|
267
|
-
if (sessionId === this.localSessionId) {
|
|
268
|
-
return {
|
|
269
|
-
length: count,
|
|
270
|
-
get: (index) => {
|
|
271
|
-
if (index < 0 || index >= count) {
|
|
272
|
-
fail('Index out of bounds of range.');
|
|
273
|
-
}
|
|
274
|
-
return (first - index);
|
|
275
|
-
},
|
|
276
|
-
};
|
|
277
|
-
}
|
|
278
|
-
else {
|
|
279
|
-
const session = (_a = this.sessions.get(sessionId)) !== null && _a !== void 0 ? _a : fail('Unknown session, range may not be finalized.');
|
|
280
|
-
const firstNumericUuid = incrementUuid(session.sessionUuid, -first - 1);
|
|
281
|
-
const firstFinal = (_b = this.compressNumericUuid(firstNumericUuid)) !== null && _b !== void 0 ? _b : fail('Remote range must be finalized before getting IDs.');
|
|
282
|
-
assert(isFinalId(firstFinal), 'ID from a remote session ID must have final form, as overrides are impossible by definition.');
|
|
283
|
-
const [baseFinalId, cluster] = (_c = this.getClusterForFinalId(firstFinal)) !== null && _c !== void 0 ? _c : fail();
|
|
284
|
-
const numIdsRemainingInFirstCluster = cluster.capacity - (firstFinal - baseFinalId);
|
|
285
|
-
let pivotFinal;
|
|
286
|
-
if (count > numIdsRemainingInFirstCluster) {
|
|
287
|
-
const compressedPivot = this.compressNumericUuid(incrementUuid(firstNumericUuid, numIdsRemainingInFirstCluster));
|
|
288
|
-
// Looking up the actual cluster can be avoided, as it is guaranteed that at most one new cluster will be
|
|
289
|
-
// created when finalizing a range (regardless of size) due to the expansion optimization.
|
|
290
|
-
if (compressedPivot === undefined || isLocalId(compressedPivot)) {
|
|
291
|
-
fail('ID from a remote session ID must have final form, as overrides are impossible by definition.');
|
|
292
|
-
}
|
|
293
|
-
else {
|
|
294
|
-
pivotFinal = compressedPivot;
|
|
295
|
-
}
|
|
258
|
+
const closestCluster = this.getClusterForFinalId(opSpaceNormalizedId);
|
|
259
|
+
if (closestCluster === undefined) {
|
|
260
|
+
if (this.sessionIdNormalizer.getCreationIndex(opSpaceNormalizedId) !== undefined) {
|
|
261
|
+
return this.attributionId;
|
|
262
|
+
}
|
|
263
|
+
else {
|
|
264
|
+
fail('Cluster does not exist for final ID');
|
|
296
265
|
}
|
|
297
|
-
return {
|
|
298
|
-
length: count,
|
|
299
|
-
get: (index) => {
|
|
300
|
-
if (index < 0 || index >= count) {
|
|
301
|
-
fail('Index out of bounds of range.');
|
|
302
|
-
}
|
|
303
|
-
if (index < numIdsRemainingInFirstCluster) {
|
|
304
|
-
return (firstFinal + index);
|
|
305
|
-
}
|
|
306
|
-
else {
|
|
307
|
-
return ((pivotFinal !== null && pivotFinal !== void 0 ? pivotFinal : fail('Pivot must exist if range spans clusters.')) +
|
|
308
|
-
(index - numIdsRemainingInFirstCluster));
|
|
309
|
-
}
|
|
310
|
-
},
|
|
311
|
-
};
|
|
312
266
|
}
|
|
267
|
+
const [_, cluster] = closestCluster;
|
|
268
|
+
return cluster.session.attributionId;
|
|
313
269
|
}
|
|
314
270
|
/**
|
|
315
271
|
* Returns a range of local IDs created by this session in a format for sending to the server for finalizing.
|
|
@@ -365,7 +321,7 @@ export class IdCompressor {
|
|
|
365
321
|
* @param range - the range of session-local IDs to finalize.
|
|
366
322
|
*/
|
|
367
323
|
finalizeCreationRange(range) {
|
|
368
|
-
var _a, _b, _c;
|
|
324
|
+
var _a, _b, _c, _d;
|
|
369
325
|
const { sessionId, attributionId } = range;
|
|
370
326
|
const isLocal = sessionId === this.localSessionId;
|
|
371
327
|
const session = (_a = this.sessions.get(sessionId)) !== null && _a !== void 0 ? _a : this.createSession(sessionId, attributionId);
|
|
@@ -379,16 +335,26 @@ export class IdCompressor {
|
|
|
379
335
|
cluster: undefined,
|
|
380
336
|
clusterBase: undefined,
|
|
381
337
|
};
|
|
382
|
-
const
|
|
383
|
-
const
|
|
384
|
-
|
|
338
|
+
const currentClusterExists = currentCluster !== undefined && currentBaseFinalId !== undefined;
|
|
339
|
+
const normalizedLastFinalizedLocal = (_b = session.lastFinalizedLocalId) !== null && _b !== void 0 ? _b : 0;
|
|
340
|
+
const { first: newFirstFinalizedLocal, last: newLastFinalizedLocal } = ids;
|
|
341
|
+
assert(newFirstFinalizedLocal === normalizedLastFinalizedLocal - 1, 'Ranges finalized out of order.');
|
|
385
342
|
// The total number of session-local IDs to finalize
|
|
386
|
-
const finalizeCount =
|
|
343
|
+
const finalizeCount = normalizedLastFinalizedLocal - newLastFinalizedLocal;
|
|
387
344
|
assert(finalizeCount >= 1, 'Cannot finalize an empty range.');
|
|
388
345
|
let initialClusterCount = 0;
|
|
389
346
|
let remainingCount = finalizeCount;
|
|
390
347
|
let newBaseUuid;
|
|
391
|
-
if (
|
|
348
|
+
if (currentClusterExists) {
|
|
349
|
+
if (isLocal) {
|
|
350
|
+
const lastKnownFinal = (_c = this.sessionIdNormalizer.getLastFinalId()) !== null && _c !== void 0 ? _c : fail('Cluster exists but normalizer does not have an entry for it.');
|
|
351
|
+
const lastFinalInCluster = (currentBaseFinalId +
|
|
352
|
+
Math.min(currentCluster.count + finalizeCount, currentCluster.capacity) -
|
|
353
|
+
1);
|
|
354
|
+
if (lastFinalInCluster > lastKnownFinal) {
|
|
355
|
+
this.sessionIdNormalizer.addFinalIds((lastKnownFinal + 1), lastFinalInCluster, currentCluster);
|
|
356
|
+
}
|
|
357
|
+
}
|
|
392
358
|
initialClusterCount = currentCluster.count;
|
|
393
359
|
const remainingCapacity = currentCluster.capacity - initialClusterCount;
|
|
394
360
|
const overflow = remainingCount - remainingCapacity;
|
|
@@ -405,6 +371,21 @@ export class IdCompressor {
|
|
|
405
371
|
this.nextClusterBaseFinalId = (this.nextClusterBaseFinalId + expansionAmount);
|
|
406
372
|
assert(this.nextClusterBaseFinalId < Number.MAX_SAFE_INTEGER, 'The number of allocated final IDs must not exceed the JS maximum safe integer.');
|
|
407
373
|
this.checkClusterForCollision(currentCluster);
|
|
374
|
+
if (isLocal) {
|
|
375
|
+
// Example with cluster size of 3:
|
|
376
|
+
// Ids generated so far: -1 1 2 -4 -5 <-- note positive numbers are eager finals
|
|
377
|
+
// Cluster: [ 0 1 2 ]
|
|
378
|
+
// ~ finalizing happens, causing expansion ~
|
|
379
|
+
// Cluster: [ 0 1 2 3 4 5 ]
|
|
380
|
+
// corresponding locals: -1 -4
|
|
381
|
+
// lastFinalizedLocalId^ ^newLastFinalizedLocalId = -6
|
|
382
|
+
// overflow = 2: ----
|
|
383
|
+
// localIdPivot^
|
|
384
|
+
// lastFinalizedFinal^
|
|
385
|
+
const lastFinalizedFinal = (currentBaseFinalId + currentCluster.count - 1);
|
|
386
|
+
const finalPivot = (lastFinalizedFinal - overflow + 1);
|
|
387
|
+
this.sessionIdNormalizer.addFinalIds(finalPivot, lastFinalizedFinal, currentCluster);
|
|
388
|
+
}
|
|
408
389
|
}
|
|
409
390
|
}
|
|
410
391
|
else {
|
|
@@ -445,9 +426,10 @@ export class IdCompressor {
|
|
|
445
426
|
session,
|
|
446
427
|
};
|
|
447
428
|
const usedCapacity = finalizeCount - remainingCount;
|
|
448
|
-
localIdPivot = (
|
|
429
|
+
localIdPivot = (newFirstFinalizedLocal - usedCapacity);
|
|
449
430
|
if (isLocal) {
|
|
450
|
-
|
|
431
|
+
const lastFinalizedFinal = (newBaseFinalId + newCluster.count - 1);
|
|
432
|
+
this.sessionIdNormalizer.addFinalIds(newBaseFinalId, lastFinalizedFinal, newCluster);
|
|
451
433
|
}
|
|
452
434
|
this.checkClusterForCollision(newCluster);
|
|
453
435
|
this.clustersAndOverridesInversion.set(stableIdFromNumericUuid(newCluster.baseUuid), {
|
|
@@ -466,8 +448,8 @@ export class IdCompressor {
|
|
|
466
448
|
const [overriddenLocal, override] = overrides[i];
|
|
467
449
|
// Note: recall that local IDs are negative
|
|
468
450
|
assert(i === 0 || overriddenLocal < overrides[i - 1][0], 'Override IDs must be in sorted order.');
|
|
469
|
-
assert(overriddenLocal <
|
|
470
|
-
assert(overriddenLocal >=
|
|
451
|
+
assert(overriddenLocal < normalizedLastFinalizedLocal, 'Ranges finalized out of order.');
|
|
452
|
+
assert(overriddenLocal >= newLastFinalizedLocal, 'Malformed range: override ID ahead of range start.');
|
|
471
453
|
let cluster;
|
|
472
454
|
let overriddenFinal;
|
|
473
455
|
if (localIdPivot !== undefined && overriddenLocal <= localIdPivot) {
|
|
@@ -482,10 +464,10 @@ export class IdCompressor {
|
|
|
482
464
|
cluster = currentCluster;
|
|
483
465
|
overriddenFinal = (currentBaseFinalId +
|
|
484
466
|
initialClusterCount +
|
|
485
|
-
(
|
|
467
|
+
(normalizedLastFinalizedLocal - overriddenLocal) -
|
|
486
468
|
1);
|
|
487
469
|
}
|
|
488
|
-
(
|
|
470
|
+
(_d = cluster.overrides) !== null && _d !== void 0 ? _d : (cluster.overrides = new Map());
|
|
489
471
|
const inversionKey = IdCompressor.createInversionKey(override);
|
|
490
472
|
const existingIds = this.getExistingIdsForNewOverride(inversionKey, true);
|
|
491
473
|
let overrideForCluster;
|
|
@@ -551,7 +533,7 @@ export class IdCompressor {
|
|
|
551
533
|
}
|
|
552
534
|
}
|
|
553
535
|
}
|
|
554
|
-
session.lastFinalizedLocalId =
|
|
536
|
+
session.lastFinalizedLocalId = newLastFinalizedLocal;
|
|
555
537
|
}
|
|
556
538
|
checkClusterForCollision(cluster) {
|
|
557
539
|
const maxClusterUuid = incrementUuid(cluster.baseUuid, cluster.capacity - 1);
|
|
@@ -631,9 +613,9 @@ export class IdCompressor {
|
|
|
631
613
|
}
|
|
632
614
|
const override = (_a = numericOverride !== null && numericOverride !== void 0 ? numericOverride : stableOverride) !== null && _a !== void 0 ? _a : (IdCompressor.isStableInversionKey(inversionKey) ? inversionKey : undefined);
|
|
633
615
|
if (override !== undefined) {
|
|
634
|
-
const
|
|
635
|
-
if (
|
|
636
|
-
return
|
|
616
|
+
const sessionSpaceId = this.getCompressedIdForStableId(override);
|
|
617
|
+
if (sessionSpaceId !== undefined) {
|
|
618
|
+
return sessionSpaceId;
|
|
637
619
|
}
|
|
638
620
|
}
|
|
639
621
|
return undefined;
|
|
@@ -675,41 +657,50 @@ export class IdCompressor {
|
|
|
675
657
|
* @returns an existing ID if one already exists for `override`, and a new local ID otherwise. The returned ID is in session space.
|
|
676
658
|
*/
|
|
677
659
|
generateCompressedId(override) {
|
|
678
|
-
|
|
660
|
+
let overrideInversionKey;
|
|
679
661
|
if (override !== undefined) {
|
|
680
|
-
|
|
681
|
-
const existingIds = this.getExistingIdsForNewOverride(
|
|
662
|
+
overrideInversionKey = IdCompressor.createInversionKey(override);
|
|
663
|
+
const existingIds = this.getExistingIdsForNewOverride(overrideInversionKey, false);
|
|
682
664
|
if (existingIds !== undefined) {
|
|
683
665
|
return typeof existingIds === 'number' ? existingIds : existingIds[0];
|
|
684
666
|
}
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
667
|
+
}
|
|
668
|
+
// Bump local counter regardless, then attempt to optimistically return a final ID.
|
|
669
|
+
// If the local session has reserved a cluster range via consensus, it is safe to hand out final IDs prior to
|
|
670
|
+
// finalizing the range that includes these locals.
|
|
671
|
+
const newLocalId = -++this.localIdCount;
|
|
672
|
+
const { currentClusterDetails } = this.localSession;
|
|
673
|
+
const { sessionIdNormalizer } = this;
|
|
674
|
+
let eagerFinalId;
|
|
675
|
+
let cluster;
|
|
676
|
+
if (currentClusterDetails !== undefined) {
|
|
677
|
+
const { clusterBase } = currentClusterDetails;
|
|
678
|
+
cluster = currentClusterDetails.cluster;
|
|
679
|
+
const lastFinalKnown = sessionIdNormalizer.getLastFinalId();
|
|
680
|
+
if (lastFinalKnown !== undefined && lastFinalKnown - clusterBase + 1 < cluster.capacity) {
|
|
681
|
+
eagerFinalId = (lastFinalKnown + 1);
|
|
692
682
|
}
|
|
693
683
|
}
|
|
684
|
+
if (overrideInversionKey !== undefined) {
|
|
685
|
+
const registeredLocal = sessionIdNormalizer.addLocalId();
|
|
686
|
+
assert(registeredLocal === newLocalId, 'TODO');
|
|
687
|
+
if (eagerFinalId !== undefined) {
|
|
688
|
+
sessionIdNormalizer.addFinalIds(eagerFinalId, eagerFinalId, cluster !== null && cluster !== void 0 ? cluster : fail());
|
|
689
|
+
}
|
|
690
|
+
this.localOverrides.append(newLocalId, override !== null && override !== void 0 ? override : fail());
|
|
691
|
+
// Since the local ID was just created, it is in both session and op space
|
|
692
|
+
const compressionMapping = newLocalId;
|
|
693
|
+
this.clustersAndOverridesInversion.set(overrideInversionKey, compressionMapping);
|
|
694
|
+
}
|
|
695
|
+
else if (eagerFinalId !== undefined) {
|
|
696
|
+
sessionIdNormalizer.addFinalIds(eagerFinalId, eagerFinalId, cluster !== null && cluster !== void 0 ? cluster : fail());
|
|
697
|
+
return eagerFinalId;
|
|
698
|
+
}
|
|
694
699
|
else {
|
|
695
|
-
|
|
700
|
+
const registeredLocal = sessionIdNormalizer.addLocalId();
|
|
701
|
+
assert(registeredLocal === newLocalId, 'TODO');
|
|
696
702
|
}
|
|
697
|
-
|
|
698
|
-
/**
|
|
699
|
-
* Generates a range of compressed IDs.
|
|
700
|
-
* This should ONLY be called to generate IDs for local operations.
|
|
701
|
-
* @param count - the number of IDs to generate, must be \> 0.
|
|
702
|
-
* @returns a persistable descriptor of the ID range.
|
|
703
|
-
*/
|
|
704
|
-
generateCompressedIdRange(count) {
|
|
705
|
-
assert(count > 0, 'Must generate a nonzero number of IDs.');
|
|
706
|
-
assert(count <= Number.MAX_SAFE_INTEGER, 'The number of allocated local IDs must not exceed the JS maximum safe integer.');
|
|
707
|
-
const first = this.generateNextLocalId();
|
|
708
|
-
this.localIdCount += count - 1;
|
|
709
|
-
return { first, count };
|
|
710
|
-
}
|
|
711
|
-
generateNextLocalId() {
|
|
712
|
-
return -++this.localIdCount;
|
|
703
|
+
return newLocalId;
|
|
713
704
|
}
|
|
714
705
|
/**
|
|
715
706
|
* Decompresses a previously compressed ID into a UUID or override string.
|
|
@@ -730,6 +721,11 @@ export class IdCompressor {
|
|
|
730
721
|
if (isFinalId(id)) {
|
|
731
722
|
const possibleCluster = this.getClusterForFinalId(id);
|
|
732
723
|
if (possibleCluster === undefined) {
|
|
724
|
+
// It may be an unfinalized eager final ID, so check with normalizer to get the offset from the session UUID
|
|
725
|
+
const creationIndex = this.sessionIdNormalizer.getCreationIndex(id);
|
|
726
|
+
if (creationIndex !== undefined) {
|
|
727
|
+
return stableIdFromNumericUuid(this.localSession.sessionUuid, creationIndex);
|
|
728
|
+
}
|
|
733
729
|
return undefined;
|
|
734
730
|
}
|
|
735
731
|
else {
|
|
@@ -797,9 +793,6 @@ export class IdCompressor {
|
|
|
797
793
|
return compressionMapping;
|
|
798
794
|
}
|
|
799
795
|
else {
|
|
800
|
-
const cluster = compressionMapping.cluster;
|
|
801
|
-
assert(IdCompressor.tryGetOverride(cluster, compressionMapping.originalOverridingFinal) !==
|
|
802
|
-
undefined, 'No override for cluster marked as having one.');
|
|
803
796
|
return ((_a = compressionMapping.associatedLocalId) !== null && _a !== void 0 ? _a : compressionMapping.originalOverridingFinal);
|
|
804
797
|
}
|
|
805
798
|
}
|
|
@@ -828,9 +821,9 @@ export class IdCompressor {
|
|
|
828
821
|
}
|
|
829
822
|
if (isStable) {
|
|
830
823
|
// May have already computed the numeric UUID, so avoid recomputing if possible
|
|
831
|
-
const
|
|
832
|
-
if (
|
|
833
|
-
return
|
|
824
|
+
const sessionSpaceId = this.getCompressedIdForStableId(numericUuid !== null && numericUuid !== void 0 ? numericUuid : inversionKey);
|
|
825
|
+
if (sessionSpaceId !== undefined) {
|
|
826
|
+
return sessionSpaceId;
|
|
834
827
|
}
|
|
835
828
|
}
|
|
836
829
|
return undefined;
|
|
@@ -849,9 +842,14 @@ export class IdCompressor {
|
|
|
849
842
|
if (-id > this.localIdCount) {
|
|
850
843
|
fail('Supplied local ID was not created by this compressor.');
|
|
851
844
|
}
|
|
852
|
-
// Check if this local ID has not been finalized yet
|
|
845
|
+
// Check if this local ID has not been finalized yet.
|
|
846
|
+
// Comparing lastFinalizedLocalId is a safe check for eager final IDs because the local IDs corresponding to them
|
|
847
|
+
// are never handed out to a consumer, and thus could not be passed into this method.
|
|
853
848
|
const { lastFinalizedLocalId } = this.localSession;
|
|
854
849
|
if (lastFinalizedLocalId === undefined || id < lastFinalizedLocalId) {
|
|
850
|
+
// Eager final IDs do not have overrides in the cluster until finalizing
|
|
851
|
+
// This means that using the normalizer to get the final/cluster associated would succeed but would not have the override,
|
|
852
|
+
// so checking localOverrides first is necessary.
|
|
855
853
|
const override = this.localOverrides.get(id);
|
|
856
854
|
if (override !== undefined) {
|
|
857
855
|
const inversionKey = IdCompressor.createInversionKey(override);
|
|
@@ -864,8 +862,7 @@ export class IdCompressor {
|
|
|
864
862
|
}
|
|
865
863
|
return id;
|
|
866
864
|
}
|
|
867
|
-
const [
|
|
868
|
-
const correspondingFinal = (finalBase + (localBase - id));
|
|
865
|
+
const [correspondingFinal, cluster] = (_b = this.sessionIdNormalizer.getFinalId(id)) !== null && _b !== void 0 ? _b : fail('Locally created cluster should be added to the map when allocated');
|
|
869
866
|
if (cluster.overrides) {
|
|
870
867
|
const override = cluster.overrides.get(correspondingFinal);
|
|
871
868
|
if (typeof override === 'object' && override.originalOverridingFinal !== undefined) {
|
|
@@ -875,11 +872,10 @@ export class IdCompressor {
|
|
|
875
872
|
}
|
|
876
873
|
return correspondingFinal;
|
|
877
874
|
}
|
|
878
|
-
normalizeToSessionSpace(id,
|
|
879
|
-
var _a, _b, _c;
|
|
880
|
-
const isLocalSession = originSessionId === this.localSessionId;
|
|
875
|
+
normalizeToSessionSpace(id, sessionIdIfLocal) {
|
|
876
|
+
var _a, _b, _c, _d;
|
|
881
877
|
if (isLocalId(id)) {
|
|
882
|
-
if (
|
|
878
|
+
if (sessionIdIfLocal === undefined || sessionIdIfLocal === this.localSessionId) {
|
|
883
879
|
const localIndex = -id;
|
|
884
880
|
if (localIndex > this.localIdCount) {
|
|
885
881
|
fail('Supplied local ID was not created by this compressor.');
|
|
@@ -887,27 +883,20 @@ export class IdCompressor {
|
|
|
887
883
|
return id;
|
|
888
884
|
}
|
|
889
885
|
else {
|
|
890
|
-
const session = this.sessions.get(
|
|
891
|
-
if (session === undefined) {
|
|
892
|
-
fail('No IDs have ever been finalized by the supplied session.');
|
|
893
|
-
}
|
|
886
|
+
const session = (_a = this.sessions.get(sessionIdIfLocal)) !== null && _a !== void 0 ? _a : fail('No IDs have ever been finalized by the supplied session.');
|
|
894
887
|
const localCount = -id;
|
|
895
888
|
const numericUuid = incrementUuid(session.sessionUuid, localCount - 1);
|
|
896
|
-
return (
|
|
889
|
+
return (_b = this.compressNumericUuid(numericUuid)) !== null && _b !== void 0 ? _b : fail('ID is not known to this compressor.');
|
|
897
890
|
}
|
|
898
891
|
}
|
|
899
|
-
const
|
|
900
|
-
if (
|
|
901
|
-
|
|
902
|
-
const indexInCluster = id - finalBase;
|
|
903
|
-
if (indexInCluster < cluster.count) {
|
|
904
|
-
return (localBase - indexInCluster);
|
|
905
|
-
}
|
|
892
|
+
const normalizedId = this.sessionIdNormalizer.getSessionSpaceId(id);
|
|
893
|
+
if (normalizedId !== undefined) {
|
|
894
|
+
return normalizedId;
|
|
906
895
|
}
|
|
907
896
|
// Check for a unified override finalized first by another session but to which the local session
|
|
908
897
|
// still has an associated local ID.
|
|
909
|
-
const [_, cluster] = (
|
|
910
|
-
const override = (
|
|
898
|
+
const [_, cluster] = (_c = this.getClusterForFinalId(id)) !== null && _c !== void 0 ? _c : fail('Supplied final ID was not finalized by this compressor.');
|
|
899
|
+
const override = (_d = cluster.overrides) === null || _d === void 0 ? void 0 : _d.get(id);
|
|
911
900
|
if (typeof override === 'object' && override.associatedLocalId !== undefined) {
|
|
912
901
|
return override.associatedLocalId;
|
|
913
902
|
}
|
|
@@ -941,13 +930,19 @@ export class IdCompressor {
|
|
|
941
930
|
}
|
|
942
931
|
return sessionSpaceId;
|
|
943
932
|
}
|
|
944
|
-
|
|
933
|
+
/**
|
|
934
|
+
* Returns a compressed ID for the supplied stable ID if it was created by the local session, and undefined otherwise.
|
|
935
|
+
*/
|
|
936
|
+
getCompressedIdForStableId(stableId) {
|
|
945
937
|
const numericUuid = typeof stableId === 'string' ? numericUuidFromStableId(stableId) : stableId;
|
|
946
|
-
const
|
|
947
|
-
if (
|
|
948
|
-
|
|
938
|
+
const creationIndex = getPositiveDelta(numericUuid, this.localSession.sessionUuid, this.localIdCount - 1);
|
|
939
|
+
if (creationIndex !== undefined) {
|
|
940
|
+
const sessionSpaceId = this.sessionIdNormalizer.getIdByCreationIndex(creationIndex);
|
|
941
|
+
if (sessionSpaceId !== undefined) {
|
|
942
|
+
return sessionSpaceId;
|
|
943
|
+
}
|
|
949
944
|
}
|
|
950
|
-
return
|
|
945
|
+
return undefined;
|
|
951
946
|
}
|
|
952
947
|
getClusterForFinalId(finalId) {
|
|
953
948
|
const possibleCluster = this.finalIdToCluster.getPairOrNextLower(finalId);
|
|
@@ -978,6 +973,9 @@ export class IdCompressor {
|
|
|
978
973
|
if (!compareMaps(this.sessions, other.sessions, (a, b) => IdCompressor.sessionDataEqual(a, b, true, compareLocalState))) {
|
|
979
974
|
return false;
|
|
980
975
|
}
|
|
976
|
+
if (!this.sessionIdNormalizer.equals(other.sessionIdNormalizer, (a, b) => IdCompressor.idClustersEqual(a, b, false, compareLocalState))) {
|
|
977
|
+
return false;
|
|
978
|
+
}
|
|
981
979
|
}
|
|
982
980
|
else {
|
|
983
981
|
for (const [keyA, valueA] of this.sessions) {
|
|
@@ -1168,6 +1166,7 @@ export class IdCompressor {
|
|
|
1168
1166
|
localIdCount: this.localIdCount,
|
|
1169
1167
|
overrides: [...this.localOverrides.entries()].map((entry) => [...entry]),
|
|
1170
1168
|
lastTakenLocalId: this.lastTakenLocalId,
|
|
1169
|
+
sessionNormalizer: this.sessionIdNormalizer.serialize(),
|
|
1171
1170
|
};
|
|
1172
1171
|
}
|
|
1173
1172
|
return serializedWithSession;
|
|
@@ -1241,12 +1240,6 @@ export class IdCompressor {
|
|
|
1241
1240
|
};
|
|
1242
1241
|
const lastFinalizedNormalized = lastFinalizedLocalId !== null && lastFinalizedLocalId !== void 0 ? lastFinalizedLocalId : 0;
|
|
1243
1242
|
const clusterBase = compressor.nextClusterBaseFinalId;
|
|
1244
|
-
if (serializedLocalState !== undefined && sessionId === compressor.localSessionId) {
|
|
1245
|
-
compressor.localIdToCluster.append((lastFinalizedNormalized - 1), [
|
|
1246
|
-
clusterBase,
|
|
1247
|
-
cluster,
|
|
1248
|
-
]);
|
|
1249
|
-
}
|
|
1250
1243
|
session.lastFinalizedLocalId = (lastFinalizedNormalized - count);
|
|
1251
1244
|
session.currentClusterDetails = { clusterBase, cluster };
|
|
1252
1245
|
compressor.nextClusterBaseFinalId = (compressor.nextClusterBaseFinalId + capacity);
|
|
@@ -1296,6 +1289,13 @@ export class IdCompressor {
|
|
|
1296
1289
|
}
|
|
1297
1290
|
}
|
|
1298
1291
|
}
|
|
1292
|
+
if (serializedLocalState !== undefined) {
|
|
1293
|
+
compressor.sessionIdNormalizer = SessionIdNormalizer.deserialize(serializedLocalState.sessionNormalizer, (finalId) => {
|
|
1294
|
+
var _a;
|
|
1295
|
+
const [_, cluster] = (_a = compressor.finalIdToCluster.getPairOrNextLower(finalId)) !== null && _a !== void 0 ? _a : fail('Final in serialized normalizer was never created.');
|
|
1296
|
+
return cluster;
|
|
1297
|
+
});
|
|
1298
|
+
}
|
|
1299
1299
|
assert(compressor.localSession.lastFinalizedLocalId === undefined ||
|
|
1300
1300
|
compressor.localIdCount >= -compressor.localSession.lastFinalizedLocalId);
|
|
1301
1301
|
return compressor;
|