@fluidframework/container-runtime 2.0.0-internal.6.1.1 → 2.0.0-internal.6.3.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 +39 -0
- package/README.md +4 -3
- package/dist/batchTracker.d.ts +1 -1
- package/dist/batchTracker.d.ts.map +1 -1
- package/dist/batchTracker.js +5 -4
- package/dist/batchTracker.js.map +1 -1
- package/dist/blobManager.d.ts +4 -21
- package/dist/blobManager.d.ts.map +1 -1
- package/dist/blobManager.js +119 -185
- package/dist/blobManager.js.map +1 -1
- package/dist/connectionTelemetry.d.ts.map +1 -1
- package/dist/connectionTelemetry.js +13 -12
- package/dist/connectionTelemetry.js.map +1 -1
- package/dist/containerRuntime.d.ts +99 -16
- package/dist/containerRuntime.d.ts.map +1 -1
- package/dist/containerRuntime.js +380 -242
- package/dist/containerRuntime.js.map +1 -1
- package/dist/dataStore.d.ts.map +1 -1
- package/dist/dataStore.js +4 -5
- package/dist/dataStore.js.map +1 -1
- package/dist/dataStoreContext.d.ts +2 -1
- package/dist/dataStoreContext.d.ts.map +1 -1
- package/dist/dataStoreContext.js +40 -41
- 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 +7 -8
- 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 +23 -25
- package/dist/dataStores.js.map +1 -1
- package/dist/deltaManagerProxyBase.d.ts +1 -1
- package/dist/deltaManagerProxyBase.js +2 -2
- package/dist/deltaManagerProxyBase.js.map +1 -1
- package/dist/deltaScheduler.js +6 -6
- package/dist/deltaScheduler.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 +4 -6
- package/dist/gc/garbageCollection.d.ts.map +1 -1
- package/dist/gc/garbageCollection.js +26 -25
- 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 +4 -2
- package/dist/gc/gcDefinitions.d.ts.map +1 -1
- package/dist/gc/gcDefinitions.js.map +1 -1
- package/dist/gc/gcHelpers.js +7 -7
- package/dist/gc/gcHelpers.js.map +1 -1
- package/dist/gc/gcSummaryStateTracker.d.ts +4 -7
- package/dist/gc/gcSummaryStateTracker.d.ts.map +1 -1
- package/dist/gc/gcSummaryStateTracker.js +15 -52
- package/dist/gc/gcSummaryStateTracker.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/gc/gcUnreferencedStateTracker.js +4 -4
- package/dist/gc/gcUnreferencedStateTracker.js.map +1 -1
- package/dist/gc/index.d.ts +1 -1
- package/dist/gc/index.d.ts.map +1 -1
- package/dist/gc/index.js +1 -2
- package/dist/gc/index.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 +26 -68
- 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 +387 -1150
- 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.js +5 -5
- package/dist/opLifecycle/opCompressor.js.map +1 -1
- package/dist/opLifecycle/opDecompressor.d.ts.map +1 -1
- package/dist/opLifecycle/opDecompressor.js +11 -10
- package/dist/opLifecycle/opDecompressor.js.map +1 -1
- package/dist/opLifecycle/opGroupingManager.js +3 -3
- package/dist/opLifecycle/opGroupingManager.js.map +1 -1
- package/dist/opLifecycle/opSplitter.d.ts.map +1 -1
- package/dist/opLifecycle/opSplitter.js +14 -15
- 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 +16 -17
- 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 +36 -23
- package/dist/pendingStateManager.js.map +1 -1
- package/dist/scheduleManager.d.ts.map +1 -1
- package/dist/scheduleManager.js +23 -23
- package/dist/scheduleManager.js.map +1 -1
- package/dist/summary/index.d.ts +3 -3
- 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 +2 -3
- package/dist/summary/orderedClientElection.d.ts.map +1 -1
- package/dist/summary/orderedClientElection.js +8 -8
- package/dist/summary/orderedClientElection.js.map +1 -1
- package/dist/summary/runWhileConnectedCoordinator.js +3 -3
- package/dist/summary/runWhileConnectedCoordinator.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 +246 -74
- 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 +73 -69
- package/dist/summary/summarizer.js.map +1 -1
- package/dist/summary/summarizerClientElection.d.ts +2 -2
- package/dist/summary/summarizerClientElection.d.ts.map +1 -1
- package/dist/summary/summarizerClientElection.js +2 -2
- package/dist/summary/summarizerClientElection.js.map +1 -1
- package/dist/summary/summarizerHeuristics.js +2 -2
- package/dist/summary/summarizerHeuristics.js.map +1 -1
- package/dist/summary/summarizerNode/index.d.ts +1 -1
- package/dist/summary/summarizerNode/index.d.ts.map +1 -1
- package/dist/summary/summarizerNode/index.js.map +1 -1
- package/dist/summary/summarizerNode/summarizerNode.d.ts +5 -14
- package/dist/summary/summarizerNode/summarizerNode.d.ts.map +1 -1
- package/dist/summary/summarizerNode/summarizerNode.js +32 -109
- package/dist/summary/summarizerNode/summarizerNode.js.map +1 -1
- package/dist/summary/summarizerNode/summarizerNodeUtils.d.ts +6 -30
- package/dist/summary/summarizerNode/summarizerNodeUtils.d.ts.map +1 -1
- package/dist/summary/summarizerNode/summarizerNodeUtils.js.map +1 -1
- package/dist/summary/summarizerNode/summarizerNodeWithGc.d.ts +0 -11
- package/dist/summary/summarizerNode/summarizerNodeWithGc.d.ts.map +1 -1
- package/dist/summary/summarizerNode/summarizerNodeWithGc.js +5 -88
- package/dist/summary/summarizerNode/summarizerNodeWithGc.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 +2 -3
- package/dist/summary/summaryCollection.d.ts.map +1 -1
- package/dist/summary/summaryCollection.js +9 -8
- package/dist/summary/summaryCollection.js.map +1 -1
- package/dist/summary/summaryFormat.js +2 -2
- package/dist/summary/summaryFormat.js.map +1 -1
- package/dist/summary/summaryGenerator.d.ts +10 -4
- package/dist/summary/summaryGenerator.d.ts.map +1 -1
- package/dist/summary/summaryGenerator.js +52 -48
- 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 +30 -22
- package/dist/summary/summaryManager.js.map +1 -1
- package/lib/batchTracker.d.ts +1 -1
- package/lib/batchTracker.d.ts.map +1 -1
- package/lib/batchTracker.js +3 -2
- package/lib/batchTracker.js.map +1 -1
- package/lib/blobManager.d.ts +4 -21
- package/lib/blobManager.d.ts.map +1 -1
- package/lib/blobManager.js +91 -157
- package/lib/blobManager.js.map +1 -1
- package/lib/connectionTelemetry.d.ts.map +1 -1
- package/lib/connectionTelemetry.js +2 -1
- package/lib/connectionTelemetry.js.map +1 -1
- package/lib/containerRuntime.d.ts +99 -16
- package/lib/containerRuntime.d.ts.map +1 -1
- package/lib/containerRuntime.js +332 -192
- package/lib/containerRuntime.js.map +1 -1
- package/lib/dataStore.d.ts.map +1 -1
- package/lib/dataStore.js +2 -3
- package/lib/dataStore.js.map +1 -1
- package/lib/dataStoreContext.d.ts +2 -1
- package/lib/dataStoreContext.d.ts.map +1 -1
- package/lib/dataStoreContext.js +3 -4
- 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 +1 -2
- 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 +2 -4
- package/lib/dataStores.js.map +1 -1
- package/lib/deltaManagerProxyBase.d.ts +1 -1
- package/lib/deltaManagerProxyBase.js +1 -1
- package/lib/deltaManagerProxyBase.js.map +1 -1
- package/lib/deltaScheduler.js +1 -1
- package/lib/deltaScheduler.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 +4 -6
- package/lib/gc/garbageCollection.d.ts.map +1 -1
- package/lib/gc/garbageCollection.js +26 -25
- 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 +4 -2
- package/lib/gc/gcDefinitions.d.ts.map +1 -1
- package/lib/gc/gcDefinitions.js.map +1 -1
- package/lib/gc/gcHelpers.js +1 -1
- package/lib/gc/gcHelpers.js.map +1 -1
- package/lib/gc/gcSummaryStateTracker.d.ts +4 -7
- package/lib/gc/gcSummaryStateTracker.d.ts.map +1 -1
- package/lib/gc/gcSummaryStateTracker.js +16 -53
- package/lib/gc/gcSummaryStateTracker.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/gc/gcUnreferencedStateTracker.js +1 -1
- package/lib/gc/gcUnreferencedStateTracker.js.map +1 -1
- package/lib/gc/index.d.ts +1 -1
- package/lib/gc/index.d.ts.map +1 -1
- package/lib/gc/index.js +1 -1
- package/lib/gc/index.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 +25 -66
- 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 +382 -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.js +3 -3
- package/lib/opLifecycle/opCompressor.js.map +1 -1
- package/lib/opLifecycle/opDecompressor.d.ts.map +1 -1
- package/lib/opLifecycle/opDecompressor.js +2 -1
- package/lib/opLifecycle/opDecompressor.js.map +1 -1
- package/lib/opLifecycle/opGroupingManager.js +1 -1
- package/lib/opLifecycle/opGroupingManager.js.map +1 -1
- package/lib/opLifecycle/opSplitter.d.ts.map +1 -1
- package/lib/opLifecycle/opSplitter.js +2 -3
- 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 +7 -8
- 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 -8
- package/lib/pendingStateManager.js.map +1 -1
- package/lib/scheduleManager.d.ts.map +1 -1
- package/lib/scheduleManager.js +3 -3
- package/lib/scheduleManager.js.map +1 -1
- package/lib/summary/index.d.ts +3 -3
- 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 +2 -3
- package/lib/summary/orderedClientElection.d.ts.map +1 -1
- package/lib/summary/orderedClientElection.js +3 -3
- package/lib/summary/orderedClientElection.js.map +1 -1
- package/lib/summary/runWhileConnectedCoordinator.js +1 -1
- package/lib/summary/runWhileConnectedCoordinator.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 +240 -68
- 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 +69 -65
- package/lib/summary/summarizer.js.map +1 -1
- package/lib/summary/summarizerClientElection.d.ts +2 -2
- package/lib/summary/summarizerClientElection.d.ts.map +1 -1
- package/lib/summary/summarizerClientElection.js +1 -1
- package/lib/summary/summarizerClientElection.js.map +1 -1
- package/lib/summary/summarizerHeuristics.js +1 -1
- package/lib/summary/summarizerHeuristics.js.map +1 -1
- package/lib/summary/summarizerNode/index.d.ts +1 -1
- package/lib/summary/summarizerNode/index.d.ts.map +1 -1
- package/lib/summary/summarizerNode/index.js.map +1 -1
- package/lib/summary/summarizerNode/summarizerNode.d.ts +5 -14
- package/lib/summary/summarizerNode/summarizerNode.d.ts.map +1 -1
- package/lib/summary/summarizerNode/summarizerNode.js +16 -93
- package/lib/summary/summarizerNode/summarizerNode.js.map +1 -1
- package/lib/summary/summarizerNode/summarizerNodeUtils.d.ts +6 -30
- package/lib/summary/summarizerNode/summarizerNodeUtils.d.ts.map +1 -1
- package/lib/summary/summarizerNode/summarizerNodeUtils.js.map +1 -1
- package/lib/summary/summarizerNode/summarizerNodeWithGc.d.ts +0 -11
- package/lib/summary/summarizerNode/summarizerNodeWithGc.d.ts.map +1 -1
- package/lib/summary/summarizerNode/summarizerNodeWithGc.js +3 -86
- package/lib/summary/summarizerNode/summarizerNodeWithGc.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 +2 -3
- package/lib/summary/summaryCollection.d.ts.map +1 -1
- package/lib/summary/summaryCollection.js +2 -1
- package/lib/summary/summaryCollection.js.map +1 -1
- package/lib/summary/summaryFormat.js +1 -1
- package/lib/summary/summaryFormat.js.map +1 -1
- package/lib/summary/summaryGenerator.d.ts +10 -4
- package/lib/summary/summaryGenerator.d.ts.map +1 -1
- package/lib/summary/summaryGenerator.js +46 -42
- 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 +26 -18
- package/lib/summary/summaryManager.js.map +1 -1
- package/package.json +30 -29
- package/src/batchTracker.ts +3 -2
- package/src/blobManager.ts +105 -185
- package/src/connectionTelemetry.ts +2 -1
- package/src/containerRuntime.ts +481 -267
- package/src/dataStore.ts +2 -3
- package/src/dataStoreContext.ts +5 -8
- package/src/dataStoreContexts.ts +2 -4
- package/src/dataStoreRegistry.ts +1 -1
- package/src/dataStores.ts +4 -7
- package/src/deltaManagerProxyBase.ts +1 -1
- package/src/deltaScheduler.ts +1 -1
- package/src/error.ts +18 -0
- package/src/gc/garbageCollection.ts +39 -41
- package/src/gc/gcConfigs.ts +4 -2
- package/src/gc/gcDefinitions.ts +4 -6
- package/src/gc/gcHelpers.ts +1 -1
- package/src/gc/gcSummaryStateTracker.ts +19 -65
- package/src/gc/gcTelemetry.ts +2 -0
- package/src/gc/gcUnreferencedStateTracker.ts +1 -1
- package/src/gc/index.ts +0 -1
- package/src/id-compressor/appendOnlySortedMap.ts +26 -87
- package/src/id-compressor/finalSpace.ts +67 -0
- package/src/id-compressor/idCompressor.ts +456 -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 +3 -3
- package/src/opLifecycle/opDecompressor.ts +2 -1
- package/src/opLifecycle/opGroupingManager.ts +1 -1
- package/src/opLifecycle/opSplitter.ts +4 -4
- package/src/opLifecycle/outbox.ts +14 -11
- package/src/opLifecycle/remoteMessageProcessor.ts +19 -6
- package/src/packageVersion.ts +1 -1
- package/src/pendingStateManager.ts +50 -29
- package/src/scheduleManager.ts +6 -4
- package/src/summary/index.ts +4 -3
- package/src/summary/orderedClientElection.ts +8 -5
- package/src/summary/runWhileConnectedCoordinator.ts +1 -1
- package/src/summary/runningSummarizer.ts +273 -97
- package/src/summary/summarizer.ts +23 -12
- package/src/summary/summarizerClientElection.ts +2 -2
- package/src/summary/summarizerHeuristics.ts +1 -1
- package/src/summary/summarizerNode/index.ts +1 -2
- package/src/summary/summarizerNode/summarizerNode.ts +23 -145
- package/src/summary/summarizerNode/summarizerNodeUtils.ts +7 -38
- package/src/summary/summarizerNode/summarizerNodeWithGc.ts +3 -123
- package/src/summary/summarizerTypes.ts +40 -25
- package/src/summary/summaryCollection.ts +3 -3
- package/src/summary/summaryFormat.ts +1 -1
- package/src/summary/summaryGenerator.ts +52 -55
- package/src/summary/summaryManager.ts +36 -13
- 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
|
@@ -2,105 +2,79 @@
|
|
|
2
2
|
* Copyright (c) Microsoft Corporation and contributors. All rights reserved.
|
|
3
3
|
* Licensed under the MIT License.
|
|
4
4
|
*/
|
|
5
|
-
|
|
6
|
-
import {
|
|
7
|
-
import
|
|
5
|
+
import { assert } from "@fluidframework/core-utils";
|
|
6
|
+
import { bufferToString, stringToBuffer } from "@fluid-internal/client-utils";
|
|
7
|
+
import { initialClusterCapacity, } from "@fluidframework/runtime-definitions";
|
|
8
8
|
import { createChildLogger } from "@fluidframework/telemetry-utils";
|
|
9
|
-
import {
|
|
10
|
-
import {
|
|
11
|
-
import {
|
|
12
|
-
import {
|
|
13
|
-
import {
|
|
14
|
-
import {
|
|
9
|
+
import { isFinalId } from "./identifiers";
|
|
10
|
+
import { createSessionId, localIdFromGenCount, genCountFromLocalId, numericUuidFromStableId, offsetNumericUuid, stableIdFromNumericUuid, subtractNumericUuids, } from "./utilities";
|
|
11
|
+
import { readBoolean, readNumber, readNumericUuid, writeBoolean, writeNumber, writeNumericUuid, } from "./persistanceUtilities";
|
|
12
|
+
import { getAlignedLocal, getAlignedFinal, lastFinalizedLocal, Session, Sessions, } from "./sessions";
|
|
13
|
+
import { SessionSpaceNormalizer } from "./sessionSpaceNormalizer";
|
|
14
|
+
import { FinalSpace } from "./finalSpace";
|
|
15
15
|
/**
|
|
16
|
-
*
|
|
17
|
-
* This
|
|
16
|
+
* The version of IdCompressor that is currently persisted.
|
|
17
|
+
* This should not be changed without careful consideration to compatibility.
|
|
18
18
|
*/
|
|
19
|
-
|
|
19
|
+
const currentWrittenVersion = 1;
|
|
20
20
|
/**
|
|
21
|
-
*
|
|
22
|
-
* This should not be changed without consideration to compatibility.
|
|
23
|
-
*/
|
|
24
|
-
const reservedSessionId = ensureSessionUuid(assertIsStableId("decaf40b-3c1a-47f8-a7a1-e8461ddb69ce"));
|
|
25
|
-
/**
|
|
26
|
-
* @returns true if the supplied ID is a final ID.
|
|
27
|
-
*/
|
|
28
|
-
export function isFinalId(id) {
|
|
29
|
-
return id >= 0;
|
|
30
|
-
}
|
|
31
|
-
/**
|
|
32
|
-
* @returns true if the supplied ID is a local ID.
|
|
33
|
-
*/
|
|
34
|
-
export function isLocalId(id) {
|
|
35
|
-
return id < 0;
|
|
36
|
-
}
|
|
37
|
-
/** Prepended to all keys in {@link IdCompressor.clustersAndOverridesInversion} that are override strings and not valid `StableIds` */
|
|
38
|
-
const nonStableOverridePrefix = "\ue15e"; // A character in the Private Use Area of the BMP (https://en.wikipedia.org/wiki/Private_Use_Areas)
|
|
39
|
-
/**
|
|
40
|
-
* See {@link IIdCompressor}
|
|
21
|
+
* See {@link IIdCompressor} and {@link IIdCompressorCore}
|
|
41
22
|
*/
|
|
42
23
|
export class IdCompressor {
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
this.
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
this.
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
/**
|
|
98
|
-
* Maps the first final ID in a cluster to its owning cluster.
|
|
99
|
-
* Can be searched in O(log n) to determine clusters for any final ID.
|
|
100
|
-
*/
|
|
101
|
-
this.finalIdToCluster = new AppendOnlySortedMap(compareFiniteNumbers);
|
|
102
|
-
this.localSession = this.createSession(localSessionId);
|
|
103
|
-
this.logger = createChildLogger({ logger });
|
|
24
|
+
// -----------------------
|
|
25
|
+
constructor(localSessionIdOrDeserialized, logger) {
|
|
26
|
+
this.logger = logger;
|
|
27
|
+
this.normalizer = new SessionSpaceNormalizer();
|
|
28
|
+
// The number of IDs generated by the local session
|
|
29
|
+
this.localGenCount = 0;
|
|
30
|
+
// -----------------------
|
|
31
|
+
// ----- Final state -----
|
|
32
|
+
// The gen count to be annotated on the range returned by the next call to `takeNextCreationRange`.
|
|
33
|
+
// This is updated to be equal to `generatedIdCount` + 1 each time it is called.
|
|
34
|
+
this.nextRangeBaseGenCount = 1;
|
|
35
|
+
// The capacity of the next cluster to be created
|
|
36
|
+
this.newClusterCapacity = initialClusterCapacity;
|
|
37
|
+
this.sessions = new Sessions();
|
|
38
|
+
this.finalSpace = new FinalSpace();
|
|
39
|
+
// -----------------------
|
|
40
|
+
// ----- Telemetry state -----
|
|
41
|
+
// The number of local IDs generated since the last telemetry was sent.
|
|
42
|
+
this.telemetryLocalIdCount = 0;
|
|
43
|
+
// The number of eager final IDs generated since the last telemetry was sent.
|
|
44
|
+
this.telemetryEagerFinalIdCount = 0;
|
|
45
|
+
if (typeof localSessionIdOrDeserialized === "string") {
|
|
46
|
+
this.localSessionId = localSessionIdOrDeserialized;
|
|
47
|
+
this.localSession = this.sessions.getOrCreate(localSessionIdOrDeserialized);
|
|
48
|
+
}
|
|
49
|
+
else {
|
|
50
|
+
// Deserialize case
|
|
51
|
+
this.sessions = localSessionIdOrDeserialized;
|
|
52
|
+
// As policy, the first session is always the local session. Preserve this invariant
|
|
53
|
+
// during deserialization.
|
|
54
|
+
const firstSession = localSessionIdOrDeserialized.sessions().next();
|
|
55
|
+
assert(!firstSession.done, 0x754 /* First session must be present. */);
|
|
56
|
+
this.localSession = firstSession.value;
|
|
57
|
+
this.localSessionId = stableIdFromNumericUuid(this.localSession.sessionUuid);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
static create(sessionIdOrLogger, loggerOrUndefined) {
|
|
61
|
+
let localSessionId;
|
|
62
|
+
let logger;
|
|
63
|
+
if (sessionIdOrLogger === undefined) {
|
|
64
|
+
localSessionId = createSessionId();
|
|
65
|
+
}
|
|
66
|
+
else {
|
|
67
|
+
if (typeof sessionIdOrLogger === "string") {
|
|
68
|
+
localSessionId = sessionIdOrLogger;
|
|
69
|
+
logger = loggerOrUndefined;
|
|
70
|
+
}
|
|
71
|
+
else {
|
|
72
|
+
localSessionId = createSessionId();
|
|
73
|
+
logger = loggerOrUndefined;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
const compressor = new IdCompressor(localSessionId, logger === undefined ? undefined : createChildLogger({ logger }));
|
|
77
|
+
return compressor;
|
|
104
78
|
}
|
|
105
79
|
/**
|
|
106
80
|
* The size of each newly created ID cluster.
|
|
@@ -113,184 +87,77 @@ export class IdCompressor {
|
|
|
113
87
|
* `IdCompressor.maxClusterSize`.
|
|
114
88
|
*/
|
|
115
89
|
set clusterCapacity(value) {
|
|
116
|
-
|
|
117
|
-
|
|
90
|
+
if (value <= 0) {
|
|
91
|
+
throw new Error("Clusters must have a positive capacity.");
|
|
92
|
+
}
|
|
93
|
+
if (value > IdCompressor.maxClusterSize) {
|
|
94
|
+
throw new Error("Clusters must not exceed max cluster size.");
|
|
95
|
+
}
|
|
118
96
|
this.newClusterCapacity = value;
|
|
119
97
|
}
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
const
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
return
|
|
98
|
+
generateCompressedId() {
|
|
99
|
+
this.localGenCount++;
|
|
100
|
+
const lastCluster = this.localSession.getLastCluster();
|
|
101
|
+
if (lastCluster === undefined) {
|
|
102
|
+
this.telemetryLocalIdCount++;
|
|
103
|
+
return this.generateNextLocalId();
|
|
104
|
+
}
|
|
105
|
+
// If there exists a cluster of final IDs already claimed by the local session that still has room in it,
|
|
106
|
+
// it is known prior to range sequencing what a local ID's corresponding final ID will be.
|
|
107
|
+
// In this case, it is safe to return the final ID immediately. This is guaranteed to be safe because
|
|
108
|
+
// any op that the local session sends that contains one of those final IDs are guaranteed to arrive to
|
|
109
|
+
// collaborators *after* the one containing the creation range.
|
|
110
|
+
const clusterOffset = this.localGenCount - genCountFromLocalId(lastCluster.baseLocalId);
|
|
111
|
+
if (lastCluster.capacity > clusterOffset) {
|
|
112
|
+
this.telemetryEagerFinalIdCount++;
|
|
113
|
+
// Space in the cluster: eager final
|
|
114
|
+
return (lastCluster.baseFinalId +
|
|
115
|
+
clusterOffset);
|
|
116
|
+
}
|
|
117
|
+
// No space in the cluster, return next local
|
|
118
|
+
this.telemetryLocalIdCount++;
|
|
119
|
+
return this.generateNextLocalId();
|
|
120
|
+
}
|
|
121
|
+
generateNextLocalId() {
|
|
122
|
+
// Must tell the normalizer that we generated a local ID
|
|
123
|
+
this.normalizer.addLocalRange(this.localGenCount, 1);
|
|
124
|
+
return localIdFromGenCount(this.localGenCount);
|
|
140
125
|
}
|
|
141
|
-
/**
|
|
142
|
-
* Returns a range of local IDs created by this session in a format for sending to the server for finalizing.
|
|
143
|
-
* The range will include all local IDs generated via calls to `generateCompressedId` since the last time this method was called.
|
|
144
|
-
* @returns the range of session-local IDs, which may be empty. This range must be sent to the server for ordering before
|
|
145
|
-
* it is finalized. Ranges must be sent to the server in the order that they are taken via calls to this method.
|
|
146
|
-
*/
|
|
147
126
|
takeNextCreationRange() {
|
|
148
|
-
const
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
const firstLocalInRange = (lastTakenNormalized - 1);
|
|
154
|
-
const overrides = [
|
|
155
|
-
...this.localOverrides.getRange((lastTakenNormalized - 1), lastLocalInRange),
|
|
156
|
-
];
|
|
157
|
-
if (hasAtLeastLength(overrides, 1)) {
|
|
158
|
-
assert(overrides[0][0] <= firstLocalInRange, 0x486 /* Inconsistent override state */);
|
|
159
|
-
assert(overrides[overrides.length - 1][0] >= lastLocalInRange, 0x487 /* Inconsistent override state */);
|
|
160
|
-
ids = {
|
|
161
|
-
overrides,
|
|
162
|
-
};
|
|
163
|
-
const first = firstLocalInRange === overrides[0][0] ? undefined : firstLocalInRange;
|
|
164
|
-
const last = lastLocalInRange === overrides[overrides.length - 1][0]
|
|
165
|
-
? undefined
|
|
166
|
-
: lastLocalInRange;
|
|
167
|
-
setPropertyIfDefined(first, ids, "first");
|
|
168
|
-
setPropertyIfDefined(last, ids, "last");
|
|
169
|
-
}
|
|
170
|
-
else {
|
|
171
|
-
ids = {
|
|
172
|
-
first: firstLocalInRange,
|
|
173
|
-
last: lastLocalInRange,
|
|
174
|
-
};
|
|
175
|
-
}
|
|
176
|
-
this.lastTakenLocalId = lastLocalInRange;
|
|
177
|
-
}
|
|
178
|
-
const range = { sessionId: this.localSessionId };
|
|
179
|
-
if (ids === undefined) {
|
|
180
|
-
return range;
|
|
127
|
+
const count = this.localGenCount - (this.nextRangeBaseGenCount - 1);
|
|
128
|
+
if (count === 0) {
|
|
129
|
+
return {
|
|
130
|
+
sessionId: this.localSessionId,
|
|
131
|
+
};
|
|
181
132
|
}
|
|
182
|
-
|
|
183
|
-
this.
|
|
184
|
-
|
|
133
|
+
const range = {
|
|
134
|
+
sessionId: this.localSessionId,
|
|
135
|
+
ids: {
|
|
136
|
+
firstGenCount: this.nextRangeBaseGenCount,
|
|
137
|
+
count,
|
|
138
|
+
},
|
|
139
|
+
};
|
|
140
|
+
this.nextRangeBaseGenCount = this.localGenCount + 1;
|
|
185
141
|
return range;
|
|
186
142
|
}
|
|
187
|
-
/**
|
|
188
|
-
* Finalizes the supplied range of IDs (which may be from either a remote or local session).
|
|
189
|
-
* @param range - the range of session-local IDs to finalize.
|
|
190
|
-
*/
|
|
191
143
|
finalizeCreationRange(range) {
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
const session = this.sessions.get(sessionId) ?? this.createSession(sessionId);
|
|
195
|
-
const ids = getIds(range);
|
|
196
|
-
if (ids === undefined) {
|
|
144
|
+
// Check if the range has IDs
|
|
145
|
+
if (range.ids === undefined) {
|
|
197
146
|
return;
|
|
198
147
|
}
|
|
199
|
-
|
|
200
|
-
const {
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
const
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
let initialClusterCount = 0;
|
|
213
|
-
let remainingCount = finalizeCount;
|
|
214
|
-
let newBaseUuid;
|
|
215
|
-
if (currentClusterExists) {
|
|
216
|
-
if (isLocal) {
|
|
217
|
-
const lastKnownFinal = this.sessionIdNormalizer.getLastFinalId() ??
|
|
218
|
-
fail("Cluster exists but normalizer does not have an entry for it.");
|
|
219
|
-
const lastAlignedFinalInCluster = (currentBaseFinalId +
|
|
220
|
-
Math.min(currentCluster.count + finalizeCount, currentCluster.capacity) -
|
|
221
|
-
1);
|
|
222
|
-
if (lastAlignedFinalInCluster > lastKnownFinal) {
|
|
223
|
-
this.sessionIdNormalizer.addFinalIds((lastKnownFinal + 1), lastAlignedFinalInCluster, currentCluster);
|
|
224
|
-
}
|
|
225
|
-
}
|
|
226
|
-
initialClusterCount = currentCluster.count;
|
|
227
|
-
const remainingCapacity = currentCluster.capacity - initialClusterCount;
|
|
228
|
-
const overflow = remainingCount - remainingCapacity;
|
|
229
|
-
const hasRoom = overflow <= 0;
|
|
230
|
-
if (hasRoom || currentBaseFinalId === this.finalIdToCluster.maxKey()) {
|
|
231
|
-
currentCluster.count += remainingCount;
|
|
232
|
-
eagerFinalIdCount = remainingCount;
|
|
233
|
-
remainingCount = 0;
|
|
234
|
-
// The common case is that there is room in the cluster, and the new final IDs can simply be added to it
|
|
235
|
-
if (!hasRoom) {
|
|
236
|
-
// The cluster is full but is the last in the list of clusters.
|
|
237
|
-
// This allows it to be expanded instead of allocating a new one.
|
|
238
|
-
const expansionAmount = this.newClusterCapacity + overflow;
|
|
239
|
-
const previousCapacity = currentCluster.capacity;
|
|
240
|
-
currentCluster.capacity += expansionAmount;
|
|
241
|
-
this.nextClusterBaseFinalId = (this.nextClusterBaseFinalId +
|
|
242
|
-
expansionAmount);
|
|
243
|
-
assert(this.nextClusterBaseFinalId < Number.MAX_SAFE_INTEGER, 0x48b /* The number of allocated final IDs must not exceed the JS maximum safe integer. */);
|
|
244
|
-
this.checkClusterForCollision(currentCluster);
|
|
245
|
-
if (isLocal) {
|
|
246
|
-
// Example with cluster size of 3:
|
|
247
|
-
// Ids generated so far: -1 1 2 -4 -5 <-- note positive numbers are eager finals
|
|
248
|
-
// Cluster: [ 0 1 2 ]
|
|
249
|
-
// ~ finalizing happens, causing expansion of 2 (overflow) + 3 (cluster capacity) ~
|
|
250
|
-
// Cluster: [ 0 1 2 3 4 _ _ _ ]
|
|
251
|
-
// corresponding locals: -1 -4 -5
|
|
252
|
-
// lastFinalizedLocalId^ ^newLastFinalizedLocalId = -5
|
|
253
|
-
// overflow = 2: ----
|
|
254
|
-
// localIdPivot^
|
|
255
|
-
// lastFinalizedFinal^
|
|
256
|
-
const newLastFinalizedFinal = (currentBaseFinalId +
|
|
257
|
-
currentCluster.count -
|
|
258
|
-
1);
|
|
259
|
-
assert(session.lastFinalizedLocalId !== undefined, 0x48c /* Cluster already exists for session but there is no finalized local ID */);
|
|
260
|
-
const finalPivot = (newLastFinalizedFinal -
|
|
261
|
-
overflow +
|
|
262
|
-
1);
|
|
263
|
-
// Inform the normalizer of all IDs that we now know will end up being finalized into this cluster, including the ones
|
|
264
|
-
// that were given out as locals (non-eager) because they exceeded the bounds of the current cluster before it was expanded.
|
|
265
|
-
// It is safe to associate the unfinalized locals with their future final IDs even before the ranges for those locals are
|
|
266
|
-
// actually finalized, because total order broadcast guarantees that any usage of those final IDs will be observed after
|
|
267
|
-
// the finalization of the ranges.
|
|
268
|
-
this.sessionIdNormalizer.registerFinalIdBlock(finalPivot, expansionAmount, currentCluster);
|
|
269
|
-
this.logger?.sendTelemetryEvent({
|
|
270
|
-
eventName: "RuntimeIdCompressor:ClusterExpansion",
|
|
271
|
-
sessionId: this.localSessionId,
|
|
272
|
-
previousCapacity,
|
|
273
|
-
newCapacity: currentCluster.capacity,
|
|
274
|
-
overflow,
|
|
275
|
-
});
|
|
276
|
-
}
|
|
277
|
-
}
|
|
278
|
-
}
|
|
279
|
-
else {
|
|
280
|
-
// The range cannot be fully allocated in the existing cluster, so allocate any space left in it and
|
|
281
|
-
// form a new one by incrementing the previous baseUuid
|
|
282
|
-
newBaseUuid = incrementUuid(currentCluster.baseUuid, currentCluster.capacity);
|
|
283
|
-
currentCluster.count += remainingCapacity;
|
|
284
|
-
remainingCount -= remainingCapacity;
|
|
285
|
-
this.logger?.sendTelemetryEvent({
|
|
286
|
-
eventName: "RuntimeIdCompressor:OverfilledCluster",
|
|
287
|
-
sessionId: this.localSessionId,
|
|
288
|
-
});
|
|
289
|
-
}
|
|
290
|
-
}
|
|
291
|
-
else {
|
|
292
|
-
// Session has never made a cluster, form a new one with the session UUID as the baseUuid
|
|
293
|
-
newBaseUuid = session.sessionUuid;
|
|
148
|
+
assert(range.ids.count > 0, 0x755 /* Malformed ID Range. */);
|
|
149
|
+
const { sessionId, ids } = range;
|
|
150
|
+
const { count, firstGenCount } = ids;
|
|
151
|
+
const session = this.sessions.getOrCreate(sessionId);
|
|
152
|
+
const isLocal = session === this.localSession;
|
|
153
|
+
const rangeBaseLocal = localIdFromGenCount(firstGenCount);
|
|
154
|
+
let lastCluster = session.getLastCluster();
|
|
155
|
+
if (lastCluster === undefined) {
|
|
156
|
+
// This is the first cluster in the session space
|
|
157
|
+
if (rangeBaseLocal !== -1) {
|
|
158
|
+
throw new Error("Ranges finalized out of order.");
|
|
159
|
+
}
|
|
160
|
+
lastCluster = this.addEmptyCluster(session, this.clusterCapacity + count);
|
|
294
161
|
if (isLocal) {
|
|
295
162
|
this.logger?.sendTelemetryEvent({
|
|
296
163
|
eventName: "RuntimeIdCompressor:FirstCluster",
|
|
@@ -298,951 +165,327 @@ export class IdCompressor {
|
|
|
298
165
|
});
|
|
299
166
|
}
|
|
300
167
|
}
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
// 2. All local IDs are finalized into the existing (current) cluster for the session.
|
|
305
|
-
// 3. Local IDs are finalized into both the current cluster and a new one, as the current cluster did not have enough room.
|
|
306
|
-
let newCluster;
|
|
307
|
-
let newBaseFinalId;
|
|
308
|
-
// The first local ID that will be finalized into a new cluster, if there is one.
|
|
309
|
-
// This lets us quickly compare which cluster an override string will go into.
|
|
310
|
-
let localIdPivot;
|
|
311
|
-
// Need to make a new cluster
|
|
312
|
-
if (newBaseUuid !== undefined) {
|
|
313
|
-
if (remainingCount <= 0) {
|
|
314
|
-
fail("Should not create an empty cluster.");
|
|
315
|
-
}
|
|
316
|
-
if (currentCluster !== undefined && currentCluster.capacity !== currentCluster.count) {
|
|
317
|
-
fail("Cluster must be filled before another is allocated.");
|
|
318
|
-
}
|
|
319
|
-
newBaseFinalId = this.nextClusterBaseFinalId;
|
|
320
|
-
const newCapacity = Math.max(this.newClusterCapacity, remainingCount);
|
|
321
|
-
newCluster = {
|
|
322
|
-
baseUuid: newBaseUuid,
|
|
323
|
-
capacity: newCapacity,
|
|
324
|
-
count: remainingCount,
|
|
325
|
-
session,
|
|
326
|
-
};
|
|
327
|
-
const usedCapacity = finalizeCount - remainingCount;
|
|
328
|
-
localIdPivot = (newFirstFinalizedLocal - usedCapacity);
|
|
329
|
-
if (isLocal) {
|
|
330
|
-
this.logger?.sendTelemetryEvent({
|
|
331
|
-
eventName: "RuntimeIdCompressor:NewCluster",
|
|
332
|
-
sessionId: this.localSessionId,
|
|
333
|
-
clusterCapacity: newCapacity,
|
|
334
|
-
clusterCount: remainingCount,
|
|
335
|
-
});
|
|
336
|
-
this.sessionIdNormalizer.registerFinalIdBlock(newBaseFinalId, newCluster.capacity, newCluster);
|
|
337
|
-
}
|
|
338
|
-
this.checkClusterForCollision(newCluster);
|
|
339
|
-
this.clustersAndOverridesInversion.set(stableIdFromNumericUuid(newCluster.baseUuid), {
|
|
340
|
-
clusterBase: newBaseFinalId,
|
|
341
|
-
cluster: newCluster,
|
|
342
|
-
});
|
|
343
|
-
session.currentClusterDetails = { cluster: newCluster, clusterBase: newBaseFinalId };
|
|
344
|
-
this.nextClusterBaseFinalId = (this.nextClusterBaseFinalId +
|
|
345
|
-
newCluster.capacity);
|
|
346
|
-
assert(this.nextClusterBaseFinalId < Number.MAX_SAFE_INTEGER, 0x48e /* The number of allocated final IDs must not exceed the JS maximum safe integer. */);
|
|
347
|
-
this.finalIdToCluster.append(newBaseFinalId, newCluster);
|
|
168
|
+
const remainingCapacity = lastCluster.capacity - lastCluster.count;
|
|
169
|
+
if (lastCluster.baseLocalId - lastCluster.count !== rangeBaseLocal) {
|
|
170
|
+
throw new Error("Ranges finalized out of order.");
|
|
348
171
|
}
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
assert(currentCluster !== undefined && currentBaseFinalId !== undefined, 0x493 /* No cluster exists but IDs were finalized. */);
|
|
370
|
-
cluster = currentCluster;
|
|
371
|
-
overriddenFinal = (currentBaseFinalId +
|
|
372
|
-
initialClusterCount +
|
|
373
|
-
(normalizedLastFinalizedLocal - overriddenLocal) -
|
|
374
|
-
1);
|
|
375
|
-
}
|
|
376
|
-
cluster.overrides ?? (cluster.overrides = new Map());
|
|
377
|
-
const inversionKey = IdCompressor.createInversionKey(override);
|
|
378
|
-
const existingIds = this.getExistingIdsForNewOverride(inversionKey, true);
|
|
379
|
-
let overrideForCluster;
|
|
380
|
-
let associatedLocal;
|
|
381
|
-
if (existingIds !== undefined) {
|
|
382
|
-
let mostFinalExistingOverride;
|
|
383
|
-
if (typeof existingIds === "number") {
|
|
384
|
-
mostFinalExistingOverride = existingIds;
|
|
385
|
-
if (isLocalId(mostFinalExistingOverride)) {
|
|
386
|
-
associatedLocal = mostFinalExistingOverride;
|
|
387
|
-
}
|
|
388
|
-
}
|
|
389
|
-
else {
|
|
390
|
-
[associatedLocal, mostFinalExistingOverride] = existingIds;
|
|
391
|
-
}
|
|
392
|
-
if (isFinalId(mostFinalExistingOverride)) {
|
|
393
|
-
// A previous range already finalized an ID with this override. See `IdCluster` for more.
|
|
394
|
-
overrideForCluster = mostFinalExistingOverride;
|
|
395
|
-
}
|
|
396
|
-
else {
|
|
397
|
-
assert(!isLocal || mostFinalExistingOverride === overriddenLocal, 0x494 /* Cannot have multiple local IDs with identical overrides. */);
|
|
398
|
-
// This session has created an ID with this override before, but has not finalized it yet. The incoming
|
|
399
|
-
// range "wins" and will contain the final ID associated with that override, regardless of if that range was
|
|
400
|
-
// made by this session or not.
|
|
401
|
-
overrideForCluster = override;
|
|
402
|
-
}
|
|
403
|
-
}
|
|
404
|
-
else {
|
|
405
|
-
// This is the first time this override has been associated with any ID
|
|
406
|
-
overrideForCluster = override;
|
|
407
|
-
}
|
|
408
|
-
assert(!cluster.overrides.has(overriddenFinal), 0x495 /* Cannot add a second override for final id */);
|
|
409
|
-
if (typeof overrideForCluster === "string") {
|
|
410
|
-
if (isLocal || associatedLocal === undefined) {
|
|
411
|
-
cluster.overrides.set(overriddenFinal, override);
|
|
412
|
-
}
|
|
413
|
-
else {
|
|
414
|
-
cluster.overrides.set(overriddenFinal, {
|
|
415
|
-
override,
|
|
416
|
-
originalOverridingFinal: overriddenFinal,
|
|
417
|
-
associatedLocalId: associatedLocal,
|
|
418
|
-
});
|
|
419
|
-
}
|
|
420
|
-
}
|
|
421
|
-
else {
|
|
422
|
-
const unifiedOverride = {
|
|
423
|
-
override,
|
|
424
|
-
originalOverridingFinal: overrideForCluster,
|
|
425
|
-
};
|
|
426
|
-
setPropertyIfDefined(associatedLocal, unifiedOverride, "associatedLocalId");
|
|
427
|
-
cluster.overrides.set(overriddenFinal, unifiedOverride);
|
|
172
|
+
if (remainingCapacity >= count) {
|
|
173
|
+
// The current range fits in the existing cluster
|
|
174
|
+
lastCluster.count += count;
|
|
175
|
+
}
|
|
176
|
+
else {
|
|
177
|
+
const overflow = count - remainingCapacity;
|
|
178
|
+
const newClaimedFinalCount = overflow + this.clusterCapacity;
|
|
179
|
+
if (lastCluster === this.finalSpace.getLastCluster()) {
|
|
180
|
+
// The last cluster in the sessions chain is the last cluster globally, so it can be expanded.
|
|
181
|
+
lastCluster.capacity += newClaimedFinalCount;
|
|
182
|
+
lastCluster.count += count;
|
|
183
|
+
assert(!this.sessions.clusterCollides(lastCluster), 0x756 /* Cluster collision detected. */);
|
|
184
|
+
if (isLocal) {
|
|
185
|
+
this.logger?.sendTelemetryEvent({
|
|
186
|
+
eventName: "RuntimeIdCompressor:ClusterExpansion",
|
|
187
|
+
sessionId: this.localSessionId,
|
|
188
|
+
previousCapacity: lastCluster.capacity - newClaimedFinalCount,
|
|
189
|
+
newCapacity: lastCluster.capacity,
|
|
190
|
+
overflow,
|
|
191
|
+
});
|
|
428
192
|
}
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
if (
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
193
|
+
}
|
|
194
|
+
else {
|
|
195
|
+
// The last cluster in the sessions chain is *not* the last cluster globally. Fill and overflow to new.
|
|
196
|
+
lastCluster.count = lastCluster.capacity;
|
|
197
|
+
const newCluster = this.addEmptyCluster(session, newClaimedFinalCount);
|
|
198
|
+
newCluster.count += overflow;
|
|
199
|
+
if (isLocal) {
|
|
200
|
+
this.logger?.sendTelemetryEvent({
|
|
201
|
+
eventName: "RuntimeIdCompressor:NewCluster",
|
|
202
|
+
sessionId: this.localSessionId,
|
|
203
|
+
});
|
|
440
204
|
}
|
|
441
205
|
}
|
|
442
206
|
}
|
|
443
207
|
if (isLocal) {
|
|
444
208
|
this.logger?.sendTelemetryEvent({
|
|
445
209
|
eventName: "RuntimeIdCompressor:IdCompressorStatus",
|
|
446
|
-
eagerFinalIdCount:
|
|
447
|
-
localIdCount:
|
|
448
|
-
overridesCount: overrides?.length ?? 0,
|
|
210
|
+
eagerFinalIdCount: this.telemetryEagerFinalIdCount,
|
|
211
|
+
localIdCount: this.telemetryLocalIdCount,
|
|
449
212
|
sessionId: this.localSessionId,
|
|
450
213
|
});
|
|
214
|
+
this.telemetryEagerFinalIdCount = 0;
|
|
215
|
+
this.telemetryLocalIdCount = 0;
|
|
451
216
|
}
|
|
452
|
-
session.
|
|
453
|
-
}
|
|
454
|
-
checkClusterForCollision(cluster) {
|
|
455
|
-
const maxClusterUuid = incrementUuid(cluster.baseUuid, cluster.capacity - 1);
|
|
456
|
-
const maxClusterStableId = stableIdFromNumericUuid(maxClusterUuid);
|
|
457
|
-
const closestMatch = this.clustersAndOverridesInversion.getPairOrNextLower(maxClusterStableId);
|
|
458
|
-
if (closestMatch !== undefined) {
|
|
459
|
-
const [inversionKey, compressionMapping] = closestMatch;
|
|
460
|
-
if (!IdCompressor.isClusterInfo(compressionMapping)) {
|
|
461
|
-
if (isStableId(inversionKey) &&
|
|
462
|
-
IdCompressor.uuidsMightCollide(inversionKey, maxClusterStableId, cluster.capacity)) {
|
|
463
|
-
const numericOverride = numericUuidFromStableId(inversionKey);
|
|
464
|
-
const delta = getPositiveDelta(maxClusterUuid, numericOverride, cluster.capacity - 1);
|
|
465
|
-
if (delta !== undefined) {
|
|
466
|
-
IdCompressor.failWithCollidingOverride(inversionKey);
|
|
467
|
-
}
|
|
468
|
-
}
|
|
469
|
-
}
|
|
470
|
-
}
|
|
471
|
-
}
|
|
472
|
-
static failWithCollidingOverride(override) {
|
|
473
|
-
fail(`Override '${override}' collides with another allocated UUID.`);
|
|
474
|
-
}
|
|
475
|
-
static isClusterInfo(compressionMapping) {
|
|
476
|
-
return compressionMapping.clusterBase !== undefined;
|
|
477
|
-
}
|
|
478
|
-
static isUnfinalizedOverride(compressionMapping) {
|
|
479
|
-
return typeof compressionMapping === "number";
|
|
480
|
-
}
|
|
481
|
-
static createInversionKey(inversionKey) {
|
|
482
|
-
return isStableId(inversionKey)
|
|
483
|
-
? inversionKey
|
|
484
|
-
: `${nonStableOverridePrefix}${inversionKey}`;
|
|
485
|
-
}
|
|
486
|
-
static isStableInversionKey(inversionKey) {
|
|
487
|
-
return inversionKey.charAt(0) !== nonStableOverridePrefix;
|
|
488
|
-
}
|
|
489
|
-
/**
|
|
490
|
-
* Returns an existing ID associated with an override, or undefined if none exists.
|
|
491
|
-
*/
|
|
492
|
-
getExistingIdsForNewOverride(inversionKey, isFinalOverride) {
|
|
493
|
-
const closestMatch = this.clustersAndOverridesInversion.getPairOrNextLower(inversionKey, reusedArray);
|
|
494
|
-
let numericOverride;
|
|
495
|
-
let stableOverride;
|
|
496
|
-
if (closestMatch !== undefined) {
|
|
497
|
-
const [key, compressionMapping] = closestMatch;
|
|
498
|
-
if (!IdCompressor.isClusterInfo(compressionMapping)) {
|
|
499
|
-
if (key === inversionKey) {
|
|
500
|
-
if (IdCompressor.isUnfinalizedOverride(compressionMapping)) {
|
|
501
|
-
return compressionMapping;
|
|
502
|
-
}
|
|
503
|
-
const finalizedOverride = compressionMapping;
|
|
504
|
-
return finalizedOverride.associatedLocalId !== undefined
|
|
505
|
-
? [
|
|
506
|
-
finalizedOverride.associatedLocalId,
|
|
507
|
-
finalizedOverride.originalOverridingFinal,
|
|
508
|
-
]
|
|
509
|
-
: finalizedOverride.originalOverridingFinal;
|
|
510
|
-
}
|
|
511
|
-
}
|
|
512
|
-
else if (IdCompressor.isStableInversionKey(inversionKey)) {
|
|
513
|
-
stableOverride = inversionKey;
|
|
514
|
-
const cluster = compressionMapping.cluster;
|
|
515
|
-
if (IdCompressor.uuidsMightCollide(inversionKey, key, cluster.capacity)) {
|
|
516
|
-
numericOverride = numericUuidFromStableId(stableOverride);
|
|
517
|
-
const delta = getPositiveDelta(numericOverride, cluster.baseUuid, cluster.capacity - 1);
|
|
518
|
-
if (delta !== undefined) {
|
|
519
|
-
if (!isFinalOverride) {
|
|
520
|
-
if (delta >= cluster.count) {
|
|
521
|
-
// TODO:#283: Properly implement unification
|
|
522
|
-
return undefined;
|
|
523
|
-
}
|
|
524
|
-
return this.normalizeToSessionSpace((compressionMapping.clusterBase + delta));
|
|
525
|
-
}
|
|
526
|
-
}
|
|
527
|
-
}
|
|
528
|
-
}
|
|
529
|
-
}
|
|
530
|
-
const override = numericOverride ??
|
|
531
|
-
stableOverride ??
|
|
532
|
-
(IdCompressor.isStableInversionKey(inversionKey) ? inversionKey : undefined);
|
|
533
|
-
if (override !== undefined) {
|
|
534
|
-
const sessionSpaceId = this.getCompressedIdForStableId(override);
|
|
535
|
-
if (sessionSpaceId !== undefined) {
|
|
536
|
-
return sessionSpaceId;
|
|
537
|
-
}
|
|
538
|
-
}
|
|
539
|
-
return undefined;
|
|
217
|
+
assert(!session.isEmpty(), 0x757 /* Empty sessions should not be created. */);
|
|
540
218
|
}
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
// Check if any of the UUIDs in the cluster collide (i.e. any in [base, base + capacity)).
|
|
547
|
-
// Optimization: All UUIDs in a cluster are the same string up until the last few characters which encode the offset from
|
|
548
|
-
// the cluster base. So, first compute the length of that shared string, and early out if it is different from the override
|
|
549
|
-
// UUID. This way we usually need not do the more expensive check below.
|
|
550
|
-
const hexDigitsToCheck = 32 - Math.ceil(Math.log2(range) / 2);
|
|
551
|
-
if (a.startsWith(b.slice(0, hexDigitsToCheck))) {
|
|
552
|
-
return true;
|
|
553
|
-
}
|
|
554
|
-
return false;
|
|
219
|
+
addEmptyCluster(session, capacity) {
|
|
220
|
+
const newCluster = session.addNewCluster(this.finalSpace.getAllocatedIdLimit(), capacity, 0);
|
|
221
|
+
assert(!this.sessions.clusterCollides(newCluster), 0x758 /* Cluster collision detected. */);
|
|
222
|
+
this.finalSpace.addCluster(newCluster);
|
|
223
|
+
return newCluster;
|
|
555
224
|
}
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
static tryGetOverride(cluster, finalId) {
|
|
560
|
-
const override = cluster.overrides?.get(finalId);
|
|
561
|
-
if (override === undefined) {
|
|
562
|
-
return undefined;
|
|
563
|
-
}
|
|
564
|
-
if (typeof override === "string") {
|
|
565
|
-
return override;
|
|
566
|
-
}
|
|
567
|
-
return override.override;
|
|
568
|
-
}
|
|
569
|
-
/**
|
|
570
|
-
* Generates a new compressed ID or returns an existing one.
|
|
571
|
-
* This should ONLY be called to generate IDs for local operations.
|
|
572
|
-
* @param override - Specifies a specific string to be associated with the returned compressed ID.
|
|
573
|
-
* Performance note: assigning override strings incurs a performance overhead.
|
|
574
|
-
* @returns an existing ID if one already exists for `override`, and a new local ID otherwise. The returned ID is in session space.
|
|
575
|
-
*/
|
|
576
|
-
generateCompressedId(override) {
|
|
577
|
-
let overrideInversionKey;
|
|
578
|
-
if (override !== undefined) {
|
|
579
|
-
overrideInversionKey = IdCompressor.createInversionKey(override);
|
|
580
|
-
const existingIds = this.getExistingIdsForNewOverride(overrideInversionKey, false);
|
|
581
|
-
if (existingIds !== undefined) {
|
|
582
|
-
return typeof existingIds === "number" ? existingIds : existingIds[0];
|
|
583
|
-
}
|
|
584
|
-
}
|
|
585
|
-
// Bump local counter regardless, then attempt to optimistically return a final ID.
|
|
586
|
-
// If the local session has reserved a cluster range via consensus, it is safe to hand out final IDs prior to
|
|
587
|
-
// finalizing the range that includes these locals.
|
|
588
|
-
const newLocalId = -++this.localIdCount;
|
|
589
|
-
const { currentClusterDetails } = this.localSession;
|
|
590
|
-
const { sessionIdNormalizer } = this;
|
|
591
|
-
let eagerFinalId;
|
|
592
|
-
let cluster;
|
|
593
|
-
if (currentClusterDetails !== undefined) {
|
|
594
|
-
cluster = currentClusterDetails.cluster;
|
|
595
|
-
const lastFinalKnown = sessionIdNormalizer.getLastFinalId();
|
|
596
|
-
if (lastFinalKnown !== undefined &&
|
|
597
|
-
lastFinalKnown - currentClusterDetails.clusterBase + 1 < cluster.capacity) {
|
|
598
|
-
eagerFinalId = (lastFinalKnown + 1);
|
|
599
|
-
}
|
|
600
|
-
}
|
|
601
|
-
if (overrideInversionKey !== undefined) {
|
|
602
|
-
const registeredLocal = sessionIdNormalizer.addLocalId();
|
|
603
|
-
assert(registeredLocal === newLocalId, 0x496 /* Session ID Normalizer produced unexpected local ID */);
|
|
604
|
-
if (eagerFinalId !== undefined) {
|
|
605
|
-
sessionIdNormalizer.addFinalIds(eagerFinalId, eagerFinalId, cluster ?? fail("No cluster when generating compressed ID"));
|
|
606
|
-
}
|
|
607
|
-
this.localOverrides.append(newLocalId, override ?? fail("Override must be defined"));
|
|
608
|
-
// Since the local ID was just created, it is in both session and op space
|
|
609
|
-
const compressionMapping = newLocalId;
|
|
610
|
-
this.clustersAndOverridesInversion.set(overrideInversionKey, compressionMapping);
|
|
611
|
-
}
|
|
612
|
-
else if (eagerFinalId !== undefined) {
|
|
613
|
-
sessionIdNormalizer.addFinalIds(eagerFinalId, eagerFinalId, cluster ?? fail("No cluster when generating compressed ID"));
|
|
614
|
-
return eagerFinalId;
|
|
225
|
+
normalizeToOpSpace(id) {
|
|
226
|
+
if (isFinalId(id)) {
|
|
227
|
+
return id;
|
|
615
228
|
}
|
|
616
229
|
else {
|
|
617
|
-
const
|
|
618
|
-
|
|
230
|
+
const local = id;
|
|
231
|
+
if (!this.normalizer.contains(local)) {
|
|
232
|
+
throw new Error("Invalid ID to normalize.");
|
|
233
|
+
}
|
|
234
|
+
const finalForm = this.localSession.tryConvertToFinal(local, true);
|
|
235
|
+
return finalForm === undefined
|
|
236
|
+
? local
|
|
237
|
+
: finalForm;
|
|
619
238
|
}
|
|
620
|
-
return newLocalId;
|
|
621
239
|
}
|
|
622
|
-
|
|
623
|
-
* Decompresses a previously compressed ID into a UUID or override string.
|
|
624
|
-
* @param id - the compressed ID to be decompressed.
|
|
625
|
-
* @returns the UUID or override string associated with the compressed ID. Fails if the ID was not generated by this compressor.
|
|
626
|
-
*/
|
|
627
|
-
decompress(id) {
|
|
628
|
-
return this.tryDecompress(id) ?? fail("Compressed ID was not generated by this compressor");
|
|
629
|
-
}
|
|
630
|
-
/**
|
|
631
|
-
* Attempts to decompress a previously compressed ID into a UUID or override string.
|
|
632
|
-
* @param id - the compressed ID to be decompressed.
|
|
633
|
-
* @returns the UUID or override string associated with the compressed ID, or undefined if the ID was not generated by this compressor.
|
|
634
|
-
*/
|
|
635
|
-
tryDecompress(id) {
|
|
240
|
+
normalizeToSessionSpace(id, originSessionId) {
|
|
636
241
|
if (isFinalId(id)) {
|
|
637
|
-
const
|
|
638
|
-
if (
|
|
639
|
-
//
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
return stableIdFromNumericUuid(this.localSession.sessionUuid, creationIndex);
|
|
242
|
+
const containingCluster = this.localSession.getClusterByAllocatedFinal(id);
|
|
243
|
+
if (containingCluster === undefined) {
|
|
244
|
+
// Does not exist in local cluster chain
|
|
245
|
+
if (id >= this.finalSpace.getFinalizedIdLimit()) {
|
|
246
|
+
throw new Error("Unknown op space ID.");
|
|
643
247
|
}
|
|
644
|
-
return
|
|
248
|
+
return id;
|
|
645
249
|
}
|
|
646
250
|
else {
|
|
647
|
-
const
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
return override;
|
|
251
|
+
const alignedLocal = getAlignedLocal(containingCluster, id);
|
|
252
|
+
if (this.normalizer.contains(alignedLocal)) {
|
|
253
|
+
return alignedLocal;
|
|
651
254
|
}
|
|
652
255
|
else {
|
|
653
|
-
|
|
654
|
-
|
|
256
|
+
if (genCountFromLocalId(alignedLocal) > this.localGenCount) {
|
|
257
|
+
throw new Error("Unknown op space ID.");
|
|
258
|
+
}
|
|
259
|
+
return id;
|
|
655
260
|
}
|
|
656
261
|
}
|
|
657
262
|
}
|
|
658
263
|
else {
|
|
659
|
-
const
|
|
660
|
-
if (
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
const localOverride = this.localOverrides?.get(id);
|
|
668
|
-
return localOverride !== undefined
|
|
669
|
-
? localOverride
|
|
670
|
-
: stableIdFromNumericUuid(this.localSession.sessionUuid, idOffset - 1);
|
|
671
|
-
}
|
|
672
|
-
}
|
|
673
|
-
/**
|
|
674
|
-
* Recompresses a decompressed ID, which could be a UUID or an override string.
|
|
675
|
-
* @param uncompressed - the UUID or override string to recompress.
|
|
676
|
-
* @returns the `CompressedId` associated with `uncompressed`. Fails if it has not been previously compressed by this compressor.
|
|
677
|
-
*/
|
|
678
|
-
recompress(uncompressed) {
|
|
679
|
-
return this.tryRecompress(uncompressed) ?? fail("No such string has ever been compressed");
|
|
680
|
-
}
|
|
681
|
-
/**
|
|
682
|
-
* Attempts to recompresses a decompressed ID, which could be a UUID or an override string.
|
|
683
|
-
* @param uncompressed - the UUID or override string to recompress,
|
|
684
|
-
* @returns the `CompressedId` associated with `uncompressed` or undefined if it has not been previously compressed by this compressor.
|
|
685
|
-
*/
|
|
686
|
-
tryRecompress(uncompressed) {
|
|
687
|
-
return this.recompressInternal(uncompressed);
|
|
688
|
-
}
|
|
689
|
-
/**
|
|
690
|
-
* Helper to compress an uncompressed UUID. It can optionally be supplied with the numeric form of `uncompressedUuid` as a
|
|
691
|
-
* performance optimization.
|
|
692
|
-
*/
|
|
693
|
-
recompressInternal(uncompressed, uncompressedUuidNumeric) {
|
|
694
|
-
let numericUuid = uncompressedUuidNumeric;
|
|
695
|
-
const inversionKey = IdCompressor.createInversionKey(uncompressed);
|
|
696
|
-
const isStable = IdCompressor.isStableInversionKey(inversionKey);
|
|
697
|
-
const closestMatch = this.clustersAndOverridesInversion.getPairOrNextLower(inversionKey, reusedArray);
|
|
698
|
-
if (closestMatch !== undefined) {
|
|
699
|
-
const [key, compressionMapping] = closestMatch;
|
|
700
|
-
if (!IdCompressor.isClusterInfo(compressionMapping)) {
|
|
701
|
-
if (key === inversionKey) {
|
|
702
|
-
return IdCompressor.isUnfinalizedOverride(compressionMapping)
|
|
703
|
-
? compressionMapping
|
|
704
|
-
: compressionMapping.associatedLocalId ??
|
|
705
|
-
compressionMapping.originalOverridingFinal;
|
|
264
|
+
const localToNormalize = id;
|
|
265
|
+
if (originSessionId === this.localSessionId) {
|
|
266
|
+
if (this.normalizer.contains(localToNormalize)) {
|
|
267
|
+
return localToNormalize;
|
|
268
|
+
}
|
|
269
|
+
else {
|
|
270
|
+
// We never generated this local ID, so fail
|
|
271
|
+
throw new Error("Unknown op space ID.");
|
|
706
272
|
}
|
|
707
273
|
}
|
|
708
274
|
else {
|
|
709
|
-
|
|
710
|
-
|
|
275
|
+
// LocalId from a remote session
|
|
276
|
+
const remoteSession = this.sessions.get(originSessionId);
|
|
277
|
+
if (remoteSession === undefined) {
|
|
278
|
+
throw new Error("No IDs have ever been finalized by the supplied session.");
|
|
711
279
|
}
|
|
712
|
-
const
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
if (uuidOffset !== undefined) {
|
|
716
|
-
let targetFinalId = (closestBaseFinalId + uuidOffset);
|
|
717
|
-
const override = closestCluster.overrides?.get(targetFinalId);
|
|
718
|
-
if (typeof override === "object") {
|
|
719
|
-
if (override.associatedLocalId !== undefined) {
|
|
720
|
-
return override.associatedLocalId;
|
|
721
|
-
}
|
|
722
|
-
// This may be a UUID that should actually compress into a different final ID that it aligns with, due to
|
|
723
|
-
// another session having an identical override (see `IdCluster` for more).
|
|
724
|
-
targetFinalId = override.originalOverridingFinal;
|
|
725
|
-
}
|
|
726
|
-
return this.normalizeToSessionSpace(targetFinalId);
|
|
280
|
+
const correspondingFinal = remoteSession.tryConvertToFinal(localToNormalize, false);
|
|
281
|
+
if (correspondingFinal === undefined) {
|
|
282
|
+
throw new Error("Unknown op space ID.");
|
|
727
283
|
}
|
|
284
|
+
return correspondingFinal;
|
|
728
285
|
}
|
|
729
286
|
}
|
|
730
|
-
if (isStable) {
|
|
731
|
-
// May have already computed the numeric UUID, so avoid recomputing if possible
|
|
732
|
-
const sessionSpaceId = this.getCompressedIdForStableId(numericUuid ?? inversionKey);
|
|
733
|
-
if (sessionSpaceId !== undefined) {
|
|
734
|
-
return sessionSpaceId;
|
|
735
|
-
}
|
|
736
|
-
}
|
|
737
|
-
return undefined;
|
|
738
287
|
}
|
|
739
|
-
|
|
740
|
-
* Normalizes a session space ID into op space.
|
|
741
|
-
* @param id - the local ID to normalize.
|
|
742
|
-
* @returns the ID in op space.
|
|
743
|
-
*/
|
|
744
|
-
normalizeToOpSpace(id) {
|
|
288
|
+
decompress(id) {
|
|
745
289
|
if (isFinalId(id)) {
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
const override = this.localOverrides.get(id);
|
|
761
|
-
if (override !== undefined) {
|
|
762
|
-
const inversionKey = IdCompressor.createInversionKey(override);
|
|
763
|
-
const compressionMapping = this.clustersAndOverridesInversion.get(inversionKey) ??
|
|
764
|
-
fail("Bimap is malformed.");
|
|
765
|
-
return !IdCompressor.isClusterInfo(compressionMapping) &&
|
|
766
|
-
!IdCompressor.isUnfinalizedOverride(compressionMapping) &&
|
|
767
|
-
compressionMapping.associatedLocalId === id
|
|
768
|
-
? compressionMapping.originalOverridingFinal
|
|
769
|
-
: id;
|
|
770
|
-
}
|
|
771
|
-
const possibleFinal = this.sessionIdNormalizer.getFinalId(id);
|
|
772
|
-
return possibleFinal?.[0] ?? id;
|
|
773
|
-
}
|
|
774
|
-
const [correspondingFinal, cluster] = this.sessionIdNormalizer.getFinalId(id) ??
|
|
775
|
-
fail("Locally created cluster should be added to the map when allocated");
|
|
776
|
-
if (cluster.overrides) {
|
|
777
|
-
const override = cluster.overrides.get(correspondingFinal);
|
|
778
|
-
if (typeof override === "object" && override.originalOverridingFinal !== undefined) {
|
|
779
|
-
// Rare case of two local IDs with same overrides are created concurrently. See `IdCluster` for more.
|
|
780
|
-
return override.originalOverridingFinal;
|
|
781
|
-
}
|
|
782
|
-
}
|
|
783
|
-
return correspondingFinal;
|
|
784
|
-
}
|
|
785
|
-
normalizeToSessionSpace(id, sessionIdIfLocal) {
|
|
786
|
-
if (isLocalId(id)) {
|
|
787
|
-
if (sessionIdIfLocal === undefined || sessionIdIfLocal === this.localSessionId) {
|
|
788
|
-
const localIndex = -id;
|
|
789
|
-
if (localIndex > this.localIdCount) {
|
|
790
|
-
fail("Supplied local ID was not created by this compressor.");
|
|
290
|
+
const containingCluster = Session.getContainingCluster(id, this.finalSpace.clusters);
|
|
291
|
+
if (containingCluster === undefined) {
|
|
292
|
+
throw new Error("Unknown ID");
|
|
293
|
+
}
|
|
294
|
+
const alignedLocal = getAlignedLocal(containingCluster, id);
|
|
295
|
+
const alignedGenCount = genCountFromLocalId(alignedLocal);
|
|
296
|
+
const lastFinalizedGenCount = genCountFromLocalId(lastFinalizedLocal(containingCluster));
|
|
297
|
+
if (alignedGenCount > lastFinalizedGenCount) {
|
|
298
|
+
// should be an eager final id generated by the local session
|
|
299
|
+
if (containingCluster.session === this.localSession) {
|
|
300
|
+
assert(!this.normalizer.contains(alignedLocal), 0x759 /* Normalizer out of sync. */);
|
|
301
|
+
}
|
|
302
|
+
else {
|
|
303
|
+
throw new Error("Unknown ID");
|
|
791
304
|
}
|
|
792
|
-
return id;
|
|
793
|
-
}
|
|
794
|
-
else {
|
|
795
|
-
const session = this.sessions.get(sessionIdIfLocal) ??
|
|
796
|
-
fail("No IDs have ever been finalized by the supplied session.");
|
|
797
|
-
const localCount = -id;
|
|
798
|
-
const numericUuid = incrementUuid(session.sessionUuid, localCount - 1);
|
|
799
|
-
return (this.compressNumericUuid(numericUuid) ??
|
|
800
|
-
fail("ID is not known to this compressor."));
|
|
801
305
|
}
|
|
306
|
+
return stableIdFromNumericUuid(offsetNumericUuid(containingCluster.session.sessionUuid, alignedGenCount - 1));
|
|
802
307
|
}
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
// Check for a unified override finalized first by another session but to which the local session
|
|
808
|
-
// still has an associated local ID.
|
|
809
|
-
const [_, cluster] = this.getClusterForFinalId(id) ??
|
|
810
|
-
fail("Supplied final ID was not finalized by this compressor.");
|
|
811
|
-
const override = cluster.overrides?.get(id);
|
|
812
|
-
if (typeof override === "object" && override.associatedLocalId !== undefined) {
|
|
813
|
-
return override.associatedLocalId;
|
|
814
|
-
}
|
|
815
|
-
return id;
|
|
816
|
-
}
|
|
817
|
-
/**
|
|
818
|
-
* Returns the session-space compressed ID corresponding to the numeric UUID, or undefined if it is not known to this compressor.
|
|
819
|
-
* Typically, it will return the session-space ID sequentially aligned with it (which will be local if `numericUuid` was made by
|
|
820
|
-
* the local session, or final otherwise). However, in the event that the aligned session-space ID was overridden with a UUID
|
|
821
|
-
* *and* that override UUID was concurrently used in an older ID (earlier, w.r.t. sequencing), this method can return the first
|
|
822
|
-
* ID to correspond to that override.
|
|
823
|
-
*
|
|
824
|
-
* As an example, consider the following two clients:
|
|
825
|
-
* ClientA, session UUID: A0000000-0000-0000-0000-000000000000
|
|
826
|
-
* ClientB, session UUID: B0000000-0000-0000-0000-000000000000
|
|
827
|
-
*
|
|
828
|
-
* If concurrently, two clients performed:
|
|
829
|
-
* ClientA: generateCompressedId(override: 'X0000000-0000-0000-0000-000000000000') // aligned with A0000000-0000-0000-0000-000000000000
|
|
830
|
-
*
|
|
831
|
-
* ClientB: generateCompressedId() // aligned with B0000000-0000-0000-0000-000000000000
|
|
832
|
-
* ClientB: generateCompressedId(override: 'X0000000-0000-0000-0000-000000000000') // aligned with B0000000-0000-0000-0000-000000000001
|
|
833
|
-
*
|
|
834
|
-
* After sequencing, calling this method and passing the numeric UUID for B0000000-0000-0000-0000-000000000001 would return the
|
|
835
|
-
* session-space ID corresponding to A0000000-0000-0000-0000-000000000000 (with override X0000000-0000-0000-0000-000000000000).
|
|
836
|
-
*/
|
|
837
|
-
compressNumericUuid(numericUuid) {
|
|
838
|
-
const stableId = stableIdFromNumericUuid(numericUuid);
|
|
839
|
-
const sessionSpaceId = this.recompressInternal(stableId, numericUuid);
|
|
840
|
-
if (sessionSpaceId === undefined) {
|
|
841
|
-
return undefined;
|
|
842
|
-
}
|
|
843
|
-
return sessionSpaceId;
|
|
844
|
-
}
|
|
845
|
-
/**
|
|
846
|
-
* Returns a compressed ID for the supplied stable ID if it was created by the local session, and undefined otherwise.
|
|
847
|
-
*/
|
|
848
|
-
getCompressedIdForStableId(stableId) {
|
|
849
|
-
const numericUuid = typeof stableId === "string" ? numericUuidFromStableId(stableId) : stableId;
|
|
850
|
-
const creationIndex = getPositiveDelta(numericUuid, this.localSession.sessionUuid, this.localIdCount - 1);
|
|
851
|
-
if (creationIndex !== undefined) {
|
|
852
|
-
const sessionSpaceId = this.sessionIdNormalizer.getIdByCreationIndex(creationIndex);
|
|
853
|
-
if (sessionSpaceId !== undefined) {
|
|
854
|
-
return sessionSpaceId;
|
|
308
|
+
else {
|
|
309
|
+
const localToDecompress = id;
|
|
310
|
+
if (!this.normalizer.contains(localToDecompress)) {
|
|
311
|
+
throw new Error("Unknown ID");
|
|
855
312
|
}
|
|
313
|
+
return stableIdFromNumericUuid(offsetNumericUuid(this.localSession.sessionUuid, genCountFromLocalId(localToDecompress) - 1));
|
|
856
314
|
}
|
|
857
|
-
return undefined;
|
|
858
315
|
}
|
|
859
|
-
|
|
860
|
-
const
|
|
861
|
-
if (
|
|
862
|
-
|
|
863
|
-
}
|
|
864
|
-
const [clusterBase, cluster] = possibleCluster;
|
|
865
|
-
if (finalId - clusterBase >= cluster.count) {
|
|
866
|
-
return undefined;
|
|
316
|
+
recompress(uncompressed) {
|
|
317
|
+
const recompressed = this.tryRecompress(uncompressed);
|
|
318
|
+
if (recompressed === undefined) {
|
|
319
|
+
throw new Error("Could not recompress.");
|
|
867
320
|
}
|
|
868
|
-
return
|
|
321
|
+
return recompressed;
|
|
869
322
|
}
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
this.
|
|
879
|
-
|
|
880
|
-
}
|
|
881
|
-
if (!this.localOverrides.equals(other.localOverrides, (a, b) => a === b)) {
|
|
882
|
-
return false;
|
|
883
|
-
}
|
|
884
|
-
if (!compareMaps(this.sessions, other.sessions, (a, b) => IdCompressor.sessionDataEqual(a, b, true, compareLocalState))) {
|
|
885
|
-
return false;
|
|
886
|
-
}
|
|
887
|
-
if (!this.sessionIdNormalizer.equals(other.sessionIdNormalizer, (a, b) => IdCompressor.idClustersEqual(a, b, false, compareLocalState))) {
|
|
888
|
-
return false;
|
|
889
|
-
}
|
|
890
|
-
}
|
|
891
|
-
else {
|
|
892
|
-
for (const [keyA, valueA] of this.sessions) {
|
|
893
|
-
const valueB = other.sessions.get(keyA);
|
|
894
|
-
if (valueB === undefined) {
|
|
895
|
-
if (valueA.lastFinalizedLocalId !== undefined) {
|
|
896
|
-
return false;
|
|
897
|
-
}
|
|
898
|
-
}
|
|
899
|
-
else if (!IdCompressor.sessionDataEqual(valueA, valueB, true, compareLocalState)) {
|
|
900
|
-
return false;
|
|
901
|
-
}
|
|
902
|
-
}
|
|
903
|
-
for (const [keyB, valueB] of other.sessions) {
|
|
904
|
-
const valueA = this.sessions.get(keyB);
|
|
905
|
-
if (valueA === undefined) {
|
|
906
|
-
if (valueB.lastFinalizedLocalId !== undefined) {
|
|
907
|
-
return false;
|
|
908
|
-
}
|
|
323
|
+
tryRecompress(uncompressed) {
|
|
324
|
+
const match = this.sessions.getContainingCluster(uncompressed);
|
|
325
|
+
if (match === undefined) {
|
|
326
|
+
const numericUncompressed = numericUuidFromStableId(uncompressed);
|
|
327
|
+
const offset = subtractNumericUuids(numericUncompressed, this.localSession.sessionUuid);
|
|
328
|
+
if (offset < Number.MAX_SAFE_INTEGER) {
|
|
329
|
+
const genCountEquivalent = Number(offset) + 1;
|
|
330
|
+
const localEquivalent = localIdFromGenCount(genCountEquivalent);
|
|
331
|
+
if (this.normalizer.contains(localEquivalent)) {
|
|
332
|
+
return localEquivalent;
|
|
909
333
|
}
|
|
910
334
|
}
|
|
335
|
+
return undefined;
|
|
911
336
|
}
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
}
|
|
919
|
-
const missingInOne = (_, value) => {
|
|
920
|
-
if (!compareLocalState && IdCompressor.isUnfinalizedOverride(value)) {
|
|
921
|
-
return undefined;
|
|
922
|
-
}
|
|
923
|
-
return { break: true };
|
|
924
|
-
};
|
|
925
|
-
const compareCompressionMappings = (a, b) => {
|
|
926
|
-
const unfinalizedA = IdCompressor.isUnfinalizedOverride(a);
|
|
927
|
-
const unfinalizedB = IdCompressor.isUnfinalizedOverride(b);
|
|
928
|
-
if (unfinalizedA) {
|
|
929
|
-
if (unfinalizedB) {
|
|
930
|
-
return a === b;
|
|
337
|
+
else {
|
|
338
|
+
const [containingCluster, alignedLocal] = match;
|
|
339
|
+
if (containingCluster.session === this.localSession) {
|
|
340
|
+
// Local session
|
|
341
|
+
if (this.normalizer.contains(alignedLocal)) {
|
|
342
|
+
return alignedLocal;
|
|
931
343
|
}
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
}
|
|
937
|
-
if (IdCompressor.isClusterInfo(a)) {
|
|
938
|
-
if (!IdCompressor.isClusterInfo(b) || a.clusterBase !== b.clusterBase) {
|
|
939
|
-
return false;
|
|
344
|
+
else {
|
|
345
|
+
assert(genCountFromLocalId(alignedLocal) <= this.localGenCount, 0x75a /* Clusters out of sync. */);
|
|
346
|
+
// Id is an eager final
|
|
347
|
+
return getAlignedFinal(containingCluster, alignedLocal);
|
|
940
348
|
}
|
|
941
349
|
}
|
|
942
350
|
else {
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
const
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
351
|
+
// Not the local session
|
|
352
|
+
return genCountFromLocalId(alignedLocal) >= lastFinalizedLocal(containingCluster)
|
|
353
|
+
? getAlignedFinal(containingCluster, alignedLocal)
|
|
354
|
+
: undefined;
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
serialize(hasLocalState) {
|
|
359
|
+
const { normalizer, finalSpace, sessions } = this;
|
|
360
|
+
const sessionIndexMap = new Map();
|
|
361
|
+
let sessionIndex = 0;
|
|
362
|
+
for (const session of sessions.sessions()) {
|
|
363
|
+
// Filter empty sessions to prevent them accumulating in the serialized state
|
|
364
|
+
if (!session.isEmpty() || hasLocalState) {
|
|
365
|
+
sessionIndexMap.set(session, sessionIndex);
|
|
366
|
+
sessionIndex++;
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
const localStateSize = hasLocalState
|
|
370
|
+
? 1 + // generated ID count
|
|
371
|
+
1 + // next range base genCount
|
|
372
|
+
1 + // count of normalizer pairs
|
|
373
|
+
this.normalizer.idRanges.size * 2 // pairs
|
|
374
|
+
: 0;
|
|
375
|
+
// Layout size, in 8 byte increments
|
|
376
|
+
const totalSize = 1 + // version
|
|
377
|
+
1 + // hasLocalState
|
|
378
|
+
1 + // cluster capacity
|
|
379
|
+
1 + // session count
|
|
380
|
+
1 + // cluster count
|
|
381
|
+
sessionIndexMap.size * 2 + // session IDs
|
|
382
|
+
finalSpace.clusters.length * 3 + // clusters: (sessionIndex, capacity, count)[]
|
|
383
|
+
localStateSize; // local state, if present
|
|
384
|
+
const serializedFloat = new Float64Array(totalSize);
|
|
385
|
+
const serializedUint = new BigUint64Array(serializedFloat.buffer);
|
|
386
|
+
let index = 0;
|
|
387
|
+
index = writeNumber(serializedFloat, index, currentWrittenVersion);
|
|
388
|
+
index = writeBoolean(serializedFloat, index, hasLocalState);
|
|
389
|
+
index = writeNumber(serializedFloat, index, this.clusterCapacity);
|
|
390
|
+
index = writeNumber(serializedFloat, index, sessionIndexMap.size);
|
|
391
|
+
index = writeNumber(serializedFloat, index, finalSpace.clusters.length);
|
|
392
|
+
for (const [session] of sessionIndexMap.entries()) {
|
|
393
|
+
index = writeNumericUuid(serializedUint, index, session.sessionUuid);
|
|
394
|
+
}
|
|
395
|
+
finalSpace.clusters.forEach((cluster) => {
|
|
396
|
+
index = writeNumber(serializedFloat, index, sessionIndexMap.get(cluster.session));
|
|
397
|
+
index = writeNumber(serializedFloat, index, cluster.capacity);
|
|
398
|
+
index = writeNumber(serializedFloat, index, cluster.count);
|
|
959
399
|
});
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
if (a.currentClusterDetails === undefined || b.currentClusterDetails === undefined) {
|
|
968
|
-
if (a.currentClusterDetails !== b.currentClusterDetails) {
|
|
969
|
-
return false;
|
|
970
|
-
}
|
|
971
|
-
return true;
|
|
972
|
-
}
|
|
973
|
-
if (checkCluster &&
|
|
974
|
-
!IdCompressor.idClustersEqual(a.currentClusterDetails.cluster, b.currentClusterDetails.cluster, false, compareLocalState)) {
|
|
975
|
-
return false;
|
|
976
|
-
}
|
|
977
|
-
return true;
|
|
978
|
-
}
|
|
979
|
-
static idClustersEqual(a, b, checkSessionData = true, compareLocalState = true) {
|
|
980
|
-
const areEqual = numericUuidEquals(a.baseUuid, b.baseUuid) &&
|
|
981
|
-
a.capacity === b.capacity &&
|
|
982
|
-
a.count === b.count &&
|
|
983
|
-
(!checkSessionData ||
|
|
984
|
-
IdCompressor.sessionDataEqual(a.session, b.session, false, compareLocalState)) &&
|
|
985
|
-
(a.overrides === undefined) === (b.overrides === undefined) &&
|
|
986
|
-
(a.overrides === undefined ||
|
|
987
|
-
compareMaps(a.overrides ?? fail("Overrides must be defined"), b.overrides ?? fail("Overrides must be defined"), (overrideA, overrideB) => {
|
|
988
|
-
if (compareLocalState) {
|
|
989
|
-
if (typeof overrideA === "string" || typeof overrideB === "string") {
|
|
990
|
-
return overrideA === overrideB;
|
|
991
|
-
}
|
|
992
|
-
const overridesEqual = overrideA.override === overrideB.override &&
|
|
993
|
-
overrideA.originalOverridingFinal ===
|
|
994
|
-
overrideB.originalOverridingFinal &&
|
|
995
|
-
(!compareLocalState ||
|
|
996
|
-
overrideA.associatedLocalId === overrideB.associatedLocalId);
|
|
997
|
-
return overridesEqual;
|
|
998
|
-
}
|
|
999
|
-
const uuidA = typeof overrideA === "string" ? overrideA : overrideA.override;
|
|
1000
|
-
const uuidB = typeof overrideB === "string" ? overrideB : overrideB.override;
|
|
1001
|
-
if (typeof overrideA !== "string" &&
|
|
1002
|
-
typeof overrideB !== "string" &&
|
|
1003
|
-
overrideA.originalOverridingFinal !== overrideB.originalOverridingFinal) {
|
|
1004
|
-
return false;
|
|
1005
|
-
}
|
|
1006
|
-
return uuidA === uuidB;
|
|
1007
|
-
}));
|
|
1008
|
-
return areEqual;
|
|
1009
|
-
}
|
|
1010
|
-
serialize(withSession) {
|
|
1011
|
-
const serializedSessions = [];
|
|
1012
|
-
const sessionIdToSessionIndex = new Map();
|
|
1013
|
-
for (const [sessionId, session] of this.sessions) {
|
|
1014
|
-
const isLocalSession = sessionId === this.localSessionId;
|
|
1015
|
-
const includeSession = sessionId !== reservedSessionId && // Ignore reserved clusters, but
|
|
1016
|
-
(session.lastFinalizedLocalId !== undefined || // always serialize sessions that made final IDs,
|
|
1017
|
-
(isLocalSession && withSession)); // include the un-acked local session if requested
|
|
1018
|
-
if (includeSession) {
|
|
1019
|
-
const sessionData = [sessionId];
|
|
1020
|
-
sessionIdToSessionIndex.set(sessionId, serializedSessions.length);
|
|
1021
|
-
serializedSessions.push(sessionData);
|
|
1022
|
-
}
|
|
1023
|
-
}
|
|
1024
|
-
const serializedClusters = [];
|
|
1025
|
-
for (const [baseFinalId, cluster] of this.finalIdToCluster.entries()) {
|
|
1026
|
-
const sessionId = stableIdFromNumericUuid(cluster.session.sessionUuid);
|
|
1027
|
-
if (sessionId !== reservedSessionId) {
|
|
1028
|
-
const sessionIndex = sessionIdToSessionIndex.get(sessionId) ??
|
|
1029
|
-
fail("Session object contains wrong session numeric UUID");
|
|
1030
|
-
const serializedCluster = [
|
|
1031
|
-
sessionIndex,
|
|
1032
|
-
cluster.capacity,
|
|
1033
|
-
];
|
|
1034
|
-
if (cluster.count !== cluster.capacity) {
|
|
1035
|
-
serializedCluster.push(cluster.count);
|
|
1036
|
-
}
|
|
1037
|
-
if (cluster.overrides !== undefined) {
|
|
1038
|
-
const serializedOverrides = [];
|
|
1039
|
-
for (const [finalId, override] of cluster.overrides) {
|
|
1040
|
-
const finalIdIndex = finalId - baseFinalId;
|
|
1041
|
-
if (typeof override === "string") {
|
|
1042
|
-
serializedOverrides.push([finalIdIndex, override]);
|
|
1043
|
-
}
|
|
1044
|
-
else if (override.originalOverridingFinal === finalId) {
|
|
1045
|
-
serializedOverrides.push([finalIdIndex, override.override]);
|
|
1046
|
-
}
|
|
1047
|
-
else {
|
|
1048
|
-
serializedOverrides.push([
|
|
1049
|
-
finalIdIndex,
|
|
1050
|
-
override.override,
|
|
1051
|
-
override.originalOverridingFinal,
|
|
1052
|
-
]);
|
|
1053
|
-
}
|
|
1054
|
-
}
|
|
1055
|
-
serializedCluster.push(serializedOverrides);
|
|
1056
|
-
}
|
|
1057
|
-
serializedClusters.push(serializedCluster);
|
|
1058
|
-
}
|
|
1059
|
-
}
|
|
1060
|
-
// Reserved session not serialized, and local session is present but may not make IDs
|
|
1061
|
-
assert(serializedSessions.length - this.sessions.size <= 2, 0x498 /* session not serialized */);
|
|
1062
|
-
const serializedIdCompressor = {
|
|
1063
|
-
version: currentWrittenVersion,
|
|
1064
|
-
clusterCapacity: this.clusterCapacity,
|
|
1065
|
-
sessions: serializedSessions,
|
|
1066
|
-
clusters: serializedClusters,
|
|
1067
|
-
};
|
|
1068
|
-
if (withSession) {
|
|
1069
|
-
const serializedWithSession = serializedIdCompressor;
|
|
1070
|
-
serializedWithSession.localSessionIndex = serializedWithSession.sessions.findIndex(([sessionId]) => sessionId === this.localSessionId);
|
|
1071
|
-
if (this.localIdCount > 0) {
|
|
1072
|
-
serializedWithSession.localState = {
|
|
1073
|
-
localIdCount: this.localIdCount,
|
|
1074
|
-
overrides: [...this.localOverrides.entries()].map((entry) => [...entry]),
|
|
1075
|
-
lastTakenLocalId: this.lastTakenLocalId,
|
|
1076
|
-
sessionNormalizer: this.sessionIdNormalizer.serialize(),
|
|
1077
|
-
};
|
|
400
|
+
if (hasLocalState) {
|
|
401
|
+
index = writeNumber(serializedFloat, index, this.localGenCount);
|
|
402
|
+
index = writeNumber(serializedFloat, index, this.nextRangeBaseGenCount);
|
|
403
|
+
index = writeNumber(serializedFloat, index, normalizer.idRanges.size);
|
|
404
|
+
for (const [leadingGenCount, count] of normalizer.idRanges.entries()) {
|
|
405
|
+
index = writeNumber(serializedFloat, index, leadingGenCount);
|
|
406
|
+
index = writeNumber(serializedFloat, index, count);
|
|
1078
407
|
}
|
|
1079
|
-
return serializedWithSession;
|
|
1080
408
|
}
|
|
409
|
+
assert(index === totalSize, 0x75b /* Serialized size was incorrectly calculated. */);
|
|
1081
410
|
this.logger?.sendTelemetryEvent({
|
|
1082
411
|
eventName: "RuntimeIdCompressor:SerializedIdCompressorSize",
|
|
1083
|
-
size:
|
|
1084
|
-
clusterCount:
|
|
1085
|
-
sessionCount:
|
|
412
|
+
size: serializedFloat.byteLength,
|
|
413
|
+
clusterCount: finalSpace.clusters.length,
|
|
414
|
+
sessionCount: sessionIndexMap.size,
|
|
1086
415
|
});
|
|
1087
|
-
return
|
|
1088
|
-
}
|
|
1089
|
-
static deserialize(
|
|
1090
|
-
const
|
|
1091
|
-
const
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
416
|
+
return bufferToString(serializedFloat.buffer, "base64");
|
|
417
|
+
}
|
|
418
|
+
static deserialize(serialized, sessionId) {
|
|
419
|
+
const buffer = stringToBuffer(serialized, "base64");
|
|
420
|
+
const index = {
|
|
421
|
+
index: 0,
|
|
422
|
+
bufferFloat: new Float64Array(buffer),
|
|
423
|
+
bufferUint: new BigUint64Array(buffer),
|
|
424
|
+
};
|
|
425
|
+
const version = readNumber(index);
|
|
426
|
+
assert(version === currentWrittenVersion, 0x75c /* Unknown serialized version. */);
|
|
427
|
+
const hasLocalState = readBoolean(index);
|
|
428
|
+
const clusterCapacity = readNumber(index);
|
|
429
|
+
const sessionCount = readNumber(index);
|
|
430
|
+
const clusterCount = readNumber(index);
|
|
431
|
+
// Sessions
|
|
432
|
+
let sessionOffset = 0;
|
|
433
|
+
const sessions = [];
|
|
434
|
+
if (!hasLocalState) {
|
|
435
|
+
// If !hasLocalState, there won't be a serialized local session ID so insert one at the beginning
|
|
436
|
+
assert(sessionId !== undefined, 0x75d /* Local session ID is undefined. */);
|
|
437
|
+
const localSessionNumeric = numericUuidFromStableId(sessionId);
|
|
438
|
+
sessions.push([localSessionNumeric, new Session(localSessionNumeric)]);
|
|
439
|
+
sessionOffset = 1;
|
|
1100
440
|
}
|
|
1101
441
|
else {
|
|
1102
|
-
|
|
442
|
+
assert(sessionId === undefined, 0x75e /* Local state should not exist in serialized form. */);
|
|
1103
443
|
}
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
if (serializedLocalState !== undefined) {
|
|
1108
|
-
// Do this part of local rehydration first since the cluster map population needs to query to local overrides
|
|
1109
|
-
compressor.localIdCount = serializedLocalState.localIdCount;
|
|
1110
|
-
compressor.lastTakenLocalId = serializedLocalState.lastTakenLocalId;
|
|
1111
|
-
if (serializedLocalState.overrides !== undefined) {
|
|
1112
|
-
for (const [localId, override] of serializedLocalState.overrides) {
|
|
1113
|
-
compressor.localOverrides.append(localId, override);
|
|
1114
|
-
localOverridesInverse.set(override, localId);
|
|
1115
|
-
compressor.clustersAndOverridesInversion.set(IdCompressor.createInversionKey(override), localId);
|
|
1116
|
-
}
|
|
1117
|
-
}
|
|
444
|
+
for (let i = 0; i < sessionCount; i++) {
|
|
445
|
+
const numeric = readNumericUuid(index);
|
|
446
|
+
sessions.push([numeric, new Session(numeric)]);
|
|
1118
447
|
}
|
|
1119
|
-
const
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
const
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
const lastFinalizedNormalized = lastFinalizedLocalId ?? 0;
|
|
1143
|
-
const clusterBase = compressor.nextClusterBaseFinalId;
|
|
1144
|
-
session.lastFinalizedLocalId = (lastFinalizedNormalized - count);
|
|
1145
|
-
session.currentClusterDetails = { clusterBase, cluster };
|
|
1146
|
-
compressor.nextClusterBaseFinalId = (compressor.nextClusterBaseFinalId +
|
|
1147
|
-
capacity);
|
|
1148
|
-
compressor.finalIdToCluster.append(clusterBase, cluster);
|
|
1149
|
-
compressor.clustersAndOverridesInversion.set(stableIdFromNumericUuid(cluster.baseUuid), {
|
|
1150
|
-
clusterBase,
|
|
1151
|
-
cluster,
|
|
1152
|
-
});
|
|
1153
|
-
if (overrides !== undefined) {
|
|
1154
|
-
cluster.overrides = new Map();
|
|
1155
|
-
for (const [finalIdIndex, override, originalOverridingFinal] of overrides) {
|
|
1156
|
-
const finalId = (clusterBase + finalIdIndex);
|
|
1157
|
-
if (originalOverridingFinal !== undefined) {
|
|
1158
|
-
const unifiedOverride = {
|
|
1159
|
-
override,
|
|
1160
|
-
originalOverridingFinal,
|
|
1161
|
-
};
|
|
1162
|
-
if (serializedLocalState !== undefined) {
|
|
1163
|
-
setPropertyIfDefined(localOverridesInverse.get(override), unifiedOverride, "associatedLocalId");
|
|
1164
|
-
}
|
|
1165
|
-
cluster.overrides.set(finalId, unifiedOverride);
|
|
1166
|
-
}
|
|
1167
|
-
else {
|
|
1168
|
-
const associatedLocal = localOverridesInverse.get(override);
|
|
1169
|
-
if (associatedLocal !== undefined && sessionId !== localSessionId) {
|
|
1170
|
-
// In this case, there is a local ID associated with this override, but this is the first cluster to contain
|
|
1171
|
-
// that override (because only the first cluster will have the string serialized). In this case, the override
|
|
1172
|
-
// needs to hold that local value.
|
|
1173
|
-
cluster.overrides.set(finalId, {
|
|
1174
|
-
override,
|
|
1175
|
-
originalOverridingFinal: finalId,
|
|
1176
|
-
associatedLocalId: associatedLocal,
|
|
1177
|
-
});
|
|
1178
|
-
}
|
|
1179
|
-
else {
|
|
1180
|
-
cluster.overrides.set(finalId, override);
|
|
1181
|
-
}
|
|
1182
|
-
const finalizedOverride = {
|
|
1183
|
-
cluster,
|
|
1184
|
-
originalOverridingFinal: finalId,
|
|
1185
|
-
};
|
|
1186
|
-
if (serializedLocalState !== undefined) {
|
|
1187
|
-
setPropertyIfDefined(associatedLocal, finalizedOverride, "associatedLocalId");
|
|
1188
|
-
}
|
|
1189
|
-
compressor.clustersAndOverridesInversion.set(IdCompressor.createInversionKey(override), finalizedOverride);
|
|
1190
|
-
}
|
|
1191
|
-
}
|
|
1192
|
-
}
|
|
1193
|
-
}
|
|
1194
|
-
if (serializedLocalState !== undefined) {
|
|
1195
|
-
compressor.sessionIdNormalizer = SessionIdNormalizer.deserialize(serializedLocalState.sessionNormalizer, (finalId) => {
|
|
1196
|
-
const [_, cluster] = compressor.finalIdToCluster.getPairOrNextLower(finalId) ??
|
|
1197
|
-
fail("Final in serialized normalizer was never created.");
|
|
1198
|
-
return cluster;
|
|
1199
|
-
});
|
|
1200
|
-
}
|
|
1201
|
-
assert(compressor.localSession.lastFinalizedLocalId === undefined ||
|
|
1202
|
-
compressor.localIdCount >= -compressor.localSession.lastFinalizedLocalId, 0x49a /* Inconsistent last finalized state when deserializing */);
|
|
448
|
+
const compressor = new IdCompressor(new Sessions(sessions));
|
|
449
|
+
compressor.clusterCapacity = clusterCapacity;
|
|
450
|
+
// Clusters
|
|
451
|
+
let baseFinalId = 0;
|
|
452
|
+
for (let i = 0; i < clusterCount; i++) {
|
|
453
|
+
const sessionIndex = readNumber(index);
|
|
454
|
+
const session = sessions[sessionIndex + sessionOffset][1];
|
|
455
|
+
const capacity = readNumber(index);
|
|
456
|
+
const count = readNumber(index);
|
|
457
|
+
const cluster = session.addNewCluster(baseFinalId, capacity, count);
|
|
458
|
+
compressor.finalSpace.addCluster(cluster);
|
|
459
|
+
baseFinalId += capacity;
|
|
460
|
+
}
|
|
461
|
+
// Local state
|
|
462
|
+
if (hasLocalState) {
|
|
463
|
+
compressor.localGenCount = readNumber(index);
|
|
464
|
+
compressor.nextRangeBaseGenCount = readNumber(index);
|
|
465
|
+
const normalizerCount = readNumber(index);
|
|
466
|
+
for (let i = 0; i < normalizerCount; i++) {
|
|
467
|
+
compressor.normalizer.addLocalRange(readNumber(index), readNumber(index));
|
|
468
|
+
}
|
|
469
|
+
}
|
|
470
|
+
assert(index.index === index.bufferFloat.length, 0x75f /* Failed to read entire serialized compressor. */);
|
|
1203
471
|
return compressor;
|
|
1204
472
|
}
|
|
1205
|
-
|
|
1206
|
-
if (
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
473
|
+
equals(other, includeLocalState) {
|
|
474
|
+
if (includeLocalState &&
|
|
475
|
+
(this.localSessionId !== other.localSessionId ||
|
|
476
|
+
!this.localSession.equals(other.localSession) ||
|
|
477
|
+
!this.normalizer.equals(other.normalizer) ||
|
|
478
|
+
this.nextRangeBaseGenCount !== other.nextRangeBaseGenCount ||
|
|
479
|
+
this.localGenCount !== other.localGenCount)) {
|
|
480
|
+
return false;
|
|
1212
481
|
}
|
|
1213
|
-
return
|
|
482
|
+
return (this.newClusterCapacity === other.newClusterCapacity &&
|
|
483
|
+
this.sessions.equals(other.sessions, includeLocalState) &&
|
|
484
|
+
this.finalSpace.equals(other.finalSpace));
|
|
1214
485
|
}
|
|
1215
486
|
}
|
|
1216
487
|
/**
|
|
1217
|
-
* Max allowed cluster size
|
|
488
|
+
* Max allowed initial cluster size.
|
|
1218
489
|
*/
|
|
1219
490
|
IdCompressor.maxClusterSize = 2 ** 20;
|
|
1220
|
-
/**
|
|
1221
|
-
* The version of `IdCompressor` that is currently persisted.
|
|
1222
|
-
*/
|
|
1223
|
-
const currentWrittenVersion = "0.0.1";
|
|
1224
|
-
/**
|
|
1225
|
-
* @returns whether or not the given serialized ID compressor has an ongoing session.
|
|
1226
|
-
*/
|
|
1227
|
-
export function hasOngoingSession(serialized) {
|
|
1228
|
-
return (serialized.localSessionIndex !==
|
|
1229
|
-
undefined);
|
|
1230
|
-
}
|
|
1231
|
-
function deserializeCluster(serializedCluster) {
|
|
1232
|
-
const [sessionIndex, capacity, countOrOverrides, overrides] = serializedCluster;
|
|
1233
|
-
const hasCount = typeof countOrOverrides === "number";
|
|
1234
|
-
return {
|
|
1235
|
-
sessionIndex,
|
|
1236
|
-
capacity,
|
|
1237
|
-
count: hasCount ? countOrOverrides : capacity,
|
|
1238
|
-
overrides: hasCount ? overrides : countOrOverrides,
|
|
1239
|
-
};
|
|
1240
|
-
}
|
|
1241
|
-
/**
|
|
1242
|
-
* Optimization used by the sorted-btree library to avoid allocating tuples every time a lookup method is called.
|
|
1243
|
-
* Lookup methods on BTree accept a pre-allocated array that it populates with the result of the lookup and retains no ownership
|
|
1244
|
-
* of after the call, so this array may be supplied to any of them. References to this array should not be retained elsewhere and
|
|
1245
|
-
* lookup results should be extracted from the tuple immediately after invocation.
|
|
1246
|
-
*/
|
|
1247
|
-
const reusedArray = [];
|
|
1248
491
|
//# sourceMappingURL=idCompressor.js.map
|