@fluidframework/container-runtime 2.0.0-internal.6.1.0 → 2.0.0-internal.6.2.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 +35 -0
- package/README.md +4 -3
- package/dist/batchTracker.d.ts +1 -1
- package/dist/batchTracker.js +1 -1
- package/dist/batchTracker.js.map +1 -1
- package/dist/blobManager.d.ts +4 -20
- package/dist/blobManager.d.ts.map +1 -1
- package/dist/blobManager.js +47 -125
- package/dist/blobManager.js.map +1 -1
- package/dist/containerRuntime.d.ts +82 -14
- package/dist/containerRuntime.d.ts.map +1 -1
- package/dist/containerRuntime.js +236 -138
- package/dist/containerRuntime.js.map +1 -1
- package/dist/dataStore.d.ts.map +1 -1
- package/dist/dataStore.js +1 -2
- package/dist/dataStore.js.map +1 -1
- package/dist/dataStoreContext.d.ts.map +1 -1
- package/dist/dataStoreContext.js +4 -5
- package/dist/dataStoreContext.js.map +1 -1
- package/dist/dataStoreContexts.d.ts +1 -2
- package/dist/dataStoreContexts.d.ts.map +1 -1
- package/dist/dataStoreContexts.js.map +1 -1
- package/dist/dataStoreRegistry.js +2 -2
- package/dist/dataStoreRegistry.js.map +1 -1
- package/dist/dataStores.d.ts.map +1 -1
- package/dist/dataStores.js +4 -5
- package/dist/dataStores.js.map +1 -1
- package/dist/error.d.ts +14 -0
- package/dist/error.d.ts.map +1 -0
- package/dist/error.js +21 -0
- package/dist/error.js.map +1 -0
- package/dist/gc/garbageCollection.d.ts +1 -1
- package/dist/gc/garbageCollection.d.ts.map +1 -1
- package/dist/gc/garbageCollection.js +23 -5
- package/dist/gc/garbageCollection.js.map +1 -1
- package/dist/gc/gcConfigs.d.ts.map +1 -1
- package/dist/gc/gcConfigs.js +5 -3
- package/dist/gc/gcConfigs.js.map +1 -1
- package/dist/gc/gcDefinitions.d.ts +2 -0
- package/dist/gc/gcDefinitions.d.ts.map +1 -1
- package/dist/gc/gcDefinitions.js.map +1 -1
- package/dist/gc/gcTelemetry.d.ts.map +1 -1
- package/dist/gc/gcTelemetry.js +2 -0
- package/dist/gc/gcTelemetry.js.map +1 -1
- package/dist/id-compressor/appendOnlySortedMap.d.ts +8 -30
- package/dist/id-compressor/appendOnlySortedMap.d.ts.map +1 -1
- package/dist/id-compressor/appendOnlySortedMap.js +25 -67
- package/dist/id-compressor/appendOnlySortedMap.js.map +1 -1
- package/dist/id-compressor/finalSpace.d.ts +29 -0
- package/dist/id-compressor/finalSpace.d.ts.map +1 -0
- package/dist/id-compressor/finalSpace.js +62 -0
- package/dist/id-compressor/finalSpace.js.map +1 -0
- package/dist/id-compressor/idCompressor.d.ts +25 -250
- package/dist/id-compressor/idCompressor.d.ts.map +1 -1
- package/dist/id-compressor/idCompressor.js +385 -1149
- package/dist/id-compressor/idCompressor.js.map +1 -1
- package/dist/id-compressor/identifiers.d.ts +32 -0
- package/dist/id-compressor/identifiers.d.ts.map +1 -0
- package/dist/id-compressor/identifiers.js +15 -0
- package/dist/id-compressor/identifiers.js.map +1 -0
- package/dist/id-compressor/index.d.ts +5 -6
- package/dist/id-compressor/index.d.ts.map +1 -1
- package/dist/id-compressor/index.js +20 -26
- package/dist/id-compressor/index.js.map +1 -1
- package/dist/id-compressor/persistanceUtilities.d.ts +22 -0
- package/dist/id-compressor/persistanceUtilities.d.ts.map +1 -0
- package/dist/id-compressor/persistanceUtilities.js +43 -0
- package/dist/id-compressor/persistanceUtilities.js.map +1 -0
- package/dist/id-compressor/sessionSpaceNormalizer.d.ts +46 -0
- package/dist/id-compressor/sessionSpaceNormalizer.d.ts.map +1 -0
- package/dist/id-compressor/sessionSpaceNormalizer.js +80 -0
- package/dist/id-compressor/sessionSpaceNormalizer.js.map +1 -0
- package/dist/id-compressor/sessions.d.ts +115 -0
- package/dist/id-compressor/sessions.d.ts.map +1 -0
- package/dist/id-compressor/sessions.js +305 -0
- package/dist/id-compressor/sessions.js.map +1 -0
- package/dist/id-compressor/utilities.d.ts +49 -0
- package/dist/id-compressor/utilities.d.ts.map +1 -0
- package/dist/id-compressor/utilities.js +166 -0
- package/dist/id-compressor/utilities.js.map +1 -0
- package/dist/index.d.ts +3 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +6 -4
- package/dist/index.js.map +1 -1
- package/dist/opLifecycle/opCompressor.d.ts.map +1 -1
- package/dist/opLifecycle/opCompressor.js +1 -2
- package/dist/opLifecycle/opCompressor.js.map +1 -1
- package/dist/opLifecycle/opSplitter.d.ts.map +1 -1
- package/dist/opLifecycle/opSplitter.js +2 -3
- package/dist/opLifecycle/opSplitter.js.map +1 -1
- package/dist/opLifecycle/outbox.d.ts +1 -0
- package/dist/opLifecycle/outbox.d.ts.map +1 -1
- package/dist/opLifecycle/outbox.js +10 -11
- package/dist/opLifecycle/outbox.js.map +1 -1
- package/dist/opLifecycle/remoteMessageProcessor.d.ts.map +1 -1
- package/dist/opLifecycle/remoteMessageProcessor.js +11 -5
- package/dist/opLifecycle/remoteMessageProcessor.js.map +1 -1
- package/dist/packageVersion.d.ts +1 -1
- package/dist/packageVersion.js +1 -1
- package/dist/packageVersion.js.map +1 -1
- package/dist/pendingStateManager.d.ts +12 -5
- package/dist/pendingStateManager.d.ts.map +1 -1
- package/dist/pendingStateManager.js +24 -10
- package/dist/pendingStateManager.js.map +1 -1
- package/dist/scheduleManager.d.ts.map +1 -1
- package/dist/scheduleManager.js +4 -5
- package/dist/scheduleManager.js.map +1 -1
- package/dist/summary/index.d.ts +2 -2
- package/dist/summary/index.d.ts.map +1 -1
- package/dist/summary/index.js +2 -1
- package/dist/summary/index.js.map +1 -1
- package/dist/summary/orderedClientElection.d.ts +1 -2
- package/dist/summary/orderedClientElection.d.ts.map +1 -1
- package/dist/summary/orderedClientElection.js +2 -3
- package/dist/summary/orderedClientElection.js.map +1 -1
- package/dist/summary/runningSummarizer.d.ts +27 -4
- package/dist/summary/runningSummarizer.d.ts.map +1 -1
- package/dist/summary/runningSummarizer.js +237 -66
- package/dist/summary/runningSummarizer.js.map +1 -1
- package/dist/summary/summarizer.d.ts +6 -5
- package/dist/summary/summarizer.d.ts.map +1 -1
- package/dist/summary/summarizer.js +70 -67
- package/dist/summary/summarizer.js.map +1 -1
- package/dist/summary/summarizerClientElection.d.ts +1 -1
- package/dist/summary/summarizerClientElection.d.ts.map +1 -1
- package/dist/summary/summarizerClientElection.js.map +1 -1
- package/dist/summary/summarizerTypes.d.ts +38 -25
- package/dist/summary/summarizerTypes.d.ts.map +1 -1
- package/dist/summary/summarizerTypes.js.map +1 -1
- package/dist/summary/summaryCollection.d.ts +1 -2
- package/dist/summary/summaryCollection.d.ts.map +1 -1
- package/dist/summary/summaryCollection.js.map +1 -1
- package/dist/summary/summaryGenerator.d.ts +9 -3
- package/dist/summary/summaryGenerator.d.ts.map +1 -1
- package/dist/summary/summaryGenerator.js +42 -38
- package/dist/summary/summaryGenerator.js.map +1 -1
- package/dist/summary/summaryManager.d.ts +7 -6
- package/dist/summary/summaryManager.d.ts.map +1 -1
- package/dist/summary/summaryManager.js +22 -15
- package/dist/summary/summaryManager.js.map +1 -1
- package/lib/batchTracker.d.ts +1 -1
- package/lib/batchTracker.js +1 -1
- package/lib/batchTracker.js.map +1 -1
- package/lib/blobManager.d.ts +4 -20
- package/lib/blobManager.d.ts.map +1 -1
- package/lib/blobManager.js +46 -124
- package/lib/blobManager.js.map +1 -1
- package/lib/containerRuntime.d.ts +82 -14
- package/lib/containerRuntime.d.ts.map +1 -1
- package/lib/containerRuntime.js +223 -123
- package/lib/containerRuntime.js.map +1 -1
- package/lib/dataStore.d.ts.map +1 -1
- package/lib/dataStore.js +1 -2
- package/lib/dataStore.js.map +1 -1
- package/lib/dataStoreContext.d.ts.map +1 -1
- package/lib/dataStoreContext.js +1 -2
- package/lib/dataStoreContext.js.map +1 -1
- package/lib/dataStoreContexts.d.ts +1 -2
- package/lib/dataStoreContexts.d.ts.map +1 -1
- package/lib/dataStoreContexts.js.map +1 -1
- package/lib/dataStoreRegistry.js +1 -1
- package/lib/dataStoreRegistry.js.map +1 -1
- package/lib/dataStores.d.ts.map +1 -1
- package/lib/dataStores.js +1 -2
- package/lib/dataStores.js.map +1 -1
- package/lib/error.d.ts +14 -0
- package/lib/error.d.ts.map +1 -0
- package/lib/error.js +17 -0
- package/lib/error.js.map +1 -0
- package/lib/gc/garbageCollection.d.ts +1 -1
- package/lib/gc/garbageCollection.d.ts.map +1 -1
- package/lib/gc/garbageCollection.js +22 -4
- package/lib/gc/garbageCollection.js.map +1 -1
- package/lib/gc/gcConfigs.d.ts.map +1 -1
- package/lib/gc/gcConfigs.js +3 -1
- package/lib/gc/gcConfigs.js.map +1 -1
- package/lib/gc/gcDefinitions.d.ts +2 -0
- package/lib/gc/gcDefinitions.d.ts.map +1 -1
- package/lib/gc/gcDefinitions.js.map +1 -1
- package/lib/gc/gcTelemetry.d.ts.map +1 -1
- package/lib/gc/gcTelemetry.js +2 -0
- package/lib/gc/gcTelemetry.js.map +1 -1
- package/lib/id-compressor/appendOnlySortedMap.d.ts +8 -30
- package/lib/id-compressor/appendOnlySortedMap.d.ts.map +1 -1
- package/lib/id-compressor/appendOnlySortedMap.js +24 -65
- package/lib/id-compressor/appendOnlySortedMap.js.map +1 -1
- package/lib/id-compressor/finalSpace.d.ts +29 -0
- package/lib/id-compressor/finalSpace.d.ts.map +1 -0
- package/lib/id-compressor/finalSpace.js +58 -0
- package/lib/id-compressor/finalSpace.js.map +1 -0
- package/lib/id-compressor/idCompressor.d.ts +25 -250
- package/lib/id-compressor/idCompressor.d.ts.map +1 -1
- package/lib/id-compressor/idCompressor.js +381 -1139
- package/lib/id-compressor/idCompressor.js.map +1 -1
- package/lib/id-compressor/identifiers.d.ts +32 -0
- package/lib/id-compressor/identifiers.d.ts.map +1 -0
- package/lib/id-compressor/identifiers.js +11 -0
- package/lib/id-compressor/identifiers.js.map +1 -0
- package/lib/id-compressor/index.d.ts +5 -6
- package/lib/id-compressor/index.d.ts.map +1 -1
- package/lib/id-compressor/index.js +5 -6
- package/lib/id-compressor/index.js.map +1 -1
- package/lib/id-compressor/persistanceUtilities.d.ts +22 -0
- package/lib/id-compressor/persistanceUtilities.d.ts.map +1 -0
- package/lib/id-compressor/persistanceUtilities.js +34 -0
- package/lib/id-compressor/persistanceUtilities.js.map +1 -0
- package/lib/id-compressor/sessionSpaceNormalizer.d.ts +46 -0
- package/lib/id-compressor/sessionSpaceNormalizer.d.ts.map +1 -0
- package/lib/id-compressor/sessionSpaceNormalizer.js +76 -0
- package/lib/id-compressor/sessionSpaceNormalizer.js.map +1 -0
- package/lib/id-compressor/sessions.d.ts +115 -0
- package/lib/id-compressor/sessions.d.ts.map +1 -0
- package/lib/id-compressor/sessions.js +290 -0
- package/lib/id-compressor/sessions.js.map +1 -0
- package/lib/id-compressor/utilities.d.ts +49 -0
- package/lib/id-compressor/utilities.d.ts.map +1 -0
- package/lib/id-compressor/utilities.js +148 -0
- package/lib/id-compressor/utilities.js.map +1 -0
- package/lib/index.d.ts +3 -3
- package/lib/index.d.ts.map +1 -1
- package/lib/index.js +2 -2
- package/lib/index.js.map +1 -1
- package/lib/opLifecycle/opCompressor.d.ts.map +1 -1
- package/lib/opLifecycle/opCompressor.js +1 -2
- package/lib/opLifecycle/opCompressor.js.map +1 -1
- package/lib/opLifecycle/opSplitter.d.ts.map +1 -1
- package/lib/opLifecycle/opSplitter.js +1 -2
- package/lib/opLifecycle/opSplitter.js.map +1 -1
- package/lib/opLifecycle/outbox.d.ts +1 -0
- package/lib/opLifecycle/outbox.d.ts.map +1 -1
- package/lib/opLifecycle/outbox.js +6 -7
- package/lib/opLifecycle/outbox.js.map +1 -1
- package/lib/opLifecycle/remoteMessageProcessor.d.ts.map +1 -1
- package/lib/opLifecycle/remoteMessageProcessor.js +12 -6
- package/lib/opLifecycle/remoteMessageProcessor.js.map +1 -1
- package/lib/packageVersion.d.ts +1 -1
- package/lib/packageVersion.js +1 -1
- package/lib/packageVersion.js.map +1 -1
- package/lib/pendingStateManager.d.ts +12 -5
- package/lib/pendingStateManager.d.ts.map +1 -1
- package/lib/pendingStateManager.js +21 -7
- package/lib/pendingStateManager.js.map +1 -1
- package/lib/scheduleManager.d.ts.map +1 -1
- package/lib/scheduleManager.js +1 -2
- package/lib/scheduleManager.js.map +1 -1
- package/lib/summary/index.d.ts +2 -2
- package/lib/summary/index.d.ts.map +1 -1
- package/lib/summary/index.js +1 -1
- package/lib/summary/index.js.map +1 -1
- package/lib/summary/orderedClientElection.d.ts +1 -2
- package/lib/summary/orderedClientElection.d.ts.map +1 -1
- package/lib/summary/orderedClientElection.js +1 -2
- package/lib/summary/orderedClientElection.js.map +1 -1
- package/lib/summary/runningSummarizer.d.ts +27 -4
- package/lib/summary/runningSummarizer.d.ts.map +1 -1
- package/lib/summary/runningSummarizer.js +237 -66
- package/lib/summary/runningSummarizer.js.map +1 -1
- package/lib/summary/summarizer.d.ts +6 -5
- package/lib/summary/summarizer.d.ts.map +1 -1
- package/lib/summary/summarizer.js +68 -65
- package/lib/summary/summarizer.js.map +1 -1
- package/lib/summary/summarizerClientElection.d.ts +1 -1
- package/lib/summary/summarizerClientElection.d.ts.map +1 -1
- package/lib/summary/summarizerClientElection.js.map +1 -1
- package/lib/summary/summarizerTypes.d.ts +38 -25
- package/lib/summary/summarizerTypes.d.ts.map +1 -1
- package/lib/summary/summarizerTypes.js.map +1 -1
- package/lib/summary/summaryCollection.d.ts +1 -2
- package/lib/summary/summaryCollection.d.ts.map +1 -1
- package/lib/summary/summaryCollection.js.map +1 -1
- package/lib/summary/summaryGenerator.d.ts +9 -3
- package/lib/summary/summaryGenerator.d.ts.map +1 -1
- package/lib/summary/summaryGenerator.js +43 -39
- package/lib/summary/summaryGenerator.js.map +1 -1
- package/lib/summary/summaryManager.d.ts +7 -6
- package/lib/summary/summaryManager.d.ts.map +1 -1
- package/lib/summary/summaryManager.js +23 -16
- package/lib/summary/summaryManager.js.map +1 -1
- package/package.json +27 -24
- package/src/batchTracker.ts +1 -1
- package/src/blobManager.ts +57 -146
- package/src/containerRuntime.ts +331 -158
- package/src/dataStore.ts +1 -2
- package/src/dataStoreContext.ts +3 -6
- package/src/dataStoreContexts.ts +1 -2
- package/src/dataStoreRegistry.ts +1 -1
- package/src/dataStores.ts +3 -5
- package/src/error.ts +18 -0
- package/src/gc/garbageCollection.ts +38 -5
- package/src/gc/gcConfigs.ts +4 -2
- package/src/gc/gcDefinitions.ts +2 -0
- package/src/gc/gcTelemetry.ts +2 -0
- package/src/id-compressor/appendOnlySortedMap.ts +25 -86
- package/src/id-compressor/finalSpace.ts +67 -0
- package/src/id-compressor/idCompressor.ts +455 -1681
- package/src/id-compressor/identifiers.ts +42 -0
- package/src/id-compressor/index.ts +11 -20
- package/src/id-compressor/persistanceUtilities.ts +58 -0
- package/src/id-compressor/sessionSpaceNormalizer.ts +83 -0
- package/src/id-compressor/sessions.ts +405 -0
- package/src/id-compressor/utilities.ts +187 -0
- package/src/index.ts +7 -1
- package/src/opLifecycle/opCompressor.ts +1 -2
- package/src/opLifecycle/opSplitter.ts +4 -4
- package/src/opLifecycle/outbox.ts +13 -10
- package/src/opLifecycle/remoteMessageProcessor.ts +19 -6
- package/src/packageVersion.ts +1 -1
- package/src/pendingStateManager.ts +49 -27
- package/src/scheduleManager.ts +5 -4
- package/src/summary/index.ts +3 -1
- package/src/summary/orderedClientElection.ts +6 -4
- package/src/summary/runningSummarizer.ts +276 -95
- package/src/summary/summarizer.ts +22 -12
- package/src/summary/summarizerClientElection.ts +1 -1
- package/src/summary/summarizerTypes.ts +40 -25
- package/src/summary/summaryCollection.ts +1 -2
- package/src/summary/summaryGenerator.ts +49 -52
- package/src/summary/summaryManager.ts +33 -11
- package/dist/id-compressor/idRange.d.ts +0 -11
- package/dist/id-compressor/idRange.d.ts.map +0 -1
- package/dist/id-compressor/idRange.js +0 -29
- package/dist/id-compressor/idRange.js.map +0 -1
- package/dist/id-compressor/numericUuid.d.ts +0 -59
- package/dist/id-compressor/numericUuid.d.ts.map +0 -1
- package/dist/id-compressor/numericUuid.js +0 -325
- package/dist/id-compressor/numericUuid.js.map +0 -1
- package/dist/id-compressor/sessionIdNormalizer.d.ts +0 -138
- package/dist/id-compressor/sessionIdNormalizer.d.ts.map +0 -1
- package/dist/id-compressor/sessionIdNormalizer.js +0 -483
- package/dist/id-compressor/sessionIdNormalizer.js.map +0 -1
- package/dist/id-compressor/utils.d.ts +0 -57
- package/dist/id-compressor/utils.d.ts.map +0 -1
- package/dist/id-compressor/utils.js +0 -90
- package/dist/id-compressor/utils.js.map +0 -1
- package/dist/id-compressor/uuidUtilities.d.ts +0 -28
- package/dist/id-compressor/uuidUtilities.d.ts.map +0 -1
- package/dist/id-compressor/uuidUtilities.js +0 -104
- package/dist/id-compressor/uuidUtilities.js.map +0 -1
- package/lib/id-compressor/idRange.d.ts +0 -11
- package/lib/id-compressor/idRange.d.ts.map +0 -1
- package/lib/id-compressor/idRange.js +0 -25
- package/lib/id-compressor/idRange.js.map +0 -1
- package/lib/id-compressor/numericUuid.d.ts +0 -59
- package/lib/id-compressor/numericUuid.d.ts.map +0 -1
- package/lib/id-compressor/numericUuid.js +0 -315
- package/lib/id-compressor/numericUuid.js.map +0 -1
- package/lib/id-compressor/sessionIdNormalizer.d.ts +0 -138
- package/lib/id-compressor/sessionIdNormalizer.d.ts.map +0 -1
- package/lib/id-compressor/sessionIdNormalizer.js +0 -479
- package/lib/id-compressor/sessionIdNormalizer.js.map +0 -1
- package/lib/id-compressor/utils.d.ts +0 -57
- package/lib/id-compressor/utils.d.ts.map +0 -1
- package/lib/id-compressor/utils.js +0 -79
- package/lib/id-compressor/utils.js.map +0 -1
- package/lib/id-compressor/uuidUtilities.d.ts +0 -28
- package/lib/id-compressor/uuidUtilities.d.ts.map +0 -1
- package/lib/id-compressor/uuidUtilities.js +0 -96
- package/lib/id-compressor/uuidUtilities.js.map +0 -1
- package/src/id-compressor/idRange.ts +0 -35
- package/src/id-compressor/numericUuid.ts +0 -383
- package/src/id-compressor/sessionIdNormalizer.ts +0 -609
- package/src/id-compressor/utils.ts +0 -114
- package/src/id-compressor/uuidUtilities.ts +0 -120
|
@@ -3,218 +3,144 @@
|
|
|
3
3
|
* Licensed under the MIT License.
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
import { assert } from "@fluidframework/common-utils";
|
|
6
|
+
import { assert, bufferToString, stringToBuffer } from "@fluidframework/common-utils";
|
|
9
7
|
import {
|
|
8
|
+
IdCreationRange,
|
|
10
9
|
IIdCompressor,
|
|
11
10
|
IIdCompressorCore,
|
|
12
|
-
LocalCompressedId,
|
|
13
|
-
FinalCompressedId,
|
|
14
|
-
SessionSpaceCompressedId,
|
|
15
|
-
StableId,
|
|
16
11
|
OpSpaceCompressedId,
|
|
17
|
-
SessionId,
|
|
18
|
-
CompressedId,
|
|
19
|
-
} from "@fluidframework/runtime-definitions";
|
|
20
|
-
import type {
|
|
21
|
-
IdCreationRange,
|
|
22
|
-
SerializedCluster,
|
|
23
|
-
SerializedClusterOverrides,
|
|
24
12
|
SerializedIdCompressor,
|
|
25
13
|
SerializedIdCompressorWithNoSession,
|
|
26
14
|
SerializedIdCompressorWithOngoingSession,
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
15
|
+
SessionId,
|
|
16
|
+
SessionSpaceCompressedId,
|
|
17
|
+
StableId,
|
|
18
|
+
initialClusterCapacity,
|
|
31
19
|
} from "@fluidframework/runtime-definitions";
|
|
32
|
-
import BTree from "sorted-btree";
|
|
33
20
|
import { ITelemetryBaseLogger } from "@fluidframework/core-interfaces";
|
|
34
21
|
import { ITelemetryLoggerExt, createChildLogger } from "@fluidframework/telemetry-utils";
|
|
22
|
+
import { FinalCompressedId, isFinalId, LocalCompressedId, NumericUuid } from "./identifiers";
|
|
35
23
|
import {
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
compareMaps,
|
|
40
|
-
compareStrings,
|
|
41
|
-
fail,
|
|
42
|
-
Mutable,
|
|
43
|
-
setPropertyIfDefined,
|
|
44
|
-
} from "./utils";
|
|
45
|
-
import { assertIsStableId, isStableId } from "./uuidUtilities";
|
|
46
|
-
import { AppendOnlySortedMap } from "./appendOnlySortedMap";
|
|
47
|
-
import { getIds } from "./idRange";
|
|
48
|
-
import {
|
|
49
|
-
numericUuidEquals,
|
|
50
|
-
getPositiveDelta,
|
|
51
|
-
incrementUuid,
|
|
24
|
+
createSessionId,
|
|
25
|
+
localIdFromGenCount,
|
|
26
|
+
genCountFromLocalId,
|
|
52
27
|
numericUuidFromStableId,
|
|
53
|
-
|
|
28
|
+
offsetNumericUuid,
|
|
54
29
|
stableIdFromNumericUuid,
|
|
55
|
-
|
|
56
|
-
} from "./
|
|
57
|
-
import {
|
|
30
|
+
subtractNumericUuids,
|
|
31
|
+
} from "./utilities";
|
|
32
|
+
import {
|
|
33
|
+
Index,
|
|
34
|
+
readBoolean,
|
|
35
|
+
readNumber,
|
|
36
|
+
readNumericUuid,
|
|
37
|
+
writeBoolean,
|
|
38
|
+
writeNumber,
|
|
39
|
+
writeNumericUuid,
|
|
40
|
+
} from "./persistanceUtilities";
|
|
41
|
+
import {
|
|
42
|
+
getAlignedLocal,
|
|
43
|
+
getAlignedFinal,
|
|
44
|
+
IdCluster,
|
|
45
|
+
lastFinalizedLocal,
|
|
46
|
+
Session,
|
|
47
|
+
Sessions,
|
|
48
|
+
} from "./sessions";
|
|
49
|
+
import { SessionSpaceNormalizer } from "./sessionSpaceNormalizer";
|
|
50
|
+
import { FinalSpace } from "./finalSpace";
|
|
58
51
|
|
|
59
52
|
/**
|
|
60
|
-
*
|
|
61
|
-
*
|
|
62
|
-
* 1. A sequentially allocated UUID that is the result of adding its offset within the cluster to `baseUuid`.
|
|
63
|
-
* 2. An override string (stored in `overrides`) specified at allocation time.
|
|
53
|
+
* The version of IdCompressor that is currently persisted.
|
|
54
|
+
* This should not be changed without careful consideration to compatibility.
|
|
64
55
|
*/
|
|
65
|
-
|
|
66
|
-
/**
|
|
67
|
-
* The UUID corresponding to the first final ID in the cluster.
|
|
68
|
-
*/
|
|
69
|
-
readonly baseUuid: NumericUuid;
|
|
70
|
-
|
|
71
|
-
/**
|
|
72
|
-
* The total number of final IDs reserved for allocation in the cluster.
|
|
73
|
-
* Clusters are reserved in blocks as a performance optimization.
|
|
74
|
-
*/
|
|
75
|
-
capacity: number;
|
|
76
|
-
|
|
77
|
-
/**
|
|
78
|
-
* The number of final IDs currently allocated in the cluster.
|
|
79
|
-
*/
|
|
80
|
-
count: number;
|
|
81
|
-
|
|
82
|
-
/**
|
|
83
|
-
* The session in which this cluster was created
|
|
84
|
-
*/
|
|
85
|
-
readonly session: Session;
|
|
86
|
-
|
|
87
|
-
/**
|
|
88
|
-
* Final IDs assigned override strings within this cluster.
|
|
89
|
-
* These are one of the following:
|
|
90
|
-
*
|
|
91
|
-
* 1. The override string
|
|
92
|
-
*
|
|
93
|
-
* 2. The override string and external override details. This occurs when local IDs corresponding to the same
|
|
94
|
-
* override string are created by different sessions before any have been finalized. This can occur due to
|
|
95
|
-
* concurrency or offline. In this case, the string is stored for the final ID that got sequenced first, and that
|
|
96
|
-
* final ID is stored associated with all subsequent final IDs with the same override.
|
|
97
|
-
*
|
|
98
|
-
* When a final ID which is safely reserved via consensus as part of a cluster (but is not yet sequenced) is
|
|
99
|
-
* allocated with an override, this collection will be temporarily inaccurate as it will not contain an entry for
|
|
100
|
-
* that final ID. This absence indicates the uncertainty about what the final ID associated with that override will
|
|
101
|
-
* be after finalizing the range (which could change due to unification of a concurrent duplicate override).
|
|
102
|
-
* This table will be adjusted to reflect the override when that final ID is finalized via consensus, and
|
|
103
|
-
* decompression will use `clustersAndOverridesInversion` until that point.
|
|
104
|
-
*/
|
|
105
|
-
overrides?: Map<FinalCompressedId, string | UnifiedOverride>;
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
type UnifiedOverride = OverrideCompressionDetails & {
|
|
109
|
-
override: string;
|
|
110
|
-
};
|
|
56
|
+
const currentWrittenVersion = 1;
|
|
111
57
|
|
|
112
58
|
/**
|
|
113
|
-
*
|
|
114
|
-
* Used to track and allocate identity clusters associated with a particular session ID.
|
|
59
|
+
* See {@link IIdCompressor} and {@link IIdCompressorCore}
|
|
115
60
|
*/
|
|
116
|
-
|
|
117
|
-
readonly sessionUuid: NumericUuid;
|
|
118
|
-
|
|
119
|
-
/**
|
|
120
|
-
* `cluster` is undefined if a new cluster must be allocated when the session requests the next final ID allocation.
|
|
121
|
-
*/
|
|
122
|
-
currentClusterDetails:
|
|
123
|
-
| { readonly clusterBase: FinalCompressedId; readonly cluster: IdCluster }
|
|
124
|
-
| undefined;
|
|
125
|
-
|
|
61
|
+
export class IdCompressor implements IIdCompressor, IIdCompressorCore {
|
|
126
62
|
/**
|
|
127
|
-
*
|
|
63
|
+
* Max allowed initial cluster size.
|
|
128
64
|
*/
|
|
129
|
-
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
/**
|
|
133
|
-
* Roughly equates to a minimum of 1M sessions before we start allocating 64 bit IDs.
|
|
134
|
-
* This value must *NOT* change without careful consideration to compatibility.
|
|
135
|
-
*/
|
|
136
|
-
export const defaultClusterCapacity = 512;
|
|
137
|
-
|
|
138
|
-
/**
|
|
139
|
-
* The base UUID for the reserved id cluster.
|
|
140
|
-
* This should not be changed without consideration to compatibility.
|
|
141
|
-
*/
|
|
142
|
-
const reservedSessionId = ensureSessionUuid(
|
|
143
|
-
assertIsStableId("decaf40b-3c1a-47f8-a7a1-e8461ddb69ce"),
|
|
144
|
-
);
|
|
145
|
-
|
|
146
|
-
/**
|
|
147
|
-
* @returns true if the supplied ID is a final ID.
|
|
148
|
-
*/
|
|
149
|
-
export function isFinalId(id: CompressedId): id is FinalCompressedId {
|
|
150
|
-
return id >= 0;
|
|
151
|
-
}
|
|
65
|
+
public static readonly maxClusterSize = 2 ** 20;
|
|
152
66
|
|
|
153
|
-
|
|
154
|
-
* @returns true if the supplied ID is a local ID.
|
|
155
|
-
*/
|
|
156
|
-
export function isLocalId(id: CompressedId): id is LocalCompressedId {
|
|
157
|
-
return id < 0;
|
|
158
|
-
}
|
|
67
|
+
// ----- Local state -----
|
|
159
68
|
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
readonly cluster: IdCluster;
|
|
166
|
-
}
|
|
69
|
+
public readonly localSessionId: SessionId;
|
|
70
|
+
private readonly localSession: Session;
|
|
71
|
+
private readonly normalizer = new SessionSpaceNormalizer();
|
|
72
|
+
// The number of IDs generated by the local session
|
|
73
|
+
private localGenCount = 0;
|
|
167
74
|
|
|
168
|
-
|
|
169
|
-
readonly originalOverridingFinal: FinalCompressedId;
|
|
170
|
-
readonly associatedLocalId?: LocalCompressedId;
|
|
171
|
-
}
|
|
75
|
+
// -----------------------
|
|
172
76
|
|
|
173
|
-
|
|
174
|
-
* An override with a final ID associated with it.
|
|
175
|
-
*
|
|
176
|
-
* `associatedLocalId` is present on this type when a local ID in this session is associated with the override.
|
|
177
|
-
*
|
|
178
|
-
* It may be present even when `overriddenFinalId` was created by another session. This occurs when local IDs corresponding to the
|
|
179
|
-
* same override string are created by different sessions before any have been finalized. `overriddenFinalId` will be set to
|
|
180
|
-
* the *first* finalized ID with that string, but `associatedLocal` will be set to the local session's local ID for that string. This is
|
|
181
|
-
* done to preserve the invariant that an override will always compress into the same session-space ID for the lifetime of the session.
|
|
182
|
-
*/
|
|
183
|
-
interface FinalizedOverride extends OverrideCompressionDetails {
|
|
184
|
-
readonly cluster: IdCluster;
|
|
185
|
-
}
|
|
77
|
+
// ----- Final state -----
|
|
186
78
|
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
type Override = UnackedLocalId | FinalizedOverride;
|
|
79
|
+
// The gen count to be annotated on the range returned by the next call to `takeNextCreationRange`.
|
|
80
|
+
// This is updated to be equal to `generatedIdCount` + 1 each time it is called.
|
|
81
|
+
private nextRangeBaseGenCount = 1;
|
|
82
|
+
// The capacity of the next cluster to be created
|
|
83
|
+
private newClusterCapacity = initialClusterCapacity;
|
|
84
|
+
private readonly sessions = new Sessions();
|
|
85
|
+
private readonly finalSpace = new FinalSpace();
|
|
195
86
|
|
|
196
|
-
|
|
87
|
+
// -----------------------
|
|
197
88
|
|
|
198
|
-
|
|
199
|
-
const nonStableOverridePrefix = "\ue15e"; // A character in the Private Use Area of the BMP (https://en.wikipedia.org/wiki/Private_Use_Areas)
|
|
89
|
+
// ----- Telemetry state -----
|
|
200
90
|
|
|
201
|
-
|
|
202
|
-
|
|
91
|
+
// The number of local IDs generated since the last telemetry was sent.
|
|
92
|
+
private telemetryLocalIdCount = 0;
|
|
93
|
+
// The number of eager final IDs generated since the last telemetry was sent.
|
|
94
|
+
private telemetryEagerFinalIdCount = 0;
|
|
203
95
|
|
|
204
|
-
|
|
205
|
-
* See {@link IIdCompressor}
|
|
206
|
-
*/
|
|
207
|
-
export class IdCompressor implements IIdCompressorCore, IIdCompressor {
|
|
208
|
-
/**
|
|
209
|
-
* Max allowed cluster size
|
|
210
|
-
*/
|
|
211
|
-
public static maxClusterSize = 2 ** 20;
|
|
96
|
+
// -----------------------
|
|
212
97
|
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
98
|
+
private constructor(
|
|
99
|
+
localSessionIdOrDeserialized: SessionId | Sessions,
|
|
100
|
+
private readonly logger?: ITelemetryLoggerExt,
|
|
101
|
+
) {
|
|
102
|
+
if (typeof localSessionIdOrDeserialized === "string") {
|
|
103
|
+
this.localSessionId = localSessionIdOrDeserialized;
|
|
104
|
+
this.localSession = this.sessions.getOrCreate(localSessionIdOrDeserialized);
|
|
105
|
+
} else {
|
|
106
|
+
// Deserialize case
|
|
107
|
+
this.sessions = localSessionIdOrDeserialized;
|
|
108
|
+
// As policy, the first session is always the local session. Preserve this invariant
|
|
109
|
+
// during deserialization.
|
|
110
|
+
const firstSession = localSessionIdOrDeserialized.sessions().next();
|
|
111
|
+
assert(!firstSession.done, 0x754 /* First session must be present. */);
|
|
112
|
+
this.localSession = firstSession.value;
|
|
113
|
+
this.localSessionId = stableIdFromNumericUuid(
|
|
114
|
+
this.localSession.sessionUuid,
|
|
115
|
+
) as SessionId;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
public static create(logger?: ITelemetryBaseLogger): IdCompressor;
|
|
120
|
+
public static create(sessionId: SessionId, logger?: ITelemetryBaseLogger): IdCompressor;
|
|
121
|
+
public static create(
|
|
122
|
+
sessionIdOrLogger?: SessionId | ITelemetryBaseLogger,
|
|
123
|
+
loggerOrUndefined?: ITelemetryBaseLogger,
|
|
124
|
+
): IdCompressor {
|
|
125
|
+
let localSessionId: SessionId;
|
|
126
|
+
let logger: ITelemetryBaseLogger | undefined;
|
|
127
|
+
if (sessionIdOrLogger === undefined) {
|
|
128
|
+
localSessionId = createSessionId();
|
|
129
|
+
} else {
|
|
130
|
+
if (typeof sessionIdOrLogger === "string") {
|
|
131
|
+
localSessionId = sessionIdOrLogger;
|
|
132
|
+
logger = loggerOrUndefined;
|
|
133
|
+
} else {
|
|
134
|
+
localSessionId = createSessionId();
|
|
135
|
+
logger = loggerOrUndefined;
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
const compressor = new IdCompressor(
|
|
139
|
+
localSessionId,
|
|
140
|
+
logger === undefined ? undefined : createChildLogger({ logger }),
|
|
141
|
+
);
|
|
142
|
+
return compressor;
|
|
143
|
+
}
|
|
218
144
|
|
|
219
145
|
/**
|
|
220
146
|
* The size of each newly created ID cluster.
|
|
@@ -228,315 +154,83 @@ export class IdCompressor implements IIdCompressorCore, IIdCompressor {
|
|
|
228
154
|
* `IdCompressor.maxClusterSize`.
|
|
229
155
|
*/
|
|
230
156
|
public set clusterCapacity(value: number) {
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
157
|
+
if (value <= 0) {
|
|
158
|
+
throw new Error("Clusters must have a positive capacity.");
|
|
159
|
+
}
|
|
160
|
+
if (value > IdCompressor.maxClusterSize) {
|
|
161
|
+
throw new Error("Clusters must not exceed max cluster size.");
|
|
162
|
+
}
|
|
236
163
|
this.newClusterCapacity = value;
|
|
237
164
|
}
|
|
238
165
|
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
* The `IdCompressor`'s current local session.
|
|
247
|
-
*/
|
|
248
|
-
private readonly localSession: Session;
|
|
249
|
-
|
|
250
|
-
/**
|
|
251
|
-
* The base final ID of the next cluster to be created.
|
|
252
|
-
*/
|
|
253
|
-
private nextClusterBaseFinalId: FinalCompressedId = 0 as FinalCompressedId;
|
|
254
|
-
|
|
255
|
-
/**
|
|
256
|
-
* Total number of IDs created locally during the current session.
|
|
257
|
-
*/
|
|
258
|
-
private localIdCount = 0;
|
|
259
|
-
|
|
260
|
-
/**
|
|
261
|
-
* The most recent (i.e. smallest, due to being negative) local ID in a range returned by `takeNextCreationRange`.
|
|
262
|
-
* Undefined if no non-empty ranges have ever been returned by this compressor.
|
|
263
|
-
*/
|
|
264
|
-
private lastTakenLocalId: LocalCompressedId | undefined;
|
|
265
|
-
|
|
266
|
-
/**
|
|
267
|
-
* Maps local IDs to override strings. This will contain an entry for every override assigned to a local ID generated during
|
|
268
|
-
* the current session, and retains entries for the lifetime of this compressor.
|
|
269
|
-
*/
|
|
270
|
-
private readonly localOverrides = new AppendOnlySortedMap<LocalCompressedId, string>(
|
|
271
|
-
compareFiniteNumbersReversed,
|
|
272
|
-
);
|
|
273
|
-
|
|
274
|
-
/**
|
|
275
|
-
* Maps local IDs to the final ID they are associated with (if any), and maps final IDs to the corresponding local ID (if any).
|
|
276
|
-
* This is used to efficiently compute normalization. This map can be thought of as mapping ranges of "optimistic uncertainty"
|
|
277
|
-
* (local IDs) to the result of consensus (reserved ranges of final IDs, a.k.a. clusters). Any given range of local IDs
|
|
278
|
-
* does not necessarily span an entire cluster, as some session-space IDs may be allocated *after* a cluster has been allocated
|
|
279
|
-
* 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.
|
|
280
|
-
* However, there is always a range of local IDs with size \>= 1 associated with the beginning of every cluster, as clusters are only
|
|
281
|
-
* created *after* they are needed and thus there is some period of uncertainty after local IDs have been handed out but before the
|
|
282
|
-
* range containing them has been finalized. There may also be ranges of local IDs that do not start at the beginning of a
|
|
283
|
-
* cluster; this happens when a cluster is expanded instead of allocating a new one.
|
|
284
|
-
* Additionally, session space IDs associated with an override string will also always be local IDs, because there is uncertainty as
|
|
285
|
-
* to whether another client simultaneously allocated the same override and could get sequenced first (a.k.a. unification) and its
|
|
286
|
-
* final ID would be associated with that override.
|
|
287
|
-
* See `SessionIdNormalizer` for more.
|
|
288
|
-
*/
|
|
289
|
-
private sessionIdNormalizer = new SessionIdNormalizer<IdCluster>();
|
|
290
|
-
|
|
291
|
-
/**
|
|
292
|
-
* Contains entries for cluster base UUIDs and override strings (both local and final).
|
|
293
|
-
* As a performance optimization, entries for finalized strings also include the containing cluster object.
|
|
294
|
-
* This can be viewed as three separate tables: the inverse table for `localOverrides`, the inverse table for the union of all
|
|
295
|
-
* the overrides of the clusters in `finalIdToCluster`, and the inverse lookup of cluster base UUIDs to their clusters.
|
|
296
|
-
* This is unified as a performance optimization, as the common case does not have overridden IDs. It is a btree due to the need
|
|
297
|
-
* to make range queries.
|
|
298
|
-
*/
|
|
299
|
-
private readonly clustersAndOverridesInversion: BTree<InversionKey, CompressionMapping> =
|
|
300
|
-
new BTree(undefined, compareStrings);
|
|
301
|
-
|
|
302
|
-
/**
|
|
303
|
-
* Maps the first final ID in a cluster to its owning cluster.
|
|
304
|
-
* Can be searched in O(log n) to determine clusters for any final ID.
|
|
305
|
-
*/
|
|
306
|
-
private readonly finalIdToCluster: AppendOnlySortedMap<FinalCompressedId, IdCluster> =
|
|
307
|
-
new AppendOnlySortedMap(compareFiniteNumbers);
|
|
308
|
-
|
|
309
|
-
private readonly logger: ITelemetryLoggerExt;
|
|
166
|
+
public generateCompressedId(): SessionSpaceCompressedId {
|
|
167
|
+
this.localGenCount++;
|
|
168
|
+
const lastCluster = this.localSession.getLastCluster();
|
|
169
|
+
if (lastCluster === undefined) {
|
|
170
|
+
this.telemetryLocalIdCount++;
|
|
171
|
+
return this.generateNextLocalId();
|
|
172
|
+
}
|
|
310
173
|
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
174
|
+
// If there exists a cluster of final IDs already claimed by the local session that still has room in it,
|
|
175
|
+
// it is known prior to range sequencing what a local ID's corresponding final ID will be.
|
|
176
|
+
// In this case, it is safe to return the final ID immediately. This is guaranteed to be safe because
|
|
177
|
+
// any op that the local session sends that contains one of those final IDs are guaranteed to arrive to
|
|
178
|
+
// collaborators *after* the one containing the creation range.
|
|
179
|
+
const clusterOffset = this.localGenCount - genCountFromLocalId(lastCluster.baseLocalId);
|
|
180
|
+
if (lastCluster.capacity > clusterOffset) {
|
|
181
|
+
this.telemetryEagerFinalIdCount++;
|
|
182
|
+
// Space in the cluster: eager final
|
|
183
|
+
return ((lastCluster.baseFinalId as number) +
|
|
184
|
+
clusterOffset) as SessionSpaceCompressedId;
|
|
185
|
+
}
|
|
186
|
+
// No space in the cluster, return next local
|
|
187
|
+
this.telemetryLocalIdCount++;
|
|
188
|
+
return this.generateNextLocalId();
|
|
318
189
|
}
|
|
319
190
|
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
* @returns the session object for the supplied ID
|
|
325
|
-
*/
|
|
326
|
-
private createSession(sessionId: SessionId): Session {
|
|
327
|
-
assert(
|
|
328
|
-
!this.clustersAndOverridesInversion.has(sessionId),
|
|
329
|
-
0x484 /* Attempted to create duplicate session */,
|
|
330
|
-
);
|
|
331
|
-
const existingSession = this.sessions.get(sessionId);
|
|
332
|
-
if (existingSession !== undefined) {
|
|
333
|
-
fail("createSession must only be called once for each session ID.");
|
|
334
|
-
}
|
|
335
|
-
const sessionUuid = numericUuidFromStableId(sessionId);
|
|
336
|
-
const session: Session = {
|
|
337
|
-
sessionUuid,
|
|
338
|
-
currentClusterDetails: undefined,
|
|
339
|
-
lastFinalizedLocalId: undefined,
|
|
340
|
-
};
|
|
341
|
-
this.sessions.set(sessionId, session);
|
|
342
|
-
return session;
|
|
191
|
+
private generateNextLocalId(): LocalCompressedId {
|
|
192
|
+
// Must tell the normalizer that we generated a local ID
|
|
193
|
+
this.normalizer.addLocalRange(this.localGenCount, 1);
|
|
194
|
+
return localIdFromGenCount(this.localGenCount);
|
|
343
195
|
}
|
|
344
196
|
|
|
345
|
-
/**
|
|
346
|
-
* Returns a range of local IDs created by this session in a format for sending to the server for finalizing.
|
|
347
|
-
* The range will include all local IDs generated via calls to `generateCompressedId` since the last time this method was called.
|
|
348
|
-
* @returns the range of session-local IDs, which may be empty. This range must be sent to the server for ordering before
|
|
349
|
-
* it is finalized. Ranges must be sent to the server in the order that they are taken via calls to this method.
|
|
350
|
-
*/
|
|
351
197
|
public takeNextCreationRange(): IdCreationRange {
|
|
352
|
-
const
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
if (lastLocalInRange !== lastTakenNormalized) {
|
|
358
|
-
const firstLocalInRange = (lastTakenNormalized - 1) as UnackedLocalId;
|
|
359
|
-
const overrides = [
|
|
360
|
-
...this.localOverrides.getRange(
|
|
361
|
-
(lastTakenNormalized - 1) as LocalCompressedId,
|
|
362
|
-
lastLocalInRange as LocalCompressedId,
|
|
363
|
-
),
|
|
364
|
-
] as (readonly [UnackedLocalId, string])[];
|
|
365
|
-
if (hasAtLeastLength(overrides, 1)) {
|
|
366
|
-
assert(
|
|
367
|
-
overrides[0][0] <= firstLocalInRange,
|
|
368
|
-
0x486 /* Inconsistent override state */,
|
|
369
|
-
);
|
|
370
|
-
assert(
|
|
371
|
-
overrides[overrides.length - 1][0] >= lastLocalInRange,
|
|
372
|
-
0x487 /* Inconsistent override state */,
|
|
373
|
-
);
|
|
374
|
-
ids = {
|
|
375
|
-
overrides,
|
|
376
|
-
};
|
|
377
|
-
const first = firstLocalInRange === overrides[0][0] ? undefined : firstLocalInRange;
|
|
378
|
-
const last =
|
|
379
|
-
lastLocalInRange === overrides[overrides.length - 1][0]
|
|
380
|
-
? undefined
|
|
381
|
-
: lastLocalInRange;
|
|
382
|
-
setPropertyIfDefined(first, ids, "first");
|
|
383
|
-
setPropertyIfDefined(last, ids, "last");
|
|
384
|
-
} else {
|
|
385
|
-
ids = {
|
|
386
|
-
first: firstLocalInRange,
|
|
387
|
-
last: lastLocalInRange,
|
|
388
|
-
};
|
|
389
|
-
}
|
|
390
|
-
this.lastTakenLocalId = lastLocalInRange;
|
|
391
|
-
}
|
|
392
|
-
|
|
393
|
-
const range: Mutable<IdCreationRange> = { sessionId: this.localSessionId };
|
|
394
|
-
|
|
395
|
-
if (ids === undefined) {
|
|
396
|
-
return range;
|
|
198
|
+
const count = this.localGenCount - (this.nextRangeBaseGenCount - 1);
|
|
199
|
+
if (count === 0) {
|
|
200
|
+
return {
|
|
201
|
+
sessionId: this.localSessionId,
|
|
202
|
+
};
|
|
397
203
|
}
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
this.
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
204
|
+
const range: IdCreationRange = {
|
|
205
|
+
sessionId: this.localSessionId,
|
|
206
|
+
ids: {
|
|
207
|
+
firstGenCount: this.nextRangeBaseGenCount,
|
|
208
|
+
count,
|
|
209
|
+
},
|
|
210
|
+
};
|
|
211
|
+
this.nextRangeBaseGenCount = this.localGenCount + 1;
|
|
406
212
|
return range;
|
|
407
213
|
}
|
|
408
214
|
|
|
409
|
-
/**
|
|
410
|
-
* Finalizes the supplied range of IDs (which may be from either a remote or local session).
|
|
411
|
-
* @param range - the range of session-local IDs to finalize.
|
|
412
|
-
*/
|
|
413
215
|
public finalizeCreationRange(range: IdCreationRange): void {
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
const isLocal = sessionId === this.localSessionId;
|
|
417
|
-
const session = this.sessions.get(sessionId) ?? this.createSession(sessionId);
|
|
418
|
-
|
|
419
|
-
const ids = getIds(range);
|
|
420
|
-
if (ids === undefined) {
|
|
216
|
+
// Check if the range has IDs
|
|
217
|
+
if (range.ids === undefined) {
|
|
421
218
|
return;
|
|
422
219
|
}
|
|
423
220
|
|
|
424
|
-
|
|
425
|
-
const {
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
assert(
|
|
436
|
-
newFirstFinalizedLocal === normalizedLastFinalizedLocal - 1,
|
|
437
|
-
0x489 /* Ranges finalized out of order. */,
|
|
438
|
-
);
|
|
439
|
-
|
|
440
|
-
// The total number of session-local IDs to finalize
|
|
441
|
-
const finalizeCount = normalizedLastFinalizedLocal - newLastFinalizedLocal;
|
|
442
|
-
assert(finalizeCount >= 1, 0x48a /* Cannot finalize an empty range. */);
|
|
443
|
-
|
|
444
|
-
let eagerFinalIdCount = 0;
|
|
445
|
-
let initialClusterCount = 0;
|
|
446
|
-
let remainingCount = finalizeCount;
|
|
447
|
-
let newBaseUuid: NumericUuid | undefined;
|
|
448
|
-
if (currentClusterExists) {
|
|
449
|
-
if (isLocal) {
|
|
450
|
-
const lastKnownFinal =
|
|
451
|
-
this.sessionIdNormalizer.getLastFinalId() ??
|
|
452
|
-
fail("Cluster exists but normalizer does not have an entry for it.");
|
|
453
|
-
const lastAlignedFinalInCluster = (currentBaseFinalId +
|
|
454
|
-
Math.min(currentCluster.count + finalizeCount, currentCluster.capacity) -
|
|
455
|
-
1) as FinalCompressedId;
|
|
456
|
-
if (lastAlignedFinalInCluster > lastKnownFinal) {
|
|
457
|
-
this.sessionIdNormalizer.addFinalIds(
|
|
458
|
-
(lastKnownFinal + 1) as FinalCompressedId,
|
|
459
|
-
lastAlignedFinalInCluster,
|
|
460
|
-
currentCluster,
|
|
461
|
-
);
|
|
462
|
-
}
|
|
463
|
-
}
|
|
464
|
-
initialClusterCount = currentCluster.count;
|
|
465
|
-
const remainingCapacity = currentCluster.capacity - initialClusterCount;
|
|
466
|
-
const overflow = remainingCount - remainingCapacity;
|
|
467
|
-
const hasRoom = overflow <= 0;
|
|
468
|
-
if (hasRoom || currentBaseFinalId === this.finalIdToCluster.maxKey()) {
|
|
469
|
-
currentCluster.count += remainingCount;
|
|
470
|
-
eagerFinalIdCount = remainingCount;
|
|
471
|
-
remainingCount = 0;
|
|
472
|
-
// The common case is that there is room in the cluster, and the new final IDs can simply be added to it
|
|
473
|
-
if (!hasRoom) {
|
|
474
|
-
// The cluster is full but is the last in the list of clusters.
|
|
475
|
-
// This allows it to be expanded instead of allocating a new one.
|
|
476
|
-
const expansionAmount = this.newClusterCapacity + overflow;
|
|
477
|
-
const previousCapacity = currentCluster.capacity;
|
|
478
|
-
currentCluster.capacity += expansionAmount;
|
|
479
|
-
this.nextClusterBaseFinalId = (this.nextClusterBaseFinalId +
|
|
480
|
-
expansionAmount) as FinalCompressedId;
|
|
481
|
-
assert(
|
|
482
|
-
this.nextClusterBaseFinalId < Number.MAX_SAFE_INTEGER,
|
|
483
|
-
0x48b /* The number of allocated final IDs must not exceed the JS maximum safe integer. */,
|
|
484
|
-
);
|
|
485
|
-
this.checkClusterForCollision(currentCluster);
|
|
486
|
-
if (isLocal) {
|
|
487
|
-
// Example with cluster size of 3:
|
|
488
|
-
// Ids generated so far: -1 1 2 -4 -5 <-- note positive numbers are eager finals
|
|
489
|
-
// Cluster: [ 0 1 2 ]
|
|
490
|
-
// ~ finalizing happens, causing expansion of 2 (overflow) + 3 (cluster capacity) ~
|
|
491
|
-
// Cluster: [ 0 1 2 3 4 _ _ _ ]
|
|
492
|
-
// corresponding locals: -1 -4 -5
|
|
493
|
-
// lastFinalizedLocalId^ ^newLastFinalizedLocalId = -5
|
|
494
|
-
// overflow = 2: ----
|
|
495
|
-
// localIdPivot^
|
|
496
|
-
// lastFinalizedFinal^
|
|
497
|
-
const newLastFinalizedFinal = (currentBaseFinalId +
|
|
498
|
-
currentCluster.count -
|
|
499
|
-
1) as FinalCompressedId;
|
|
500
|
-
assert(
|
|
501
|
-
session.lastFinalizedLocalId !== undefined,
|
|
502
|
-
0x48c /* Cluster already exists for session but there is no finalized local ID */,
|
|
503
|
-
);
|
|
504
|
-
const finalPivot = (newLastFinalizedFinal -
|
|
505
|
-
overflow +
|
|
506
|
-
1) as FinalCompressedId;
|
|
507
|
-
// Inform the normalizer of all IDs that we now know will end up being finalized into this cluster, including the ones
|
|
508
|
-
// that were given out as locals (non-eager) because they exceeded the bounds of the current cluster before it was expanded.
|
|
509
|
-
// It is safe to associate the unfinalized locals with their future final IDs even before the ranges for those locals are
|
|
510
|
-
// actually finalized, because total order broadcast guarantees that any usage of those final IDs will be observed after
|
|
511
|
-
// the finalization of the ranges.
|
|
512
|
-
this.sessionIdNormalizer.registerFinalIdBlock(
|
|
513
|
-
finalPivot,
|
|
514
|
-
expansionAmount,
|
|
515
|
-
currentCluster,
|
|
516
|
-
);
|
|
517
|
-
this.logger?.sendTelemetryEvent({
|
|
518
|
-
eventName: "RuntimeIdCompressor:ClusterExpansion",
|
|
519
|
-
sessionId: this.localSessionId,
|
|
520
|
-
previousCapacity,
|
|
521
|
-
newCapacity: currentCluster.capacity,
|
|
522
|
-
overflow,
|
|
523
|
-
});
|
|
524
|
-
}
|
|
525
|
-
}
|
|
526
|
-
} else {
|
|
527
|
-
// The range cannot be fully allocated in the existing cluster, so allocate any space left in it and
|
|
528
|
-
// form a new one by incrementing the previous baseUuid
|
|
529
|
-
newBaseUuid = incrementUuid(currentCluster.baseUuid, currentCluster.capacity);
|
|
530
|
-
currentCluster.count += remainingCapacity;
|
|
531
|
-
remainingCount -= remainingCapacity;
|
|
532
|
-
this.logger?.sendTelemetryEvent({
|
|
533
|
-
eventName: "RuntimeIdCompressor:OverfilledCluster",
|
|
534
|
-
sessionId: this.localSessionId,
|
|
535
|
-
});
|
|
221
|
+
assert(range.ids.count > 0, 0x755 /* Malformed ID Range. */);
|
|
222
|
+
const { sessionId, ids } = range;
|
|
223
|
+
const { count, firstGenCount } = ids;
|
|
224
|
+
const session = this.sessions.getOrCreate(sessionId);
|
|
225
|
+
const isLocal = session === this.localSession;
|
|
226
|
+
const rangeBaseLocal = localIdFromGenCount(firstGenCount);
|
|
227
|
+
let lastCluster = session.getLastCluster();
|
|
228
|
+
if (lastCluster === undefined) {
|
|
229
|
+
// This is the first cluster in the session space
|
|
230
|
+
if (rangeBaseLocal !== -1) {
|
|
231
|
+
throw new Error("Ranges finalized out of order.");
|
|
536
232
|
}
|
|
537
|
-
|
|
538
|
-
// Session has never made a cluster, form a new one with the session UUID as the baseUuid
|
|
539
|
-
newBaseUuid = session.sessionUuid;
|
|
233
|
+
lastCluster = this.addEmptyCluster(session, this.clusterCapacity + count);
|
|
540
234
|
if (isLocal) {
|
|
541
235
|
this.logger?.sendTelemetryEvent({
|
|
542
236
|
eventName: "RuntimeIdCompressor:FirstCluster",
|
|
@@ -545,177 +239,44 @@ export class IdCompressor implements IIdCompressorCore, IIdCompressor {
|
|
|
545
239
|
}
|
|
546
240
|
}
|
|
547
241
|
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
// 2. All local IDs are finalized into the existing (current) cluster for the session.
|
|
552
|
-
// 3. Local IDs are finalized into both the current cluster and a new one, as the current cluster did not have enough room.
|
|
553
|
-
let newCluster: IdCluster | undefined;
|
|
554
|
-
let newBaseFinalId: FinalCompressedId | undefined;
|
|
555
|
-
// The first local ID that will be finalized into a new cluster, if there is one.
|
|
556
|
-
// This lets us quickly compare which cluster an override string will go into.
|
|
557
|
-
let localIdPivot: LocalCompressedId | undefined;
|
|
558
|
-
|
|
559
|
-
// Need to make a new cluster
|
|
560
|
-
if (newBaseUuid !== undefined) {
|
|
561
|
-
if (remainingCount <= 0) {
|
|
562
|
-
fail("Should not create an empty cluster.");
|
|
563
|
-
}
|
|
564
|
-
if (currentCluster !== undefined && currentCluster.capacity !== currentCluster.count) {
|
|
565
|
-
fail("Cluster must be filled before another is allocated.");
|
|
566
|
-
}
|
|
567
|
-
|
|
568
|
-
newBaseFinalId = this.nextClusterBaseFinalId;
|
|
569
|
-
const newCapacity = Math.max(this.newClusterCapacity, remainingCount);
|
|
570
|
-
newCluster = {
|
|
571
|
-
baseUuid: newBaseUuid,
|
|
572
|
-
capacity: newCapacity,
|
|
573
|
-
count: remainingCount,
|
|
574
|
-
session,
|
|
575
|
-
};
|
|
576
|
-
|
|
577
|
-
const usedCapacity = finalizeCount - remainingCount;
|
|
578
|
-
localIdPivot = (newFirstFinalizedLocal - usedCapacity) as LocalCompressedId;
|
|
579
|
-
|
|
580
|
-
if (isLocal) {
|
|
581
|
-
this.logger?.sendTelemetryEvent({
|
|
582
|
-
eventName: "RuntimeIdCompressor:NewCluster",
|
|
583
|
-
sessionId: this.localSessionId,
|
|
584
|
-
clusterCapacity: newCapacity,
|
|
585
|
-
clusterCount: remainingCount,
|
|
586
|
-
});
|
|
587
|
-
this.sessionIdNormalizer.registerFinalIdBlock(
|
|
588
|
-
newBaseFinalId,
|
|
589
|
-
newCluster.capacity,
|
|
590
|
-
newCluster,
|
|
591
|
-
);
|
|
592
|
-
}
|
|
593
|
-
|
|
594
|
-
this.checkClusterForCollision(newCluster);
|
|
595
|
-
this.clustersAndOverridesInversion.set(stableIdFromNumericUuid(newCluster.baseUuid), {
|
|
596
|
-
clusterBase: newBaseFinalId,
|
|
597
|
-
cluster: newCluster,
|
|
598
|
-
});
|
|
599
|
-
session.currentClusterDetails = { cluster: newCluster, clusterBase: newBaseFinalId };
|
|
600
|
-
this.nextClusterBaseFinalId = (this.nextClusterBaseFinalId +
|
|
601
|
-
newCluster.capacity) as FinalCompressedId;
|
|
602
|
-
assert(
|
|
603
|
-
this.nextClusterBaseFinalId < Number.MAX_SAFE_INTEGER,
|
|
604
|
-
0x48e /* The number of allocated final IDs must not exceed the JS maximum safe integer. */,
|
|
605
|
-
);
|
|
606
|
-
this.finalIdToCluster.append(newBaseFinalId, newCluster);
|
|
242
|
+
const remainingCapacity = lastCluster.capacity - lastCluster.count;
|
|
243
|
+
if (lastCluster.baseLocalId - lastCluster.count !== rangeBaseLocal) {
|
|
244
|
+
throw new Error("Ranges finalized out of order.");
|
|
607
245
|
}
|
|
608
246
|
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
assert(
|
|
620
|
-
overriddenLocal < normalizedLastFinalizedLocal,
|
|
621
|
-
0x490 /* Ranges finalized out of order. */,
|
|
622
|
-
);
|
|
623
|
-
assert(
|
|
624
|
-
overriddenLocal >= newLastFinalizedLocal,
|
|
625
|
-
0x491 /* Malformed range: override ID ahead of range start. */,
|
|
626
|
-
);
|
|
627
|
-
let cluster: IdCluster;
|
|
628
|
-
let overriddenFinal: FinalCompressedId;
|
|
629
|
-
if (localIdPivot !== undefined && overriddenLocal <= localIdPivot) {
|
|
630
|
-
// Override is at or past the pivot, so it is in a new cluster.
|
|
631
|
-
assert(
|
|
632
|
-
newCluster !== undefined && newBaseFinalId !== undefined,
|
|
633
|
-
0x492 /* No cluster was created when overflow occurred. */,
|
|
634
|
-
);
|
|
635
|
-
cluster = newCluster;
|
|
636
|
-
overriddenFinal = (newBaseFinalId +
|
|
637
|
-
(localIdPivot - overriddenLocal)) as FinalCompressedId;
|
|
638
|
-
} else {
|
|
639
|
-
// Override was finalized into an existing cluster
|
|
640
|
-
assert(
|
|
641
|
-
currentCluster !== undefined && currentBaseFinalId !== undefined,
|
|
642
|
-
0x493 /* No cluster exists but IDs were finalized. */,
|
|
643
|
-
);
|
|
644
|
-
cluster = currentCluster;
|
|
645
|
-
overriddenFinal = (currentBaseFinalId +
|
|
646
|
-
initialClusterCount +
|
|
647
|
-
(normalizedLastFinalizedLocal - overriddenLocal) -
|
|
648
|
-
1) as FinalCompressedId;
|
|
649
|
-
}
|
|
650
|
-
cluster.overrides ??= new Map();
|
|
651
|
-
|
|
652
|
-
const inversionKey = IdCompressor.createInversionKey(override);
|
|
653
|
-
const existingIds = this.getExistingIdsForNewOverride(inversionKey, true);
|
|
654
|
-
let overrideForCluster: string | FinalCompressedId;
|
|
655
|
-
let associatedLocal: LocalCompressedId | undefined;
|
|
656
|
-
if (existingIds !== undefined) {
|
|
657
|
-
let mostFinalExistingOverride: CompressedId;
|
|
658
|
-
if (typeof existingIds === "number") {
|
|
659
|
-
mostFinalExistingOverride = existingIds;
|
|
660
|
-
if (isLocalId(mostFinalExistingOverride)) {
|
|
661
|
-
associatedLocal = mostFinalExistingOverride;
|
|
662
|
-
}
|
|
663
|
-
} else {
|
|
664
|
-
[associatedLocal, mostFinalExistingOverride] = existingIds;
|
|
665
|
-
}
|
|
666
|
-
if (isFinalId(mostFinalExistingOverride)) {
|
|
667
|
-
// A previous range already finalized an ID with this override. See `IdCluster` for more.
|
|
668
|
-
overrideForCluster = mostFinalExistingOverride;
|
|
669
|
-
} else {
|
|
670
|
-
assert(
|
|
671
|
-
!isLocal || mostFinalExistingOverride === overriddenLocal,
|
|
672
|
-
0x494 /* Cannot have multiple local IDs with identical overrides. */,
|
|
673
|
-
);
|
|
674
|
-
// This session has created an ID with this override before, but has not finalized it yet. The incoming
|
|
675
|
-
// range "wins" and will contain the final ID associated with that override, regardless of if that range was
|
|
676
|
-
// made by this session or not.
|
|
677
|
-
overrideForCluster = override;
|
|
678
|
-
}
|
|
679
|
-
} else {
|
|
680
|
-
// This is the first time this override has been associated with any ID
|
|
681
|
-
overrideForCluster = override;
|
|
682
|
-
}
|
|
683
|
-
|
|
247
|
+
if (remainingCapacity >= count) {
|
|
248
|
+
// The current range fits in the existing cluster
|
|
249
|
+
lastCluster.count += count;
|
|
250
|
+
} else {
|
|
251
|
+
const overflow = count - remainingCapacity;
|
|
252
|
+
const newClaimedFinalCount = overflow + this.clusterCapacity;
|
|
253
|
+
if (lastCluster === this.finalSpace.getLastCluster()) {
|
|
254
|
+
// The last cluster in the sessions chain is the last cluster globally, so it can be expanded.
|
|
255
|
+
lastCluster.capacity += newClaimedFinalCount;
|
|
256
|
+
lastCluster.count += count;
|
|
684
257
|
assert(
|
|
685
|
-
!
|
|
686
|
-
|
|
258
|
+
!this.sessions.clusterCollides(lastCluster),
|
|
259
|
+
0x756 /* Cluster collision detected. */,
|
|
687
260
|
);
|
|
688
|
-
if (
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
});
|
|
697
|
-
}
|
|
698
|
-
} else {
|
|
699
|
-
const unifiedOverride: UnifiedOverride = {
|
|
700
|
-
override,
|
|
701
|
-
originalOverridingFinal: overrideForCluster,
|
|
702
|
-
};
|
|
703
|
-
setPropertyIfDefined(associatedLocal, unifiedOverride, "associatedLocalId");
|
|
704
|
-
cluster.overrides.set(overriddenFinal, unifiedOverride);
|
|
261
|
+
if (isLocal) {
|
|
262
|
+
this.logger?.sendTelemetryEvent({
|
|
263
|
+
eventName: "RuntimeIdCompressor:ClusterExpansion",
|
|
264
|
+
sessionId: this.localSessionId,
|
|
265
|
+
previousCapacity: lastCluster.capacity - newClaimedFinalCount,
|
|
266
|
+
newCapacity: lastCluster.capacity,
|
|
267
|
+
overflow,
|
|
268
|
+
});
|
|
705
269
|
}
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
// Update the map to contain a finalized override, but never update it with future finalized overrides with
|
|
717
|
-
// the same string; those should decompress to the first final ID with that override.
|
|
718
|
-
this.clustersAndOverridesInversion.set(inversionKey, finalizedOverride);
|
|
270
|
+
} else {
|
|
271
|
+
// The last cluster in the sessions chain is *not* the last cluster globally. Fill and overflow to new.
|
|
272
|
+
lastCluster.count = lastCluster.capacity;
|
|
273
|
+
const newCluster = this.addEmptyCluster(session, newClaimedFinalCount);
|
|
274
|
+
newCluster.count += overflow;
|
|
275
|
+
if (isLocal) {
|
|
276
|
+
this.logger?.sendTelemetryEvent({
|
|
277
|
+
eventName: "RuntimeIdCompressor:NewCluster",
|
|
278
|
+
sessionId: this.localSessionId,
|
|
279
|
+
});
|
|
719
280
|
}
|
|
720
281
|
}
|
|
721
282
|
}
|
|
@@ -723,1133 +284,346 @@ export class IdCompressor implements IIdCompressorCore, IIdCompressor {
|
|
|
723
284
|
if (isLocal) {
|
|
724
285
|
this.logger?.sendTelemetryEvent({
|
|
725
286
|
eventName: "RuntimeIdCompressor:IdCompressorStatus",
|
|
726
|
-
eagerFinalIdCount:
|
|
727
|
-
localIdCount:
|
|
728
|
-
overridesCount: overrides?.length ?? 0,
|
|
287
|
+
eagerFinalIdCount: this.telemetryEagerFinalIdCount,
|
|
288
|
+
localIdCount: this.telemetryLocalIdCount,
|
|
729
289
|
sessionId: this.localSessionId,
|
|
730
290
|
});
|
|
291
|
+
this.telemetryEagerFinalIdCount = 0;
|
|
292
|
+
this.telemetryLocalIdCount = 0;
|
|
731
293
|
}
|
|
732
294
|
|
|
733
|
-
session.
|
|
734
|
-
}
|
|
735
|
-
|
|
736
|
-
private checkClusterForCollision(cluster: IdCluster): void {
|
|
737
|
-
const maxClusterUuid = incrementUuid(cluster.baseUuid, cluster.capacity - 1);
|
|
738
|
-
const maxClusterStableId = stableIdFromNumericUuid(maxClusterUuid);
|
|
739
|
-
const closestMatch =
|
|
740
|
-
this.clustersAndOverridesInversion.getPairOrNextLower(maxClusterStableId);
|
|
741
|
-
if (closestMatch !== undefined) {
|
|
742
|
-
const [inversionKey, compressionMapping] = closestMatch;
|
|
743
|
-
if (!IdCompressor.isClusterInfo(compressionMapping)) {
|
|
744
|
-
if (
|
|
745
|
-
isStableId(inversionKey) &&
|
|
746
|
-
IdCompressor.uuidsMightCollide(
|
|
747
|
-
inversionKey,
|
|
748
|
-
maxClusterStableId,
|
|
749
|
-
cluster.capacity,
|
|
750
|
-
)
|
|
751
|
-
) {
|
|
752
|
-
const numericOverride = numericUuidFromStableId(inversionKey);
|
|
753
|
-
const delta = getPositiveDelta(
|
|
754
|
-
maxClusterUuid,
|
|
755
|
-
numericOverride,
|
|
756
|
-
cluster.capacity - 1,
|
|
757
|
-
);
|
|
758
|
-
if (delta !== undefined) {
|
|
759
|
-
IdCompressor.failWithCollidingOverride(inversionKey);
|
|
760
|
-
}
|
|
761
|
-
}
|
|
762
|
-
}
|
|
763
|
-
}
|
|
764
|
-
}
|
|
765
|
-
|
|
766
|
-
private static failWithCollidingOverride(override: string): void {
|
|
767
|
-
fail(`Override '${override}' collides with another allocated UUID.`);
|
|
768
|
-
}
|
|
769
|
-
|
|
770
|
-
private static isClusterInfo(
|
|
771
|
-
compressionMapping: CompressionMapping,
|
|
772
|
-
): compressionMapping is ClusterInfo {
|
|
773
|
-
return (compressionMapping as ClusterInfo).clusterBase !== undefined;
|
|
774
|
-
}
|
|
775
|
-
|
|
776
|
-
private static isUnfinalizedOverride(
|
|
777
|
-
compressionMapping: CompressionMapping,
|
|
778
|
-
): compressionMapping is UnackedLocalId {
|
|
779
|
-
return typeof compressionMapping === "number";
|
|
780
|
-
}
|
|
781
|
-
|
|
782
|
-
private static createInversionKey(inversionKey: string): InversionKey {
|
|
783
|
-
return isStableId(inversionKey)
|
|
784
|
-
? inversionKey
|
|
785
|
-
: `${nonStableOverridePrefix}${inversionKey}`;
|
|
786
|
-
}
|
|
787
|
-
|
|
788
|
-
private static isStableInversionKey(inversionKey: InversionKey): inversionKey is StableId {
|
|
789
|
-
return inversionKey.charAt(0) !== nonStableOverridePrefix;
|
|
295
|
+
assert(!session.isEmpty(), 0x757 /* Empty sessions should not be created. */);
|
|
790
296
|
}
|
|
791
297
|
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
isFinalOverride: boolean,
|
|
798
|
-
): SessionSpaceCompressedId | [LocalCompressedId, FinalCompressedId] | undefined {
|
|
799
|
-
const closestMatch = this.clustersAndOverridesInversion.getPairOrNextLower(
|
|
800
|
-
inversionKey,
|
|
801
|
-
reusedArray,
|
|
298
|
+
private addEmptyCluster(session: Session, capacity: number): IdCluster {
|
|
299
|
+
const newCluster = session.addNewCluster(
|
|
300
|
+
this.finalSpace.getAllocatedIdLimit(),
|
|
301
|
+
capacity,
|
|
302
|
+
0,
|
|
802
303
|
);
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
const [key, compressionMapping] = closestMatch;
|
|
807
|
-
if (!IdCompressor.isClusterInfo(compressionMapping)) {
|
|
808
|
-
if (key === inversionKey) {
|
|
809
|
-
if (IdCompressor.isUnfinalizedOverride(compressionMapping)) {
|
|
810
|
-
return compressionMapping;
|
|
811
|
-
}
|
|
812
|
-
const finalizedOverride = compressionMapping;
|
|
813
|
-
return finalizedOverride.associatedLocalId !== undefined
|
|
814
|
-
? [
|
|
815
|
-
finalizedOverride.associatedLocalId,
|
|
816
|
-
finalizedOverride.originalOverridingFinal,
|
|
817
|
-
]
|
|
818
|
-
: (finalizedOverride.originalOverridingFinal as SessionSpaceCompressedId);
|
|
819
|
-
}
|
|
820
|
-
} else if (IdCompressor.isStableInversionKey(inversionKey)) {
|
|
821
|
-
stableOverride = inversionKey;
|
|
822
|
-
const cluster = compressionMapping.cluster;
|
|
823
|
-
if (
|
|
824
|
-
IdCompressor.uuidsMightCollide(inversionKey, key as StableId, cluster.capacity)
|
|
825
|
-
) {
|
|
826
|
-
numericOverride = numericUuidFromStableId(stableOverride);
|
|
827
|
-
const delta = getPositiveDelta(
|
|
828
|
-
numericOverride,
|
|
829
|
-
cluster.baseUuid,
|
|
830
|
-
cluster.capacity - 1,
|
|
831
|
-
);
|
|
832
|
-
if (delta !== undefined) {
|
|
833
|
-
if (!isFinalOverride) {
|
|
834
|
-
if (delta >= cluster.count) {
|
|
835
|
-
// TODO:#283: Properly implement unification
|
|
836
|
-
return undefined;
|
|
837
|
-
}
|
|
838
|
-
return this.normalizeToSessionSpace(
|
|
839
|
-
(compressionMapping.clusterBase + delta) as FinalCompressedId,
|
|
840
|
-
);
|
|
841
|
-
}
|
|
842
|
-
}
|
|
843
|
-
}
|
|
844
|
-
}
|
|
845
|
-
}
|
|
846
|
-
|
|
847
|
-
const override =
|
|
848
|
-
numericOverride ??
|
|
849
|
-
stableOverride ??
|
|
850
|
-
(IdCompressor.isStableInversionKey(inversionKey) ? inversionKey : undefined);
|
|
851
|
-
|
|
852
|
-
if (override !== undefined) {
|
|
853
|
-
const sessionSpaceId = this.getCompressedIdForStableId(override);
|
|
854
|
-
if (sessionSpaceId !== undefined) {
|
|
855
|
-
return sessionSpaceId;
|
|
856
|
-
}
|
|
857
|
-
}
|
|
858
|
-
|
|
859
|
-
return undefined;
|
|
860
|
-
}
|
|
861
|
-
|
|
862
|
-
/**
|
|
863
|
-
* Check if `a` might be within `range` of `b`, where both are treated as hex numbers.
|
|
864
|
-
* @param range - an integer
|
|
865
|
-
*/
|
|
866
|
-
private static uuidsMightCollide(a: StableId, b: StableId, range: number): boolean {
|
|
867
|
-
// Check if any of the UUIDs in the cluster collide (i.e. any in [base, base + capacity)).
|
|
868
|
-
// Optimization: All UUIDs in a cluster are the same string up until the last few characters which encode the offset from
|
|
869
|
-
// the cluster base. So, first compute the length of that shared string, and early out if it is different from the override
|
|
870
|
-
// UUID. This way we usually need not do the more expensive check below.
|
|
871
|
-
const hexDigitsToCheck = 32 - Math.ceil(Math.log2(range) / 2);
|
|
872
|
-
if (a.startsWith(b.slice(0, hexDigitsToCheck))) {
|
|
873
|
-
return true;
|
|
874
|
-
}
|
|
875
|
-
|
|
876
|
-
return false;
|
|
304
|
+
assert(!this.sessions.clusterCollides(newCluster), 0x758 /* Cluster collision detected. */);
|
|
305
|
+
this.finalSpace.addCluster(newCluster);
|
|
306
|
+
return newCluster;
|
|
877
307
|
}
|
|
878
308
|
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
private static tryGetOverride(
|
|
883
|
-
cluster: IdCluster,
|
|
884
|
-
finalId: FinalCompressedId,
|
|
885
|
-
): string | undefined {
|
|
886
|
-
const override = cluster.overrides?.get(finalId);
|
|
887
|
-
if (override === undefined) {
|
|
888
|
-
return undefined;
|
|
889
|
-
}
|
|
890
|
-
if (typeof override === "string") {
|
|
891
|
-
return override;
|
|
892
|
-
}
|
|
893
|
-
return override.override;
|
|
894
|
-
}
|
|
895
|
-
|
|
896
|
-
/**
|
|
897
|
-
* Generates a new compressed ID or returns an existing one.
|
|
898
|
-
* This should ONLY be called to generate IDs for local operations.
|
|
899
|
-
* @param override - Specifies a specific string to be associated with the returned compressed ID.
|
|
900
|
-
* Performance note: assigning override strings incurs a performance overhead.
|
|
901
|
-
* @returns an existing ID if one already exists for `override`, and a new local ID otherwise. The returned ID is in session space.
|
|
902
|
-
*/
|
|
903
|
-
public generateCompressedId(override?: string): SessionSpaceCompressedId {
|
|
904
|
-
let overrideInversionKey: InversionKey | undefined;
|
|
905
|
-
if (override !== undefined) {
|
|
906
|
-
overrideInversionKey = IdCompressor.createInversionKey(override);
|
|
907
|
-
const existingIds = this.getExistingIdsForNewOverride(overrideInversionKey, false);
|
|
908
|
-
if (existingIds !== undefined) {
|
|
909
|
-
return typeof existingIds === "number" ? existingIds : existingIds[0];
|
|
910
|
-
}
|
|
911
|
-
}
|
|
912
|
-
|
|
913
|
-
// Bump local counter regardless, then attempt to optimistically return a final ID.
|
|
914
|
-
// If the local session has reserved a cluster range via consensus, it is safe to hand out final IDs prior to
|
|
915
|
-
// finalizing the range that includes these locals.
|
|
916
|
-
const newLocalId = -++this.localIdCount as LocalCompressedId;
|
|
917
|
-
const { currentClusterDetails } = this.localSession;
|
|
918
|
-
const { sessionIdNormalizer } = this;
|
|
919
|
-
let eagerFinalId: (FinalCompressedId & SessionSpaceCompressedId) | undefined;
|
|
920
|
-
let cluster: IdCluster | undefined;
|
|
921
|
-
if (currentClusterDetails !== undefined) {
|
|
922
|
-
cluster = currentClusterDetails.cluster;
|
|
923
|
-
const lastFinalKnown = sessionIdNormalizer.getLastFinalId();
|
|
924
|
-
if (
|
|
925
|
-
lastFinalKnown !== undefined &&
|
|
926
|
-
lastFinalKnown - currentClusterDetails.clusterBase + 1 < cluster.capacity
|
|
927
|
-
) {
|
|
928
|
-
eagerFinalId = (lastFinalKnown + 1) as FinalCompressedId & SessionSpaceCompressedId;
|
|
929
|
-
}
|
|
930
|
-
}
|
|
931
|
-
|
|
932
|
-
if (overrideInversionKey !== undefined) {
|
|
933
|
-
const registeredLocal = sessionIdNormalizer.addLocalId();
|
|
934
|
-
assert(
|
|
935
|
-
registeredLocal === newLocalId,
|
|
936
|
-
0x496 /* Session ID Normalizer produced unexpected local ID */,
|
|
937
|
-
);
|
|
938
|
-
if (eagerFinalId !== undefined) {
|
|
939
|
-
sessionIdNormalizer.addFinalIds(
|
|
940
|
-
eagerFinalId,
|
|
941
|
-
eagerFinalId,
|
|
942
|
-
cluster ?? fail("No cluster when generating compressed ID"),
|
|
943
|
-
);
|
|
944
|
-
}
|
|
945
|
-
this.localOverrides.append(newLocalId, override ?? fail("Override must be defined"));
|
|
946
|
-
// Since the local ID was just created, it is in both session and op space
|
|
947
|
-
const compressionMapping = newLocalId as UnackedLocalId;
|
|
948
|
-
this.clustersAndOverridesInversion.set(overrideInversionKey, compressionMapping);
|
|
949
|
-
} else if (eagerFinalId !== undefined) {
|
|
950
|
-
sessionIdNormalizer.addFinalIds(
|
|
951
|
-
eagerFinalId,
|
|
952
|
-
eagerFinalId,
|
|
953
|
-
cluster ?? fail("No cluster when generating compressed ID"),
|
|
954
|
-
);
|
|
955
|
-
return eagerFinalId;
|
|
309
|
+
public normalizeToOpSpace(id: SessionSpaceCompressedId): OpSpaceCompressedId {
|
|
310
|
+
if (isFinalId(id)) {
|
|
311
|
+
return id;
|
|
956
312
|
} else {
|
|
957
|
-
const
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
);
|
|
313
|
+
const local = id as unknown as LocalCompressedId;
|
|
314
|
+
if (!this.normalizer.contains(local)) {
|
|
315
|
+
throw new Error("Invalid ID to normalize.");
|
|
316
|
+
}
|
|
317
|
+
const finalForm = this.localSession.tryConvertToFinal(local, true);
|
|
318
|
+
return finalForm === undefined
|
|
319
|
+
? (local as unknown as OpSpaceCompressedId)
|
|
320
|
+
: (finalForm as OpSpaceCompressedId);
|
|
962
321
|
}
|
|
963
|
-
|
|
964
|
-
return newLocalId;
|
|
965
|
-
}
|
|
966
|
-
|
|
967
|
-
/**
|
|
968
|
-
* Decompresses a previously compressed ID into a UUID or override string.
|
|
969
|
-
* @param id - the compressed ID to be decompressed.
|
|
970
|
-
* @returns the UUID or override string associated with the compressed ID. Fails if the ID was not generated by this compressor.
|
|
971
|
-
*/
|
|
972
|
-
public decompress(id: SessionSpaceCompressedId | FinalCompressedId): StableId | string {
|
|
973
|
-
return this.tryDecompress(id) ?? fail("Compressed ID was not generated by this compressor");
|
|
974
322
|
}
|
|
975
323
|
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
*/
|
|
981
|
-
public tryDecompress(
|
|
982
|
-
id: SessionSpaceCompressedId | FinalCompressedId,
|
|
983
|
-
): StableId | string | undefined {
|
|
324
|
+
public normalizeToSessionSpace(
|
|
325
|
+
id: OpSpaceCompressedId,
|
|
326
|
+
originSessionId: SessionId,
|
|
327
|
+
): SessionSpaceCompressedId {
|
|
984
328
|
if (isFinalId(id)) {
|
|
985
|
-
const
|
|
986
|
-
if (
|
|
987
|
-
//
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
return stableIdFromNumericUuid(this.localSession.sessionUuid, creationIndex);
|
|
329
|
+
const containingCluster = this.localSession.getClusterByAllocatedFinal(id);
|
|
330
|
+
if (containingCluster === undefined) {
|
|
331
|
+
// Does not exist in local cluster chain
|
|
332
|
+
if (id >= this.finalSpace.getFinalizedIdLimit()) {
|
|
333
|
+
throw new Error("Unknown op space ID.");
|
|
991
334
|
}
|
|
992
|
-
return
|
|
335
|
+
return id as unknown as SessionSpaceCompressedId;
|
|
993
336
|
} else {
|
|
994
|
-
const
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
return override;
|
|
337
|
+
const alignedLocal = getAlignedLocal(containingCluster, id);
|
|
338
|
+
if (this.normalizer.contains(alignedLocal)) {
|
|
339
|
+
return alignedLocal;
|
|
998
340
|
} else {
|
|
999
|
-
|
|
1000
|
-
|
|
341
|
+
if (genCountFromLocalId(alignedLocal) > this.localGenCount) {
|
|
342
|
+
throw new Error("Unknown op space ID.");
|
|
343
|
+
}
|
|
344
|
+
return id as unknown as SessionSpaceCompressedId;
|
|
1001
345
|
}
|
|
1002
346
|
}
|
|
1003
347
|
} else {
|
|
1004
|
-
const
|
|
1005
|
-
if (
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
// `localOverrides`s. Otherwise, it is a sequential allocation from the session UUID and can simply be negated and
|
|
1012
|
-
// added to that UUID to obtain the stable ID associated with it.
|
|
1013
|
-
const localOverride = this.localOverrides?.get(id);
|
|
1014
|
-
return localOverride !== undefined
|
|
1015
|
-
? localOverride
|
|
1016
|
-
: stableIdFromNumericUuid(this.localSession.sessionUuid, idOffset - 1);
|
|
1017
|
-
}
|
|
1018
|
-
}
|
|
1019
|
-
|
|
1020
|
-
/**
|
|
1021
|
-
* Recompresses a decompressed ID, which could be a UUID or an override string.
|
|
1022
|
-
* @param uncompressed - the UUID or override string to recompress.
|
|
1023
|
-
* @returns the `CompressedId` associated with `uncompressed`. Fails if it has not been previously compressed by this compressor.
|
|
1024
|
-
*/
|
|
1025
|
-
public recompress(uncompressed: string): SessionSpaceCompressedId {
|
|
1026
|
-
return this.tryRecompress(uncompressed) ?? fail("No such string has ever been compressed");
|
|
1027
|
-
}
|
|
1028
|
-
|
|
1029
|
-
/**
|
|
1030
|
-
* Attempts to recompresses a decompressed ID, which could be a UUID or an override string.
|
|
1031
|
-
* @param uncompressed - the UUID or override string to recompress,
|
|
1032
|
-
* @returns the `CompressedId` associated with `uncompressed` or undefined if it has not been previously compressed by this compressor.
|
|
1033
|
-
*/
|
|
1034
|
-
public tryRecompress(uncompressed: string): SessionSpaceCompressedId | undefined {
|
|
1035
|
-
return this.recompressInternal(uncompressed);
|
|
1036
|
-
}
|
|
1037
|
-
|
|
1038
|
-
/**
|
|
1039
|
-
* Helper to compress an uncompressed UUID. It can optionally be supplied with the numeric form of `uncompressedUuid` as a
|
|
1040
|
-
* performance optimization.
|
|
1041
|
-
*/
|
|
1042
|
-
private recompressInternal(
|
|
1043
|
-
uncompressed: string,
|
|
1044
|
-
uncompressedUuidNumeric?: NumericUuid,
|
|
1045
|
-
): SessionSpaceCompressedId | undefined {
|
|
1046
|
-
let numericUuid = uncompressedUuidNumeric;
|
|
1047
|
-
const inversionKey = IdCompressor.createInversionKey(uncompressed);
|
|
1048
|
-
const isStable = IdCompressor.isStableInversionKey(inversionKey);
|
|
1049
|
-
const closestMatch = this.clustersAndOverridesInversion.getPairOrNextLower(
|
|
1050
|
-
inversionKey,
|
|
1051
|
-
reusedArray,
|
|
1052
|
-
);
|
|
1053
|
-
if (closestMatch !== undefined) {
|
|
1054
|
-
const [key, compressionMapping] = closestMatch;
|
|
1055
|
-
if (!IdCompressor.isClusterInfo(compressionMapping)) {
|
|
1056
|
-
if (key === inversionKey) {
|
|
1057
|
-
return IdCompressor.isUnfinalizedOverride(compressionMapping)
|
|
1058
|
-
? compressionMapping
|
|
1059
|
-
: compressionMapping.associatedLocalId ??
|
|
1060
|
-
(compressionMapping.originalOverridingFinal as SessionSpaceCompressedId);
|
|
348
|
+
const localToNormalize = id as unknown as LocalCompressedId;
|
|
349
|
+
if (originSessionId === this.localSessionId) {
|
|
350
|
+
if (this.normalizer.contains(localToNormalize)) {
|
|
351
|
+
return localToNormalize;
|
|
352
|
+
} else {
|
|
353
|
+
// We never generated this local ID, so fail
|
|
354
|
+
throw new Error("Unknown op space ID.");
|
|
1061
355
|
}
|
|
1062
356
|
} else {
|
|
1063
|
-
|
|
1064
|
-
|
|
357
|
+
// LocalId from a remote session
|
|
358
|
+
const remoteSession = this.sessions.get(originSessionId);
|
|
359
|
+
if (remoteSession === undefined) {
|
|
360
|
+
throw new Error("No IDs have ever been finalized by the supplied session.");
|
|
1065
361
|
}
|
|
1066
|
-
const
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
const uuidOffset = getPositiveDelta(
|
|
1070
|
-
numericUuid,
|
|
1071
|
-
closestCluster.baseUuid,
|
|
1072
|
-
closestCluster.count - 1,
|
|
1073
|
-
);
|
|
1074
|
-
if (uuidOffset !== undefined) {
|
|
1075
|
-
let targetFinalId = (closestBaseFinalId + uuidOffset) as FinalCompressedId;
|
|
1076
|
-
const override = closestCluster.overrides?.get(targetFinalId);
|
|
1077
|
-
if (typeof override === "object") {
|
|
1078
|
-
if (override.associatedLocalId !== undefined) {
|
|
1079
|
-
return override.associatedLocalId;
|
|
1080
|
-
}
|
|
1081
|
-
// This may be a UUID that should actually compress into a different final ID that it aligns with, due to
|
|
1082
|
-
// another session having an identical override (see `IdCluster` for more).
|
|
1083
|
-
targetFinalId = override.originalOverridingFinal;
|
|
1084
|
-
}
|
|
1085
|
-
return this.normalizeToSessionSpace(targetFinalId);
|
|
362
|
+
const correspondingFinal = remoteSession.tryConvertToFinal(localToNormalize, false);
|
|
363
|
+
if (correspondingFinal === undefined) {
|
|
364
|
+
throw new Error("Unknown op space ID.");
|
|
1086
365
|
}
|
|
366
|
+
return correspondingFinal as unknown as SessionSpaceCompressedId;
|
|
1087
367
|
}
|
|
1088
368
|
}
|
|
1089
|
-
|
|
1090
|
-
if (isStable) {
|
|
1091
|
-
// May have already computed the numeric UUID, so avoid recomputing if possible
|
|
1092
|
-
const sessionSpaceId = this.getCompressedIdForStableId(numericUuid ?? inversionKey);
|
|
1093
|
-
if (sessionSpaceId !== undefined) {
|
|
1094
|
-
return sessionSpaceId;
|
|
1095
|
-
}
|
|
1096
|
-
}
|
|
1097
|
-
return undefined;
|
|
1098
369
|
}
|
|
1099
370
|
|
|
1100
|
-
|
|
1101
|
-
* Normalizes a session space ID into op space.
|
|
1102
|
-
* @param id - the local ID to normalize.
|
|
1103
|
-
* @returns the ID in op space.
|
|
1104
|
-
*/
|
|
1105
|
-
public normalizeToOpSpace(id: SessionSpaceCompressedId): OpSpaceCompressedId {
|
|
371
|
+
public decompress(id: SessionSpaceCompressedId): StableId {
|
|
1106
372
|
if (isFinalId(id)) {
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
const inversionKey = IdCompressor.createInversionKey(override);
|
|
1126
|
-
const compressionMapping =
|
|
1127
|
-
this.clustersAndOverridesInversion.get(inversionKey) ??
|
|
1128
|
-
fail("Bimap is malformed.");
|
|
1129
|
-
return !IdCompressor.isClusterInfo(compressionMapping) &&
|
|
1130
|
-
!IdCompressor.isUnfinalizedOverride(compressionMapping) &&
|
|
1131
|
-
compressionMapping.associatedLocalId === id
|
|
1132
|
-
? compressionMapping.originalOverridingFinal
|
|
1133
|
-
: (id as OpSpaceCompressedId);
|
|
1134
|
-
}
|
|
1135
|
-
const possibleFinal = this.sessionIdNormalizer.getFinalId(id);
|
|
1136
|
-
return possibleFinal?.[0] ?? (id as OpSpaceCompressedId);
|
|
1137
|
-
}
|
|
1138
|
-
const [correspondingFinal, cluster] =
|
|
1139
|
-
this.sessionIdNormalizer.getFinalId(id) ??
|
|
1140
|
-
fail("Locally created cluster should be added to the map when allocated");
|
|
1141
|
-
if (cluster.overrides) {
|
|
1142
|
-
const override = cluster.overrides.get(correspondingFinal);
|
|
1143
|
-
if (typeof override === "object" && override.originalOverridingFinal !== undefined) {
|
|
1144
|
-
// Rare case of two local IDs with same overrides are created concurrently. See `IdCluster` for more.
|
|
1145
|
-
return override.originalOverridingFinal;
|
|
1146
|
-
}
|
|
1147
|
-
}
|
|
1148
|
-
return correspondingFinal;
|
|
1149
|
-
}
|
|
1150
|
-
|
|
1151
|
-
/**
|
|
1152
|
-
* Normalizes an ID into session space.
|
|
1153
|
-
* @param id - the ID to normalize. If it is a local ID, it is assumed to have been created by the session corresponding
|
|
1154
|
-
* to `sessionId`.
|
|
1155
|
-
* @param originSessionId - the session from which `id` originated
|
|
1156
|
-
* @returns the session-space ID corresponding to `id`, which might not have been a final ID if the client that created it had not yet
|
|
1157
|
-
* finalized it. This can occur when a client references an ID during the window of time in which it is waiting to receive the ordered
|
|
1158
|
-
* range that contained it from the server.
|
|
1159
|
-
*/
|
|
1160
|
-
public normalizeToSessionSpace(
|
|
1161
|
-
id: OpSpaceCompressedId,
|
|
1162
|
-
originSessionId: SessionId,
|
|
1163
|
-
): SessionSpaceCompressedId;
|
|
1164
|
-
|
|
1165
|
-
/**
|
|
1166
|
-
* Normalizes a final ID into session space.
|
|
1167
|
-
* @param id - the final ID to normalize.
|
|
1168
|
-
* @returns the session-space ID corresponding to `id`.
|
|
1169
|
-
*/
|
|
1170
|
-
public normalizeToSessionSpace(id: FinalCompressedId): SessionSpaceCompressedId;
|
|
1171
|
-
|
|
1172
|
-
public normalizeToSessionSpace(
|
|
1173
|
-
id: OpSpaceCompressedId,
|
|
1174
|
-
sessionIdIfLocal?: SessionId,
|
|
1175
|
-
): SessionSpaceCompressedId {
|
|
1176
|
-
if (isLocalId(id)) {
|
|
1177
|
-
if (sessionIdIfLocal === undefined || sessionIdIfLocal === this.localSessionId) {
|
|
1178
|
-
const localIndex = -id;
|
|
1179
|
-
if (localIndex > this.localIdCount) {
|
|
1180
|
-
fail("Supplied local ID was not created by this compressor.");
|
|
373
|
+
const containingCluster = Session.getContainingCluster(id, this.finalSpace.clusters);
|
|
374
|
+
if (containingCluster === undefined) {
|
|
375
|
+
throw new Error("Unknown ID");
|
|
376
|
+
}
|
|
377
|
+
const alignedLocal = getAlignedLocal(containingCluster, id);
|
|
378
|
+
const alignedGenCount = genCountFromLocalId(alignedLocal);
|
|
379
|
+
const lastFinalizedGenCount = genCountFromLocalId(
|
|
380
|
+
lastFinalizedLocal(containingCluster),
|
|
381
|
+
);
|
|
382
|
+
if (alignedGenCount > lastFinalizedGenCount) {
|
|
383
|
+
// should be an eager final id generated by the local session
|
|
384
|
+
if (containingCluster.session === this.localSession) {
|
|
385
|
+
assert(
|
|
386
|
+
!this.normalizer.contains(alignedLocal),
|
|
387
|
+
0x759 /* Normalizer out of sync. */,
|
|
388
|
+
);
|
|
389
|
+
} else {
|
|
390
|
+
throw new Error("Unknown ID");
|
|
1181
391
|
}
|
|
1182
|
-
return id;
|
|
1183
|
-
} else {
|
|
1184
|
-
const session =
|
|
1185
|
-
this.sessions.get(sessionIdIfLocal) ??
|
|
1186
|
-
fail("No IDs have ever been finalized by the supplied session.");
|
|
1187
|
-
const localCount = -id;
|
|
1188
|
-
const numericUuid = incrementUuid(session.sessionUuid, localCount - 1);
|
|
1189
|
-
return (
|
|
1190
|
-
this.compressNumericUuid(numericUuid) ??
|
|
1191
|
-
fail("ID is not known to this compressor.")
|
|
1192
|
-
);
|
|
1193
392
|
}
|
|
1194
|
-
}
|
|
1195
|
-
|
|
1196
|
-
const normalizedId = this.sessionIdNormalizer.getSessionSpaceId(id);
|
|
1197
|
-
if (normalizedId !== undefined) {
|
|
1198
|
-
return normalizedId;
|
|
1199
|
-
}
|
|
1200
393
|
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
394
|
+
return stableIdFromNumericUuid(
|
|
395
|
+
offsetNumericUuid(containingCluster.session.sessionUuid, alignedGenCount - 1),
|
|
396
|
+
);
|
|
397
|
+
} else {
|
|
398
|
+
const localToDecompress = id as unknown as LocalCompressedId;
|
|
399
|
+
if (!this.normalizer.contains(localToDecompress)) {
|
|
400
|
+
throw new Error("Unknown ID");
|
|
401
|
+
}
|
|
402
|
+
return stableIdFromNumericUuid(
|
|
403
|
+
offsetNumericUuid(
|
|
404
|
+
this.localSession.sessionUuid,
|
|
405
|
+
genCountFromLocalId(localToDecompress) - 1,
|
|
406
|
+
),
|
|
407
|
+
);
|
|
1209
408
|
}
|
|
1210
|
-
return id as SessionSpaceCompressedId;
|
|
1211
409
|
}
|
|
1212
410
|
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
* *and* that override UUID was concurrently used in an older ID (earlier, w.r.t. sequencing), this method can return the first
|
|
1218
|
-
* ID to correspond to that override.
|
|
1219
|
-
*
|
|
1220
|
-
* As an example, consider the following two clients:
|
|
1221
|
-
* ClientA, session UUID: A0000000-0000-0000-0000-000000000000
|
|
1222
|
-
* ClientB, session UUID: B0000000-0000-0000-0000-000000000000
|
|
1223
|
-
*
|
|
1224
|
-
* If concurrently, two clients performed:
|
|
1225
|
-
* ClientA: generateCompressedId(override: 'X0000000-0000-0000-0000-000000000000') // aligned with A0000000-0000-0000-0000-000000000000
|
|
1226
|
-
*
|
|
1227
|
-
* ClientB: generateCompressedId() // aligned with B0000000-0000-0000-0000-000000000000
|
|
1228
|
-
* ClientB: generateCompressedId(override: 'X0000000-0000-0000-0000-000000000000') // aligned with B0000000-0000-0000-0000-000000000001
|
|
1229
|
-
*
|
|
1230
|
-
* After sequencing, calling this method and passing the numeric UUID for B0000000-0000-0000-0000-000000000001 would return the
|
|
1231
|
-
* session-space ID corresponding to A0000000-0000-0000-0000-000000000000 (with override X0000000-0000-0000-0000-000000000000).
|
|
1232
|
-
*/
|
|
1233
|
-
private compressNumericUuid(numericUuid: NumericUuid): SessionSpaceCompressedId | undefined {
|
|
1234
|
-
const stableId = stableIdFromNumericUuid(numericUuid);
|
|
1235
|
-
const sessionSpaceId = this.recompressInternal(stableId, numericUuid);
|
|
1236
|
-
if (sessionSpaceId === undefined) {
|
|
1237
|
-
return undefined;
|
|
411
|
+
public recompress(uncompressed: StableId): SessionSpaceCompressedId {
|
|
412
|
+
const recompressed = this.tryRecompress(uncompressed);
|
|
413
|
+
if (recompressed === undefined) {
|
|
414
|
+
throw new Error("Could not recompress.");
|
|
1238
415
|
}
|
|
1239
|
-
return
|
|
416
|
+
return recompressed;
|
|
1240
417
|
}
|
|
1241
418
|
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
this.localIdCount - 1,
|
|
1254
|
-
);
|
|
1255
|
-
if (creationIndex !== undefined) {
|
|
1256
|
-
const sessionSpaceId = this.sessionIdNormalizer.getIdByCreationIndex(creationIndex);
|
|
1257
|
-
if (sessionSpaceId !== undefined) {
|
|
1258
|
-
return sessionSpaceId;
|
|
419
|
+
public tryRecompress(uncompressed: StableId): SessionSpaceCompressedId | undefined {
|
|
420
|
+
const match = this.sessions.getContainingCluster(uncompressed);
|
|
421
|
+
if (match === undefined) {
|
|
422
|
+
const numericUncompressed = numericUuidFromStableId(uncompressed);
|
|
423
|
+
const offset = subtractNumericUuids(numericUncompressed, this.localSession.sessionUuid);
|
|
424
|
+
if (offset < Number.MAX_SAFE_INTEGER) {
|
|
425
|
+
const genCountEquivalent = Number(offset) + 1;
|
|
426
|
+
const localEquivalent = localIdFromGenCount(genCountEquivalent);
|
|
427
|
+
if (this.normalizer.contains(localEquivalent)) {
|
|
428
|
+
return localEquivalent;
|
|
429
|
+
}
|
|
1259
430
|
}
|
|
1260
|
-
}
|
|
1261
|
-
return undefined;
|
|
1262
|
-
}
|
|
1263
|
-
|
|
1264
|
-
private getClusterForFinalId(
|
|
1265
|
-
finalId: FinalCompressedId,
|
|
1266
|
-
): readonly [baseFinalId: FinalCompressedId, cluster: IdCluster] | undefined {
|
|
1267
|
-
const possibleCluster = this.finalIdToCluster.getPairOrNextLower(finalId);
|
|
1268
|
-
if (possibleCluster === undefined) {
|
|
1269
|
-
return undefined;
|
|
1270
|
-
}
|
|
1271
|
-
const [clusterBase, cluster] = possibleCluster;
|
|
1272
|
-
if (finalId - clusterBase >= cluster.count) {
|
|
1273
431
|
return undefined;
|
|
1274
|
-
}
|
|
1275
|
-
return possibleCluster;
|
|
1276
|
-
}
|
|
1277
|
-
|
|
1278
|
-
/**
|
|
1279
|
-
* @returns if `other` is equal to this `IdCompressor`. The equality check includes local session state only if specified.
|
|
1280
|
-
* \@testOnly
|
|
1281
|
-
*/
|
|
1282
|
-
public equals(other: IdCompressor, compareLocalState: boolean): boolean {
|
|
1283
|
-
if (compareLocalState) {
|
|
1284
|
-
if (
|
|
1285
|
-
this.localIdCount !== other.localIdCount ||
|
|
1286
|
-
this.localSessionId !== other.localSessionId ||
|
|
1287
|
-
this.lastTakenLocalId !== other.lastTakenLocalId
|
|
1288
|
-
) {
|
|
1289
|
-
return false;
|
|
1290
|
-
}
|
|
1291
|
-
if (!this.localOverrides.equals(other.localOverrides, (a, b) => a === b)) {
|
|
1292
|
-
return false;
|
|
1293
|
-
}
|
|
1294
|
-
if (
|
|
1295
|
-
!compareMaps(this.sessions, other.sessions, (a, b) =>
|
|
1296
|
-
IdCompressor.sessionDataEqual(a, b, true, compareLocalState),
|
|
1297
|
-
)
|
|
1298
|
-
) {
|
|
1299
|
-
return false;
|
|
1300
|
-
}
|
|
1301
|
-
if (
|
|
1302
|
-
!this.sessionIdNormalizer.equals(other.sessionIdNormalizer, (a, b) =>
|
|
1303
|
-
IdCompressor.idClustersEqual(a, b, false, compareLocalState),
|
|
1304
|
-
)
|
|
1305
|
-
) {
|
|
1306
|
-
return false;
|
|
1307
|
-
}
|
|
1308
432
|
} else {
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
const valueA = this.sessions.get(keyB);
|
|
1324
|
-
if (valueA === undefined) {
|
|
1325
|
-
if (valueB.lastFinalizedLocalId !== undefined) {
|
|
1326
|
-
return false;
|
|
1327
|
-
}
|
|
1328
|
-
}
|
|
1329
|
-
}
|
|
1330
|
-
}
|
|
1331
|
-
if (
|
|
1332
|
-
this.nextClusterBaseFinalId !== other.nextClusterBaseFinalId ||
|
|
1333
|
-
this.newClusterCapacity !== other.newClusterCapacity
|
|
1334
|
-
) {
|
|
1335
|
-
return false;
|
|
1336
|
-
}
|
|
1337
|
-
if (
|
|
1338
|
-
!this.finalIdToCluster.equals(other.finalIdToCluster, (a, b) =>
|
|
1339
|
-
IdCompressor.idClustersEqual(a, b, true, compareLocalState),
|
|
1340
|
-
)
|
|
1341
|
-
) {
|
|
1342
|
-
return false;
|
|
1343
|
-
}
|
|
1344
|
-
|
|
1345
|
-
const missingInOne = (
|
|
1346
|
-
_: string,
|
|
1347
|
-
value: CompressionMapping,
|
|
1348
|
-
): { break: boolean } | undefined => {
|
|
1349
|
-
if (!compareLocalState && IdCompressor.isUnfinalizedOverride(value)) {
|
|
1350
|
-
return undefined;
|
|
1351
|
-
}
|
|
1352
|
-
return { break: true };
|
|
1353
|
-
};
|
|
1354
|
-
|
|
1355
|
-
const compareCompressionMappings = (a: CompressionMapping, b: CompressionMapping) => {
|
|
1356
|
-
const unfinalizedA = IdCompressor.isUnfinalizedOverride(a);
|
|
1357
|
-
const unfinalizedB = IdCompressor.isUnfinalizedOverride(b);
|
|
1358
|
-
if (unfinalizedA) {
|
|
1359
|
-
if (unfinalizedB) {
|
|
1360
|
-
return a === b;
|
|
1361
|
-
}
|
|
1362
|
-
return false;
|
|
1363
|
-
} else if (unfinalizedB) {
|
|
1364
|
-
return false;
|
|
1365
|
-
}
|
|
1366
|
-
|
|
1367
|
-
if (IdCompressor.isClusterInfo(a)) {
|
|
1368
|
-
if (!IdCompressor.isClusterInfo(b) || a.clusterBase !== b.clusterBase) {
|
|
1369
|
-
return false;
|
|
433
|
+
const [containingCluster, alignedLocal] = match;
|
|
434
|
+
if (containingCluster.session === this.localSession) {
|
|
435
|
+
// Local session
|
|
436
|
+
if (this.normalizer.contains(alignedLocal)) {
|
|
437
|
+
return alignedLocal;
|
|
438
|
+
} else {
|
|
439
|
+
assert(
|
|
440
|
+
genCountFromLocalId(alignedLocal) <= this.localGenCount,
|
|
441
|
+
0x75a /* Clusters out of sync. */,
|
|
442
|
+
);
|
|
443
|
+
// Id is an eager final
|
|
444
|
+
return getAlignedFinal(containingCluster, alignedLocal) as
|
|
445
|
+
| SessionSpaceCompressedId
|
|
446
|
+
| undefined;
|
|
1370
447
|
}
|
|
1371
448
|
} else {
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
(
|
|
1375
|
-
|
|
1376
|
-
|
|
1377
|
-
|
|
1378
|
-
}
|
|
449
|
+
// Not the local session
|
|
450
|
+
return genCountFromLocalId(alignedLocal) >= lastFinalizedLocal(containingCluster)
|
|
451
|
+
? (getAlignedFinal(containingCluster, alignedLocal) as
|
|
452
|
+
| SessionSpaceCompressedId
|
|
453
|
+
| undefined)
|
|
454
|
+
: undefined;
|
|
1379
455
|
}
|
|
1380
|
-
if (!IdCompressor.idClustersEqual(a.cluster, b.cluster, true, compareLocalState)) {
|
|
1381
|
-
return false;
|
|
1382
|
-
}
|
|
1383
|
-
return true;
|
|
1384
|
-
};
|
|
1385
|
-
|
|
1386
|
-
const diff = this.clustersAndOverridesInversion.diffAgainst(
|
|
1387
|
-
other.clustersAndOverridesInversion,
|
|
1388
|
-
missingInOne,
|
|
1389
|
-
missingInOne,
|
|
1390
|
-
(_, valA, valB) => {
|
|
1391
|
-
if (!compareCompressionMappings(valA, valB)) {
|
|
1392
|
-
return { break: true };
|
|
1393
|
-
}
|
|
1394
|
-
return undefined;
|
|
1395
|
-
},
|
|
1396
|
-
);
|
|
1397
|
-
|
|
1398
|
-
return diff === undefined;
|
|
1399
|
-
}
|
|
1400
|
-
|
|
1401
|
-
private static sessionDataEqual(
|
|
1402
|
-
a: Session,
|
|
1403
|
-
b: Session,
|
|
1404
|
-
checkCluster = true,
|
|
1405
|
-
compareLocalState = true,
|
|
1406
|
-
): boolean {
|
|
1407
|
-
if (
|
|
1408
|
-
!numericUuidEquals(a.sessionUuid, b.sessionUuid) ||
|
|
1409
|
-
a.lastFinalizedLocalId !== b.lastFinalizedLocalId
|
|
1410
|
-
) {
|
|
1411
|
-
return false;
|
|
1412
|
-
}
|
|
1413
|
-
if (a.currentClusterDetails === undefined || b.currentClusterDetails === undefined) {
|
|
1414
|
-
if (a.currentClusterDetails !== b.currentClusterDetails) {
|
|
1415
|
-
return false;
|
|
1416
|
-
}
|
|
1417
|
-
return true;
|
|
1418
|
-
}
|
|
1419
|
-
if (
|
|
1420
|
-
checkCluster &&
|
|
1421
|
-
!IdCompressor.idClustersEqual(
|
|
1422
|
-
a.currentClusterDetails.cluster,
|
|
1423
|
-
b.currentClusterDetails.cluster,
|
|
1424
|
-
false,
|
|
1425
|
-
compareLocalState,
|
|
1426
|
-
)
|
|
1427
|
-
) {
|
|
1428
|
-
return false;
|
|
1429
456
|
}
|
|
1430
|
-
return true;
|
|
1431
|
-
}
|
|
1432
|
-
|
|
1433
|
-
private static idClustersEqual(
|
|
1434
|
-
a: IdCluster,
|
|
1435
|
-
b: IdCluster,
|
|
1436
|
-
checkSessionData = true,
|
|
1437
|
-
compareLocalState = true,
|
|
1438
|
-
): boolean {
|
|
1439
|
-
const areEqual =
|
|
1440
|
-
numericUuidEquals(a.baseUuid, b.baseUuid) &&
|
|
1441
|
-
a.capacity === b.capacity &&
|
|
1442
|
-
a.count === b.count &&
|
|
1443
|
-
(!checkSessionData ||
|
|
1444
|
-
IdCompressor.sessionDataEqual(a.session, b.session, false, compareLocalState)) &&
|
|
1445
|
-
(a.overrides === undefined) === (b.overrides === undefined) &&
|
|
1446
|
-
(a.overrides === undefined ||
|
|
1447
|
-
compareMaps(
|
|
1448
|
-
a.overrides ?? fail("Overrides must be defined"),
|
|
1449
|
-
b.overrides ?? fail("Overrides must be defined"),
|
|
1450
|
-
(overrideA, overrideB) => {
|
|
1451
|
-
if (compareLocalState) {
|
|
1452
|
-
if (typeof overrideA === "string" || typeof overrideB === "string") {
|
|
1453
|
-
return overrideA === overrideB;
|
|
1454
|
-
}
|
|
1455
|
-
const overridesEqual =
|
|
1456
|
-
overrideA.override === overrideB.override &&
|
|
1457
|
-
overrideA.originalOverridingFinal ===
|
|
1458
|
-
overrideB.originalOverridingFinal &&
|
|
1459
|
-
(!compareLocalState ||
|
|
1460
|
-
overrideA.associatedLocalId === overrideB.associatedLocalId);
|
|
1461
|
-
return overridesEqual;
|
|
1462
|
-
}
|
|
1463
|
-
|
|
1464
|
-
const uuidA =
|
|
1465
|
-
typeof overrideA === "string" ? overrideA : overrideA.override;
|
|
1466
|
-
const uuidB =
|
|
1467
|
-
typeof overrideB === "string" ? overrideB : overrideB.override;
|
|
1468
|
-
if (
|
|
1469
|
-
typeof overrideA !== "string" &&
|
|
1470
|
-
typeof overrideB !== "string" &&
|
|
1471
|
-
overrideA.originalOverridingFinal !== overrideB.originalOverridingFinal
|
|
1472
|
-
) {
|
|
1473
|
-
return false;
|
|
1474
|
-
}
|
|
1475
|
-
return uuidA === uuidB;
|
|
1476
|
-
},
|
|
1477
|
-
));
|
|
1478
|
-
return areEqual;
|
|
1479
457
|
}
|
|
1480
458
|
|
|
1481
|
-
/**
|
|
1482
|
-
* Returns a persistable form of the current state of this `IdCompressor` which can be rehydrated via `IdCompressor.deserialize()`.
|
|
1483
|
-
* This includes finalized state as well as un-finalized state and is therefore suitable for use in offline scenarios.
|
|
1484
|
-
*/
|
|
1485
|
-
public serialize(
|
|
1486
|
-
withSession: boolean,
|
|
1487
|
-
): SerializedIdCompressorWithOngoingSession | SerializedIdCompressorWithNoSession;
|
|
1488
|
-
|
|
1489
|
-
/**
|
|
1490
|
-
* Returns a persistable form of the current state of this `IdCompressor` which can be rehydrated via `IdCompressor.deserialize()`.
|
|
1491
|
-
* This includes finalized state as well as un-finalized state and is therefore suitable for use in offline scenarios.
|
|
1492
|
-
*/
|
|
1493
459
|
public serialize(withSession: true): SerializedIdCompressorWithOngoingSession;
|
|
1494
|
-
|
|
1495
|
-
/**
|
|
1496
|
-
* Returns a persistable form of the current state of this `IdCompressor` which can be rehydrated via `IdCompressor.deserialize()`.
|
|
1497
|
-
* This only includes finalized state and is therefore suitable for use in summaries.
|
|
1498
|
-
*/
|
|
1499
460
|
public serialize(withSession: false): SerializedIdCompressorWithNoSession;
|
|
1500
|
-
|
|
1501
|
-
|
|
1502
|
-
const
|
|
1503
|
-
|
|
1504
|
-
|
|
1505
|
-
|
|
1506
|
-
|
|
1507
|
-
|
|
1508
|
-
|
|
1509
|
-
|
|
1510
|
-
|
|
1511
|
-
|
|
1512
|
-
|
|
1513
|
-
|
|
1514
|
-
|
|
1515
|
-
|
|
1516
|
-
|
|
1517
|
-
|
|
1518
|
-
|
|
1519
|
-
|
|
1520
|
-
|
|
1521
|
-
|
|
1522
|
-
|
|
1523
|
-
|
|
1524
|
-
|
|
1525
|
-
|
|
1526
|
-
|
|
1527
|
-
|
|
1528
|
-
|
|
1529
|
-
|
|
1530
|
-
|
|
1531
|
-
|
|
1532
|
-
|
|
1533
|
-
|
|
1534
|
-
|
|
1535
|
-
|
|
1536
|
-
|
|
1537
|
-
|
|
1538
|
-
|
|
1539
|
-
|
|
1540
|
-
|
|
1541
|
-
|
|
1542
|
-
|
|
1543
|
-
|
|
1544
|
-
|
|
1545
|
-
|
|
1546
|
-
override.override,
|
|
1547
|
-
override.originalOverridingFinal,
|
|
1548
|
-
]);
|
|
1549
|
-
}
|
|
1550
|
-
}
|
|
1551
|
-
serializedCluster.push(serializedOverrides);
|
|
1552
|
-
}
|
|
1553
|
-
|
|
1554
|
-
serializedClusters.push(serializedCluster);
|
|
1555
|
-
}
|
|
1556
|
-
}
|
|
1557
|
-
|
|
1558
|
-
// Reserved session not serialized, and local session is present but may not make IDs
|
|
1559
|
-
assert(
|
|
1560
|
-
serializedSessions.length - this.sessions.size <= 2,
|
|
1561
|
-
0x498 /* session not serialized */,
|
|
1562
|
-
);
|
|
1563
|
-
|
|
1564
|
-
const serializedIdCompressor: Omit<
|
|
1565
|
-
SerializedIdCompressor,
|
|
1566
|
-
"_versionedSerializedIdCompressor"
|
|
1567
|
-
> = {
|
|
1568
|
-
version: currentWrittenVersion,
|
|
1569
|
-
clusterCapacity: this.clusterCapacity,
|
|
1570
|
-
sessions: serializedSessions,
|
|
1571
|
-
clusters: serializedClusters,
|
|
1572
|
-
};
|
|
1573
|
-
|
|
1574
|
-
if (withSession) {
|
|
1575
|
-
const serializedWithSession =
|
|
1576
|
-
serializedIdCompressor as Mutable<SerializedIdCompressorWithOngoingSession>;
|
|
1577
|
-
serializedWithSession.localSessionIndex = serializedWithSession.sessions.findIndex(
|
|
1578
|
-
([sessionId]) => sessionId === this.localSessionId,
|
|
461
|
+
public serialize(hasLocalState: boolean): SerializedIdCompressor {
|
|
462
|
+
const { normalizer, finalSpace, sessions } = this;
|
|
463
|
+
const sessionIndexMap = new Map<Session, number>();
|
|
464
|
+
let sessionIndex = 0;
|
|
465
|
+
for (const session of sessions.sessions()) {
|
|
466
|
+
// Filter empty sessions to prevent them accumulating in the serialized state
|
|
467
|
+
if (!session.isEmpty() || hasLocalState) {
|
|
468
|
+
sessionIndexMap.set(session, sessionIndex);
|
|
469
|
+
sessionIndex++;
|
|
470
|
+
}
|
|
471
|
+
}
|
|
472
|
+
const localStateSize = hasLocalState
|
|
473
|
+
? 1 + // generated ID count
|
|
474
|
+
1 + // next range base genCount
|
|
475
|
+
1 + // count of normalizer pairs
|
|
476
|
+
this.normalizer.idRanges.size * 2 // pairs
|
|
477
|
+
: 0;
|
|
478
|
+
// Layout size, in 8 byte increments
|
|
479
|
+
const totalSize =
|
|
480
|
+
1 + // version
|
|
481
|
+
1 + // hasLocalState
|
|
482
|
+
1 + // cluster capacity
|
|
483
|
+
1 + // session count
|
|
484
|
+
1 + // cluster count
|
|
485
|
+
sessionIndexMap.size * 2 + // session IDs
|
|
486
|
+
finalSpace.clusters.length * 3 + // clusters: (sessionIndex, capacity, count)[]
|
|
487
|
+
localStateSize; // local state, if present
|
|
488
|
+
|
|
489
|
+
const serializedFloat = new Float64Array(totalSize);
|
|
490
|
+
const serializedUint = new BigUint64Array(serializedFloat.buffer);
|
|
491
|
+
let index = 0;
|
|
492
|
+
index = writeNumber(serializedFloat, index, currentWrittenVersion);
|
|
493
|
+
index = writeBoolean(serializedFloat, index, hasLocalState);
|
|
494
|
+
index = writeNumber(serializedFloat, index, this.clusterCapacity);
|
|
495
|
+
index = writeNumber(serializedFloat, index, sessionIndexMap.size);
|
|
496
|
+
index = writeNumber(serializedFloat, index, finalSpace.clusters.length);
|
|
497
|
+
|
|
498
|
+
for (const [session] of sessionIndexMap.entries()) {
|
|
499
|
+
index = writeNumericUuid(serializedUint, index, session.sessionUuid);
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
finalSpace.clusters.forEach((cluster) => {
|
|
503
|
+
index = writeNumber(
|
|
504
|
+
serializedFloat,
|
|
505
|
+
index,
|
|
506
|
+
sessionIndexMap.get(cluster.session) as number,
|
|
1579
507
|
);
|
|
1580
|
-
|
|
1581
|
-
|
|
1582
|
-
|
|
1583
|
-
overrides: [...this.localOverrides.entries()].map((entry) => [...entry]),
|
|
1584
|
-
lastTakenLocalId: this.lastTakenLocalId,
|
|
1585
|
-
sessionNormalizer: this.sessionIdNormalizer.serialize(),
|
|
1586
|
-
};
|
|
1587
|
-
}
|
|
508
|
+
index = writeNumber(serializedFloat, index, cluster.capacity);
|
|
509
|
+
index = writeNumber(serializedFloat, index, cluster.count);
|
|
510
|
+
});
|
|
1588
511
|
|
|
1589
|
-
|
|
512
|
+
if (hasLocalState) {
|
|
513
|
+
index = writeNumber(serializedFloat, index, this.localGenCount);
|
|
514
|
+
index = writeNumber(serializedFloat, index, this.nextRangeBaseGenCount);
|
|
515
|
+
index = writeNumber(serializedFloat, index, normalizer.idRanges.size);
|
|
516
|
+
for (const [leadingGenCount, count] of normalizer.idRanges.entries()) {
|
|
517
|
+
index = writeNumber(serializedFloat, index, leadingGenCount);
|
|
518
|
+
index = writeNumber(serializedFloat, index, count);
|
|
519
|
+
}
|
|
1590
520
|
}
|
|
1591
521
|
|
|
522
|
+
assert(index === totalSize, 0x75b /* Serialized size was incorrectly calculated. */);
|
|
1592
523
|
this.logger?.sendTelemetryEvent({
|
|
1593
524
|
eventName: "RuntimeIdCompressor:SerializedIdCompressorSize",
|
|
1594
|
-
size:
|
|
1595
|
-
clusterCount:
|
|
1596
|
-
sessionCount:
|
|
525
|
+
size: serializedFloat.byteLength,
|
|
526
|
+
clusterCount: finalSpace.clusters.length,
|
|
527
|
+
sessionCount: sessionIndexMap.size,
|
|
1597
528
|
});
|
|
1598
529
|
|
|
1599
|
-
return
|
|
530
|
+
return bufferToString(serializedFloat.buffer, "base64") as SerializedIdCompressor;
|
|
1600
531
|
}
|
|
1601
532
|
|
|
1602
|
-
/**
|
|
1603
|
-
* Deserialize an serialized IdCompressor that is part of an ongoing session, thereby resuming that session.
|
|
1604
|
-
*/
|
|
1605
533
|
public static deserialize(serialized: SerializedIdCompressorWithOngoingSession): IdCompressor;
|
|
1606
|
-
|
|
1607
|
-
/**
|
|
1608
|
-
* Deserialize a serialized IdCompressor with a new session.
|
|
1609
|
-
* @param serialized - the serialized compressor state
|
|
1610
|
-
* @param newSessionId - the session ID for the new compressor.
|
|
1611
|
-
*/
|
|
1612
534
|
public static deserialize(
|
|
1613
535
|
serialized: SerializedIdCompressorWithNoSession,
|
|
1614
536
|
newSessionId: SessionId,
|
|
1615
537
|
): IdCompressor;
|
|
1616
|
-
|
|
1617
538
|
public static deserialize(
|
|
1618
|
-
|
|
1619
|
-
|
|
1620
|
-
| [serialized: SerializedIdCompressorWithOngoingSession, newSessionIdMaybe?: undefined]
|
|
539
|
+
serialized: SerializedIdCompressor,
|
|
540
|
+
sessionId?: SessionId,
|
|
1621
541
|
): IdCompressor {
|
|
1622
|
-
const
|
|
1623
|
-
|
|
1624
|
-
|
|
1625
|
-
|
|
1626
|
-
|
|
1627
|
-
|
|
1628
|
-
|
|
1629
|
-
|
|
1630
|
-
|
|
1631
|
-
|
|
1632
|
-
|
|
1633
|
-
|
|
1634
|
-
|
|
1635
|
-
|
|
1636
|
-
|
|
1637
|
-
|
|
1638
|
-
|
|
542
|
+
const buffer = stringToBuffer(serialized, "base64");
|
|
543
|
+
const index: Index = {
|
|
544
|
+
index: 0,
|
|
545
|
+
bufferFloat: new Float64Array(buffer),
|
|
546
|
+
bufferUint: new BigUint64Array(buffer),
|
|
547
|
+
};
|
|
548
|
+
const version = readNumber(index);
|
|
549
|
+
assert(version === currentWrittenVersion, 0x75c /* Unknown serialized version. */);
|
|
550
|
+
const hasLocalState = readBoolean(index);
|
|
551
|
+
const clusterCapacity = readNumber(index);
|
|
552
|
+
const sessionCount = readNumber(index);
|
|
553
|
+
const clusterCount = readNumber(index);
|
|
554
|
+
|
|
555
|
+
// Sessions
|
|
556
|
+
let sessionOffset = 0;
|
|
557
|
+
const sessions: [NumericUuid, Session][] = [];
|
|
558
|
+
if (!hasLocalState) {
|
|
559
|
+
// If !hasLocalState, there won't be a serialized local session ID so insert one at the beginning
|
|
560
|
+
assert(sessionId !== undefined, 0x75d /* Local session ID is undefined. */);
|
|
561
|
+
const localSessionNumeric = numericUuidFromStableId(sessionId);
|
|
562
|
+
sessions.push([localSessionNumeric, new Session(localSessionNumeric)]);
|
|
563
|
+
sessionOffset = 1;
|
|
1639
564
|
} else {
|
|
1640
|
-
|
|
1641
|
-
|
|
1642
|
-
|
|
1643
|
-
|
|
1644
|
-
compressor.clusterCapacity = clusterCapacity;
|
|
1645
|
-
|
|
1646
|
-
const localOverridesInverse = new Map<string, LocalCompressedId>();
|
|
1647
|
-
if (serializedLocalState !== undefined) {
|
|
1648
|
-
// Do this part of local rehydration first since the cluster map population needs to query to local overrides
|
|
1649
|
-
compressor.localIdCount = serializedLocalState.localIdCount;
|
|
1650
|
-
compressor.lastTakenLocalId = serializedLocalState.lastTakenLocalId;
|
|
1651
|
-
if (serializedLocalState.overrides !== undefined) {
|
|
1652
|
-
for (const [localId, override] of serializedLocalState.overrides) {
|
|
1653
|
-
compressor.localOverrides.append(localId, override);
|
|
1654
|
-
localOverridesInverse.set(override, localId);
|
|
1655
|
-
compressor.clustersAndOverridesInversion.set(
|
|
1656
|
-
IdCompressor.createInversionKey(override),
|
|
1657
|
-
localId as UnackedLocalId,
|
|
1658
|
-
);
|
|
1659
|
-
}
|
|
1660
|
-
}
|
|
565
|
+
assert(
|
|
566
|
+
sessionId === undefined,
|
|
567
|
+
0x75e /* Local state should not exist in serialized form. */,
|
|
568
|
+
);
|
|
1661
569
|
}
|
|
1662
570
|
|
|
1663
|
-
|
|
1664
|
-
|
|
1665
|
-
|
|
1666
|
-
}[] = [];
|
|
1667
|
-
for (const serializedSession of serializedSessions) {
|
|
1668
|
-
const [sessionId] = serializedSession;
|
|
1669
|
-
if (sessionId === localSessionId) {
|
|
1670
|
-
assert(hasOngoingSession(serialized), 0x499 /* Cannot resume existing session. */);
|
|
1671
|
-
sessionInfos.push({ session: compressor.localSession, sessionId });
|
|
1672
|
-
} else {
|
|
1673
|
-
const session = compressor.createSession(sessionId);
|
|
1674
|
-
sessionInfos.push({ session, sessionId });
|
|
1675
|
-
}
|
|
571
|
+
for (let i = 0; i < sessionCount; i++) {
|
|
572
|
+
const numeric = readNumericUuid(index);
|
|
573
|
+
sessions.push([numeric, new Session(numeric)]);
|
|
1676
574
|
}
|
|
1677
575
|
|
|
1678
|
-
|
|
1679
|
-
|
|
1680
|
-
deserializeCluster(serializedCluster);
|
|
1681
|
-
const { session, sessionId } = sessionInfos[sessionIndex];
|
|
1682
|
-
const { lastFinalizedLocalId, sessionUuid } = session;
|
|
1683
|
-
const currentIdCount = lastFinalizedLocalId === undefined ? 0 : -lastFinalizedLocalId;
|
|
576
|
+
const compressor = new IdCompressor(new Sessions(sessions));
|
|
577
|
+
compressor.clusterCapacity = clusterCapacity;
|
|
1684
578
|
|
|
1685
|
-
|
|
579
|
+
// Clusters
|
|
580
|
+
let baseFinalId = 0;
|
|
581
|
+
for (let i = 0; i < clusterCount; i++) {
|
|
582
|
+
const sessionIndex = readNumber(index);
|
|
583
|
+
const session = sessions[sessionIndex + sessionOffset][1];
|
|
584
|
+
const capacity = readNumber(index);
|
|
585
|
+
const count = readNumber(index);
|
|
586
|
+
const cluster = session.addNewCluster(
|
|
587
|
+
baseFinalId as FinalCompressedId,
|
|
1686
588
|
capacity,
|
|
1687
589
|
count,
|
|
1688
|
-
baseUuid: incrementUuid(sessionUuid, currentIdCount),
|
|
1689
|
-
session,
|
|
1690
|
-
};
|
|
1691
|
-
|
|
1692
|
-
const lastFinalizedNormalized = lastFinalizedLocalId ?? 0;
|
|
1693
|
-
const clusterBase = compressor.nextClusterBaseFinalId;
|
|
1694
|
-
|
|
1695
|
-
session.lastFinalizedLocalId = (lastFinalizedNormalized - count) as LocalCompressedId;
|
|
1696
|
-
session.currentClusterDetails = { clusterBase, cluster };
|
|
1697
|
-
compressor.nextClusterBaseFinalId = (compressor.nextClusterBaseFinalId +
|
|
1698
|
-
capacity) as FinalCompressedId;
|
|
1699
|
-
compressor.finalIdToCluster.append(clusterBase, cluster);
|
|
1700
|
-
compressor.clustersAndOverridesInversion.set(
|
|
1701
|
-
stableIdFromNumericUuid(cluster.baseUuid),
|
|
1702
|
-
{
|
|
1703
|
-
clusterBase,
|
|
1704
|
-
cluster,
|
|
1705
|
-
},
|
|
1706
590
|
);
|
|
1707
|
-
|
|
1708
|
-
|
|
1709
|
-
cluster.overrides = new Map();
|
|
1710
|
-
for (const [finalIdIndex, override, originalOverridingFinal] of overrides) {
|
|
1711
|
-
const finalId = (clusterBase + finalIdIndex) as FinalCompressedId;
|
|
1712
|
-
if (originalOverridingFinal !== undefined) {
|
|
1713
|
-
const unifiedOverride: Mutable<UnifiedOverride> = {
|
|
1714
|
-
override,
|
|
1715
|
-
originalOverridingFinal,
|
|
1716
|
-
};
|
|
1717
|
-
if (serializedLocalState !== undefined) {
|
|
1718
|
-
setPropertyIfDefined(
|
|
1719
|
-
localOverridesInverse.get(override),
|
|
1720
|
-
unifiedOverride,
|
|
1721
|
-
"associatedLocalId",
|
|
1722
|
-
);
|
|
1723
|
-
}
|
|
1724
|
-
cluster.overrides.set(finalId, unifiedOverride);
|
|
1725
|
-
} else {
|
|
1726
|
-
const associatedLocal = localOverridesInverse.get(override);
|
|
1727
|
-
if (associatedLocal !== undefined && sessionId !== localSessionId) {
|
|
1728
|
-
// In this case, there is a local ID associated with this override, but this is the first cluster to contain
|
|
1729
|
-
// that override (because only the first cluster will have the string serialized). In this case, the override
|
|
1730
|
-
// needs to hold that local value.
|
|
1731
|
-
cluster.overrides.set(finalId, {
|
|
1732
|
-
override,
|
|
1733
|
-
originalOverridingFinal: finalId,
|
|
1734
|
-
associatedLocalId: associatedLocal,
|
|
1735
|
-
});
|
|
1736
|
-
} else {
|
|
1737
|
-
cluster.overrides.set(finalId, override);
|
|
1738
|
-
}
|
|
1739
|
-
const finalizedOverride: Mutable<FinalizedOverride> = {
|
|
1740
|
-
cluster,
|
|
1741
|
-
originalOverridingFinal: finalId,
|
|
1742
|
-
};
|
|
1743
|
-
if (serializedLocalState !== undefined) {
|
|
1744
|
-
setPropertyIfDefined(
|
|
1745
|
-
associatedLocal,
|
|
1746
|
-
finalizedOverride,
|
|
1747
|
-
"associatedLocalId",
|
|
1748
|
-
);
|
|
1749
|
-
}
|
|
1750
|
-
compressor.clustersAndOverridesInversion.set(
|
|
1751
|
-
IdCompressor.createInversionKey(override),
|
|
1752
|
-
finalizedOverride,
|
|
1753
|
-
);
|
|
1754
|
-
}
|
|
1755
|
-
}
|
|
1756
|
-
}
|
|
591
|
+
compressor.finalSpace.addCluster(cluster);
|
|
592
|
+
baseFinalId += capacity;
|
|
1757
593
|
}
|
|
1758
594
|
|
|
1759
|
-
|
|
1760
|
-
|
|
1761
|
-
|
|
1762
|
-
|
|
1763
|
-
|
|
1764
|
-
|
|
1765
|
-
|
|
1766
|
-
|
|
1767
|
-
},
|
|
1768
|
-
);
|
|
595
|
+
// Local state
|
|
596
|
+
if (hasLocalState) {
|
|
597
|
+
compressor.localGenCount = readNumber(index);
|
|
598
|
+
compressor.nextRangeBaseGenCount = readNumber(index);
|
|
599
|
+
const normalizerCount = readNumber(index);
|
|
600
|
+
for (let i = 0; i < normalizerCount; i++) {
|
|
601
|
+
compressor.normalizer.addLocalRange(readNumber(index), readNumber(index));
|
|
602
|
+
}
|
|
1769
603
|
}
|
|
1770
604
|
|
|
1771
605
|
assert(
|
|
1772
|
-
|
|
1773
|
-
|
|
1774
|
-
0x49a /* Inconsistent last finalized state when deserializing */,
|
|
606
|
+
index.index === index.bufferFloat.length,
|
|
607
|
+
0x75f /* Failed to read entire serialized compressor. */,
|
|
1775
608
|
);
|
|
1776
|
-
|
|
1777
609
|
return compressor;
|
|
1778
610
|
}
|
|
1779
611
|
|
|
1780
|
-
|
|
1781
|
-
|
|
1782
|
-
|
|
1783
|
-
|
|
1784
|
-
|
|
1785
|
-
|
|
1786
|
-
|
|
1787
|
-
|
|
1788
|
-
|
|
1789
|
-
|
|
1790
|
-
/**
|
|
1791
|
-
* Converts the given serialized compressor to the current version.
|
|
1792
|
-
* @param serializedCompressor - the serialized compressor to convert. Must have been serialized with an ongoing session.
|
|
1793
|
-
* @returns a serialized compressor with the same ongoing session.
|
|
1794
|
-
*/
|
|
1795
|
-
public static convertToCurrentVersion(
|
|
1796
|
-
serializedCompressor: VersionedSerializedIdCompressor,
|
|
1797
|
-
hasSession: true,
|
|
1798
|
-
): SerializedIdCompressorWithOngoingSession;
|
|
1799
|
-
|
|
1800
|
-
public static convertToCurrentVersion(
|
|
1801
|
-
serializedCompressor: VersionedSerializedIdCompressor,
|
|
1802
|
-
hasSession: boolean,
|
|
1803
|
-
): SerializedIdCompressor | undefined {
|
|
1804
|
-
if (serializedCompressor.version !== currentWrittenVersion) {
|
|
1805
|
-
fail("Unknown SerializedIdCompressor version number");
|
|
1806
|
-
}
|
|
1807
|
-
const serialized = serializedCompressor as SerializedIdCompressorWithOngoingSession;
|
|
1808
|
-
if (hasSession !== hasOngoingSession(serialized)) {
|
|
1809
|
-
return undefined;
|
|
612
|
+
public equals(other: IdCompressor, includeLocalState: boolean): boolean {
|
|
613
|
+
if (
|
|
614
|
+
includeLocalState &&
|
|
615
|
+
(this.localSessionId !== other.localSessionId ||
|
|
616
|
+
!this.localSession.equals(other.localSession) ||
|
|
617
|
+
!this.normalizer.equals(other.normalizer) ||
|
|
618
|
+
this.nextRangeBaseGenCount !== other.nextRangeBaseGenCount ||
|
|
619
|
+
this.localGenCount !== other.localGenCount)
|
|
620
|
+
) {
|
|
621
|
+
return false;
|
|
1810
622
|
}
|
|
1811
|
-
return
|
|
623
|
+
return (
|
|
624
|
+
this.newClusterCapacity === other.newClusterCapacity &&
|
|
625
|
+
this.sessions.equals(other.sessions, includeLocalState) &&
|
|
626
|
+
this.finalSpace.equals(other.finalSpace)
|
|
627
|
+
);
|
|
1812
628
|
}
|
|
1813
629
|
}
|
|
1814
|
-
|
|
1815
|
-
/**
|
|
1816
|
-
* The version of `IdCompressor` that is currently persisted.
|
|
1817
|
-
*/
|
|
1818
|
-
const currentWrittenVersion = "0.0.1";
|
|
1819
|
-
|
|
1820
|
-
/**
|
|
1821
|
-
* @returns whether or not the given serialized ID compressor has an ongoing session.
|
|
1822
|
-
*/
|
|
1823
|
-
export function hasOngoingSession(
|
|
1824
|
-
serialized: SerializedIdCompressorWithNoSession | SerializedIdCompressorWithOngoingSession,
|
|
1825
|
-
): serialized is SerializedIdCompressorWithOngoingSession {
|
|
1826
|
-
return (
|
|
1827
|
-
(serialized as Partial<SerializedIdCompressorWithOngoingSession>).localSessionIndex !==
|
|
1828
|
-
undefined
|
|
1829
|
-
);
|
|
1830
|
-
}
|
|
1831
|
-
|
|
1832
|
-
function deserializeCluster(serializedCluster: SerializedCluster): {
|
|
1833
|
-
sessionIndex: number;
|
|
1834
|
-
capacity: number;
|
|
1835
|
-
count: number;
|
|
1836
|
-
overrides?: SerializedClusterOverrides;
|
|
1837
|
-
} {
|
|
1838
|
-
const [sessionIndex, capacity, countOrOverrides, overrides] = serializedCluster;
|
|
1839
|
-
const hasCount = typeof countOrOverrides === "number";
|
|
1840
|
-
|
|
1841
|
-
return {
|
|
1842
|
-
sessionIndex,
|
|
1843
|
-
capacity,
|
|
1844
|
-
count: hasCount ? countOrOverrides : capacity,
|
|
1845
|
-
overrides: hasCount ? overrides : countOrOverrides,
|
|
1846
|
-
};
|
|
1847
|
-
}
|
|
1848
|
-
|
|
1849
|
-
/**
|
|
1850
|
-
* Optimization used by the sorted-btree library to avoid allocating tuples every time a lookup method is called.
|
|
1851
|
-
* Lookup methods on BTree accept a pre-allocated array that it populates with the result of the lookup and retains no ownership
|
|
1852
|
-
* of after the call, so this array may be supplied to any of them. References to this array should not be retained elsewhere and
|
|
1853
|
-
* lookup results should be extracted from the tuple immediately after invocation.
|
|
1854
|
-
*/
|
|
1855
|
-
const reusedArray: [any, any] = [] as unknown as [any, any];
|