@fluidframework/tree 2.1.0-276326 → 2.1.0-281041
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/.eslintrc.cjs +7 -0
- package/.vscode/Tree.code-workspace +7 -1
- package/README.md +134 -29
- package/api-report/tree.alpha.api.md +1 -0
- package/api-report/tree.beta.api.md +1 -0
- package/api-report/tree.public.api.md +1 -0
- package/beta.d.ts +1 -1
- package/dist/beta.d.ts +1 -1
- package/dist/core/forest/editableForest.d.ts +6 -3
- package/dist/core/forest/editableForest.d.ts.map +1 -1
- package/dist/core/forest/editableForest.js +14 -4
- package/dist/core/forest/editableForest.js.map +1 -1
- package/dist/core/index.d.ts +1 -1
- package/dist/core/index.d.ts.map +1 -1
- package/dist/core/index.js +3 -1
- package/dist/core/index.js.map +1 -1
- package/dist/core/rebase/index.d.ts +1 -1
- package/dist/core/rebase/index.d.ts.map +1 -1
- package/dist/core/rebase/index.js +3 -1
- package/dist/core/rebase/index.js.map +1 -1
- package/dist/core/rebase/types.d.ts +2 -0
- package/dist/core/rebase/types.d.ts.map +1 -1
- package/dist/core/rebase/types.js +9 -1
- package/dist/core/rebase/types.js.map +1 -1
- package/dist/core/tree/visitDelta.d.ts.map +1 -1
- package/dist/core/tree/visitDelta.js.map +1 -1
- package/dist/events/events.d.ts +4 -1
- package/dist/events/events.d.ts.map +1 -1
- package/dist/events/events.js.map +1 -1
- package/dist/feature-libraries/default-schema/defaultEditBuilder.js +1 -1
- package/dist/feature-libraries/default-schema/defaultEditBuilder.js.map +1 -1
- package/dist/feature-libraries/default-schema/defaultFieldKinds.d.ts.map +1 -1
- package/dist/feature-libraries/default-schema/defaultFieldKinds.js +1 -0
- package/dist/feature-libraries/default-schema/defaultFieldKinds.js.map +1 -1
- package/dist/feature-libraries/flex-map-tree/mapTreeNode.d.ts +0 -2
- package/dist/feature-libraries/flex-map-tree/mapTreeNode.d.ts.map +1 -1
- package/dist/feature-libraries/flex-map-tree/mapTreeNode.js +0 -20
- package/dist/feature-libraries/flex-map-tree/mapTreeNode.js.map +1 -1
- package/dist/feature-libraries/flex-tree/flexTreeTypes.d.ts +0 -38
- package/dist/feature-libraries/flex-tree/flexTreeTypes.d.ts.map +1 -1
- package/dist/feature-libraries/flex-tree/flexTreeTypes.js.map +1 -1
- package/dist/feature-libraries/flex-tree/index.d.ts +1 -1
- package/dist/feature-libraries/flex-tree/index.d.ts.map +1 -1
- package/dist/feature-libraries/flex-tree/index.js.map +1 -1
- package/dist/feature-libraries/flex-tree/lazyField.d.ts +0 -4
- package/dist/feature-libraries/flex-tree/lazyField.d.ts.map +1 -1
- package/dist/feature-libraries/flex-tree/lazyField.js +1 -14
- package/dist/feature-libraries/flex-tree/lazyField.js.map +1 -1
- package/dist/feature-libraries/flex-tree/lazyNode.d.ts +0 -1
- package/dist/feature-libraries/flex-tree/lazyNode.d.ts.map +1 -1
- package/dist/feature-libraries/flex-tree/lazyNode.js +0 -3
- package/dist/feature-libraries/flex-tree/lazyNode.js.map +1 -1
- package/dist/feature-libraries/index.d.ts +3 -3
- package/dist/feature-libraries/index.d.ts.map +1 -1
- package/dist/feature-libraries/index.js +2 -2
- package/dist/feature-libraries/index.js.map +1 -1
- package/dist/feature-libraries/modular-schema/crossFieldQueries.d.ts +11 -0
- package/dist/feature-libraries/modular-schema/crossFieldQueries.d.ts.map +1 -1
- package/dist/feature-libraries/modular-schema/crossFieldQueries.js.map +1 -1
- package/dist/feature-libraries/modular-schema/discrepancies.d.ts +96 -0
- package/dist/feature-libraries/modular-schema/discrepancies.d.ts.map +1 -0
- package/dist/feature-libraries/modular-schema/discrepancies.js +264 -0
- package/dist/feature-libraries/modular-schema/discrepancies.js.map +1 -0
- package/dist/feature-libraries/modular-schema/fieldChangeHandler.d.ts +9 -2
- package/dist/feature-libraries/modular-schema/fieldChangeHandler.d.ts.map +1 -1
- package/dist/feature-libraries/modular-schema/fieldChangeHandler.js.map +1 -1
- package/dist/feature-libraries/modular-schema/genericFieldKind.d.ts.map +1 -1
- package/dist/feature-libraries/modular-schema/genericFieldKind.js +3 -0
- package/dist/feature-libraries/modular-schema/genericFieldKind.js.map +1 -1
- package/dist/feature-libraries/modular-schema/index.d.ts +2 -1
- package/dist/feature-libraries/modular-schema/index.d.ts.map +1 -1
- package/dist/feature-libraries/modular-schema/index.js +3 -1
- package/dist/feature-libraries/modular-schema/index.js.map +1 -1
- package/dist/feature-libraries/modular-schema/modularChangeCodecs.d.ts.map +1 -1
- package/dist/feature-libraries/modular-schema/modularChangeCodecs.js +42 -26
- package/dist/feature-libraries/modular-schema/modularChangeCodecs.js.map +1 -1
- package/dist/feature-libraries/modular-schema/modularChangeFamily.d.ts +51 -2
- package/dist/feature-libraries/modular-schema/modularChangeFamily.d.ts.map +1 -1
- package/dist/feature-libraries/modular-schema/modularChangeFamily.js +830 -245
- package/dist/feature-libraries/modular-schema/modularChangeFamily.js.map +1 -1
- package/dist/feature-libraries/modular-schema/modularChangeFormat.d.ts.map +1 -1
- package/dist/feature-libraries/modular-schema/modularChangeFormat.js +2 -0
- package/dist/feature-libraries/modular-schema/modularChangeFormat.js.map +1 -1
- package/dist/feature-libraries/modular-schema/modularChangeTypes.d.ts +44 -1
- package/dist/feature-libraries/modular-schema/modularChangeTypes.d.ts.map +1 -1
- package/dist/feature-libraries/modular-schema/modularChangeTypes.js.map +1 -1
- package/dist/feature-libraries/node-key/index.d.ts +0 -1
- package/dist/feature-libraries/node-key/index.d.ts.map +1 -1
- package/dist/feature-libraries/node-key/index.js +1 -3
- package/dist/feature-libraries/node-key/index.js.map +1 -1
- package/dist/feature-libraries/optional-field/optionalField.d.ts.map +1 -1
- package/dist/feature-libraries/optional-field/optionalField.js +1 -0
- package/dist/feature-libraries/optional-field/optionalField.js.map +1 -1
- package/dist/feature-libraries/sequence-field/index.d.ts +1 -1
- package/dist/feature-libraries/sequence-field/index.d.ts.map +1 -1
- package/dist/feature-libraries/sequence-field/index.js +1 -2
- package/dist/feature-libraries/sequence-field/index.js.map +1 -1
- package/dist/feature-libraries/sequence-field/invert.js +1 -1
- package/dist/feature-libraries/sequence-field/invert.js.map +1 -1
- package/dist/feature-libraries/sequence-field/rebase.js +6 -1
- package/dist/feature-libraries/sequence-field/rebase.js.map +1 -1
- package/dist/feature-libraries/sequence-field/sequenceFieldChangeHandler.d.ts.map +1 -1
- package/dist/feature-libraries/sequence-field/sequenceFieldChangeHandler.js +1 -0
- package/dist/feature-libraries/sequence-field/sequenceFieldChangeHandler.js.map +1 -1
- package/dist/feature-libraries/sequence-field/utils.d.ts +2 -17
- package/dist/feature-libraries/sequence-field/utils.d.ts.map +1 -1
- package/dist/feature-libraries/sequence-field/utils.js +31 -39
- package/dist/feature-libraries/sequence-field/utils.js.map +1 -1
- package/dist/feature-libraries/typed-schema/typedTreeSchema.d.ts +1 -0
- package/dist/feature-libraries/typed-schema/typedTreeSchema.d.ts.map +1 -1
- package/dist/feature-libraries/typed-schema/typedTreeSchema.js +2 -0
- package/dist/feature-libraries/typed-schema/typedTreeSchema.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js.map +1 -1
- package/dist/packageVersion.d.ts +1 -1
- package/dist/packageVersion.js +1 -1
- package/dist/packageVersion.js.map +1 -1
- package/dist/public.d.ts +1 -1
- package/dist/shared-tree/schematizingTreeView.d.ts +4 -2
- package/dist/shared-tree/schematizingTreeView.d.ts.map +1 -1
- package/dist/shared-tree/schematizingTreeView.js +240 -184
- package/dist/shared-tree/schematizingTreeView.js.map +1 -1
- package/dist/shared-tree/sharedTree.d.ts +3 -2
- package/dist/shared-tree/sharedTree.d.ts.map +1 -1
- package/dist/shared-tree/sharedTree.js +150 -90
- package/dist/shared-tree/sharedTree.js.map +1 -1
- package/dist/shared-tree/treeCheckout.d.ts.map +1 -1
- package/dist/shared-tree/treeCheckout.js +2 -1
- package/dist/shared-tree/treeCheckout.js.map +1 -1
- package/dist/shared-tree-core/branch.d.ts.map +1 -1
- package/dist/shared-tree-core/branch.js +1 -0
- package/dist/shared-tree-core/branch.js.map +1 -1
- package/dist/shared-tree-core/sharedTreeCore.d.ts +4 -6
- package/dist/shared-tree-core/sharedTreeCore.d.ts.map +1 -1
- package/dist/shared-tree-core/sharedTreeCore.js +265 -209
- package/dist/shared-tree-core/sharedTreeCore.js.map +1 -1
- package/dist/simple-tree/arrayNode.d.ts +4 -0
- package/dist/simple-tree/arrayNode.d.ts.map +1 -1
- package/dist/simple-tree/arrayNode.js +36 -19
- package/dist/simple-tree/arrayNode.js.map +1 -1
- package/dist/simple-tree/leafNodeSchema.d.ts +22 -1
- package/dist/simple-tree/leafNodeSchema.d.ts.map +1 -1
- package/dist/simple-tree/leafNodeSchema.js +2 -1
- package/dist/simple-tree/leafNodeSchema.js.map +1 -1
- package/dist/simple-tree/mapNode.d.ts.map +1 -1
- package/dist/simple-tree/mapNode.js.map +1 -1
- package/dist/simple-tree/objectNode.d.ts.map +1 -1
- package/dist/simple-tree/objectNode.js +2 -1
- package/dist/simple-tree/objectNode.js.map +1 -1
- package/dist/simple-tree/proxies.d.ts.map +1 -1
- package/dist/simple-tree/proxies.js +2 -4
- package/dist/simple-tree/proxies.js.map +1 -1
- package/dist/simple-tree/schemaFactory.d.ts +24 -1
- package/dist/simple-tree/schemaFactory.d.ts.map +1 -1
- package/dist/simple-tree/schemaFactory.js +40 -4
- package/dist/simple-tree/schemaFactory.js.map +1 -1
- package/dist/simple-tree/schemaTypes.d.ts +36 -1
- package/dist/simple-tree/schemaTypes.d.ts.map +1 -1
- package/dist/simple-tree/schemaTypes.js.map +1 -1
- package/dist/simple-tree/toFlexSchema.d.ts +2 -2
- package/dist/simple-tree/toFlexSchema.d.ts.map +1 -1
- package/dist/simple-tree/toFlexSchema.js +3 -2
- package/dist/simple-tree/toFlexSchema.js.map +1 -1
- package/dist/simple-tree/tree.d.ts +4 -1
- package/dist/simple-tree/tree.d.ts.map +1 -1
- package/dist/simple-tree/tree.js +48 -1
- package/dist/simple-tree/tree.js.map +1 -1
- package/dist/simple-tree/treeNodeApi.d.ts.map +1 -1
- package/dist/simple-tree/treeNodeApi.js +10 -10
- package/dist/simple-tree/treeNodeApi.js.map +1 -1
- package/dist/simple-tree/types.d.ts +22 -3
- package/dist/simple-tree/types.d.ts.map +1 -1
- package/dist/simple-tree/types.js +32 -21
- package/dist/simple-tree/types.js.map +1 -1
- package/dist/util/breakable.d.ts +83 -0
- package/dist/util/breakable.d.ts.map +1 -0
- package/dist/util/breakable.js +178 -0
- package/dist/util/breakable.js.map +1 -0
- package/dist/util/index.d.ts +3 -2
- package/dist/util/index.d.ts.map +1 -1
- package/dist/util/index.js +9 -2
- package/dist/util/index.js.map +1 -1
- package/dist/util/nestedMap.d.ts +23 -3
- package/dist/util/nestedMap.d.ts.map +1 -1
- package/dist/util/nestedMap.js +38 -7
- package/dist/util/nestedMap.js.map +1 -1
- package/dist/util/utils.d.ts +7 -0
- package/dist/util/utils.d.ts.map +1 -1
- package/dist/util/utils.js +15 -1
- package/dist/util/utils.js.map +1 -1
- package/docs/README.md +1 -1
- package/docs/main/compatibility.md +1 -1
- package/docs/main/stored-and-view-schema.md +1 -1
- package/internal.d.ts +1 -1
- package/lib/beta.d.ts +1 -1
- package/lib/core/forest/editableForest.d.ts +6 -3
- package/lib/core/forest/editableForest.d.ts.map +1 -1
- package/lib/core/forest/editableForest.js +15 -5
- package/lib/core/forest/editableForest.js.map +1 -1
- package/lib/core/index.d.ts +1 -1
- package/lib/core/index.d.ts.map +1 -1
- package/lib/core/index.js +1 -1
- package/lib/core/index.js.map +1 -1
- package/lib/core/rebase/index.d.ts +1 -1
- package/lib/core/rebase/index.d.ts.map +1 -1
- package/lib/core/rebase/index.js +1 -1
- package/lib/core/rebase/index.js.map +1 -1
- package/lib/core/rebase/types.d.ts +2 -0
- package/lib/core/rebase/types.d.ts.map +1 -1
- package/lib/core/rebase/types.js +7 -1
- package/lib/core/rebase/types.js.map +1 -1
- package/lib/core/tree/visitDelta.d.ts.map +1 -1
- package/lib/core/tree/visitDelta.js.map +1 -1
- package/lib/events/events.d.ts +4 -1
- package/lib/events/events.d.ts.map +1 -1
- package/lib/events/events.js.map +1 -1
- package/lib/feature-libraries/default-schema/defaultEditBuilder.js +1 -1
- package/lib/feature-libraries/default-schema/defaultEditBuilder.js.map +1 -1
- package/lib/feature-libraries/default-schema/defaultFieldKinds.d.ts.map +1 -1
- package/lib/feature-libraries/default-schema/defaultFieldKinds.js +1 -0
- package/lib/feature-libraries/default-schema/defaultFieldKinds.js.map +1 -1
- package/lib/feature-libraries/flex-map-tree/mapTreeNode.d.ts +0 -2
- package/lib/feature-libraries/flex-map-tree/mapTreeNode.d.ts.map +1 -1
- package/lib/feature-libraries/flex-map-tree/mapTreeNode.js +0 -20
- package/lib/feature-libraries/flex-map-tree/mapTreeNode.js.map +1 -1
- package/lib/feature-libraries/flex-tree/flexTreeTypes.d.ts +0 -38
- package/lib/feature-libraries/flex-tree/flexTreeTypes.d.ts.map +1 -1
- package/lib/feature-libraries/flex-tree/flexTreeTypes.js.map +1 -1
- package/lib/feature-libraries/flex-tree/index.d.ts +1 -1
- package/lib/feature-libraries/flex-tree/index.d.ts.map +1 -1
- package/lib/feature-libraries/flex-tree/index.js.map +1 -1
- package/lib/feature-libraries/flex-tree/lazyField.d.ts +0 -4
- package/lib/feature-libraries/flex-tree/lazyField.d.ts.map +1 -1
- package/lib/feature-libraries/flex-tree/lazyField.js +1 -14
- package/lib/feature-libraries/flex-tree/lazyField.js.map +1 -1
- package/lib/feature-libraries/flex-tree/lazyNode.d.ts +0 -1
- package/lib/feature-libraries/flex-tree/lazyNode.d.ts.map +1 -1
- package/lib/feature-libraries/flex-tree/lazyNode.js +0 -3
- package/lib/feature-libraries/flex-tree/lazyNode.js.map +1 -1
- package/lib/feature-libraries/index.d.ts +3 -3
- package/lib/feature-libraries/index.d.ts.map +1 -1
- package/lib/feature-libraries/index.js +2 -2
- package/lib/feature-libraries/index.js.map +1 -1
- package/lib/feature-libraries/modular-schema/crossFieldQueries.d.ts +11 -0
- package/lib/feature-libraries/modular-schema/crossFieldQueries.d.ts.map +1 -1
- package/lib/feature-libraries/modular-schema/crossFieldQueries.js.map +1 -1
- package/lib/feature-libraries/modular-schema/discrepancies.d.ts +96 -0
- package/lib/feature-libraries/modular-schema/discrepancies.d.ts.map +1 -0
- package/lib/feature-libraries/modular-schema/discrepancies.js +260 -0
- package/lib/feature-libraries/modular-schema/discrepancies.js.map +1 -0
- package/lib/feature-libraries/modular-schema/fieldChangeHandler.d.ts +9 -2
- package/lib/feature-libraries/modular-schema/fieldChangeHandler.d.ts.map +1 -1
- package/lib/feature-libraries/modular-schema/fieldChangeHandler.js.map +1 -1
- package/lib/feature-libraries/modular-schema/genericFieldKind.d.ts.map +1 -1
- package/lib/feature-libraries/modular-schema/genericFieldKind.js +3 -0
- package/lib/feature-libraries/modular-schema/genericFieldKind.js.map +1 -1
- package/lib/feature-libraries/modular-schema/index.d.ts +2 -1
- package/lib/feature-libraries/modular-schema/index.d.ts.map +1 -1
- package/lib/feature-libraries/modular-schema/index.js +1 -0
- package/lib/feature-libraries/modular-schema/index.js.map +1 -1
- package/lib/feature-libraries/modular-schema/modularChangeCodecs.d.ts.map +1 -1
- package/lib/feature-libraries/modular-schema/modularChangeCodecs.js +42 -26
- package/lib/feature-libraries/modular-schema/modularChangeCodecs.js.map +1 -1
- package/lib/feature-libraries/modular-schema/modularChangeFamily.d.ts +51 -2
- package/lib/feature-libraries/modular-schema/modularChangeFamily.d.ts.map +1 -1
- package/lib/feature-libraries/modular-schema/modularChangeFamily.js +829 -247
- package/lib/feature-libraries/modular-schema/modularChangeFamily.js.map +1 -1
- package/lib/feature-libraries/modular-schema/modularChangeFormat.d.ts.map +1 -1
- package/lib/feature-libraries/modular-schema/modularChangeFormat.js +2 -0
- package/lib/feature-libraries/modular-schema/modularChangeFormat.js.map +1 -1
- package/lib/feature-libraries/modular-schema/modularChangeTypes.d.ts +44 -1
- package/lib/feature-libraries/modular-schema/modularChangeTypes.d.ts.map +1 -1
- package/lib/feature-libraries/modular-schema/modularChangeTypes.js.map +1 -1
- package/lib/feature-libraries/node-key/index.d.ts +0 -1
- package/lib/feature-libraries/node-key/index.d.ts.map +1 -1
- package/lib/feature-libraries/node-key/index.js +0 -1
- package/lib/feature-libraries/node-key/index.js.map +1 -1
- package/lib/feature-libraries/optional-field/optionalField.d.ts.map +1 -1
- package/lib/feature-libraries/optional-field/optionalField.js +1 -0
- package/lib/feature-libraries/optional-field/optionalField.js.map +1 -1
- package/lib/feature-libraries/sequence-field/index.d.ts +1 -1
- package/lib/feature-libraries/sequence-field/index.d.ts.map +1 -1
- package/lib/feature-libraries/sequence-field/index.js +1 -1
- package/lib/feature-libraries/sequence-field/index.js.map +1 -1
- package/lib/feature-libraries/sequence-field/invert.js +1 -1
- package/lib/feature-libraries/sequence-field/invert.js.map +1 -1
- package/lib/feature-libraries/sequence-field/rebase.js +6 -1
- package/lib/feature-libraries/sequence-field/rebase.js.map +1 -1
- package/lib/feature-libraries/sequence-field/sequenceFieldChangeHandler.d.ts.map +1 -1
- package/lib/feature-libraries/sequence-field/sequenceFieldChangeHandler.js +2 -1
- package/lib/feature-libraries/sequence-field/sequenceFieldChangeHandler.js.map +1 -1
- package/lib/feature-libraries/sequence-field/utils.d.ts +2 -17
- package/lib/feature-libraries/sequence-field/utils.d.ts.map +1 -1
- package/lib/feature-libraries/sequence-field/utils.js +31 -39
- package/lib/feature-libraries/sequence-field/utils.js.map +1 -1
- package/lib/feature-libraries/typed-schema/typedTreeSchema.d.ts +1 -0
- package/lib/feature-libraries/typed-schema/typedTreeSchema.d.ts.map +1 -1
- package/lib/feature-libraries/typed-schema/typedTreeSchema.js +4 -2
- package/lib/feature-libraries/typed-schema/typedTreeSchema.js.map +1 -1
- package/lib/index.d.ts +1 -1
- package/lib/index.d.ts.map +1 -1
- package/lib/index.js.map +1 -1
- package/lib/packageVersion.d.ts +1 -1
- package/lib/packageVersion.js +1 -1
- package/lib/packageVersion.js.map +1 -1
- package/lib/public.d.ts +1 -1
- package/lib/shared-tree/schematizingTreeView.d.ts +4 -2
- package/lib/shared-tree/schematizingTreeView.d.ts.map +1 -1
- package/lib/shared-tree/schematizingTreeView.js +242 -185
- package/lib/shared-tree/schematizingTreeView.js.map +1 -1
- package/lib/shared-tree/sharedTree.d.ts +3 -2
- package/lib/shared-tree/sharedTree.d.ts.map +1 -1
- package/lib/shared-tree/sharedTree.js +151 -90
- package/lib/shared-tree/sharedTree.js.map +1 -1
- package/lib/shared-tree/treeCheckout.d.ts.map +1 -1
- package/lib/shared-tree/treeCheckout.js +2 -1
- package/lib/shared-tree/treeCheckout.js.map +1 -1
- package/lib/shared-tree-core/branch.d.ts.map +1 -1
- package/lib/shared-tree-core/branch.js +1 -0
- package/lib/shared-tree-core/branch.js.map +1 -1
- package/lib/shared-tree-core/sharedTreeCore.d.ts +4 -6
- package/lib/shared-tree-core/sharedTreeCore.d.ts.map +1 -1
- package/lib/shared-tree-core/sharedTreeCore.js +267 -210
- package/lib/shared-tree-core/sharedTreeCore.js.map +1 -1
- package/lib/simple-tree/arrayNode.d.ts +4 -0
- package/lib/simple-tree/arrayNode.d.ts.map +1 -1
- package/lib/simple-tree/arrayNode.js +38 -21
- package/lib/simple-tree/arrayNode.js.map +1 -1
- package/lib/simple-tree/leafNodeSchema.d.ts +22 -1
- package/lib/simple-tree/leafNodeSchema.d.ts.map +1 -1
- package/lib/simple-tree/leafNodeSchema.js +1 -1
- package/lib/simple-tree/leafNodeSchema.js.map +1 -1
- package/lib/simple-tree/mapNode.d.ts.map +1 -1
- package/lib/simple-tree/mapNode.js.map +1 -1
- package/lib/simple-tree/objectNode.d.ts.map +1 -1
- package/lib/simple-tree/objectNode.js +3 -2
- package/lib/simple-tree/objectNode.js.map +1 -1
- package/lib/simple-tree/proxies.d.ts.map +1 -1
- package/lib/simple-tree/proxies.js +2 -4
- package/lib/simple-tree/proxies.js.map +1 -1
- package/lib/simple-tree/schemaFactory.d.ts +24 -1
- package/lib/simple-tree/schemaFactory.d.ts.map +1 -1
- package/lib/simple-tree/schemaFactory.js +38 -3
- package/lib/simple-tree/schemaFactory.js.map +1 -1
- package/lib/simple-tree/schemaTypes.d.ts +36 -1
- package/lib/simple-tree/schemaTypes.d.ts.map +1 -1
- package/lib/simple-tree/schemaTypes.js.map +1 -1
- package/lib/simple-tree/toFlexSchema.d.ts +2 -2
- package/lib/simple-tree/toFlexSchema.d.ts.map +1 -1
- package/lib/simple-tree/toFlexSchema.js +3 -2
- package/lib/simple-tree/toFlexSchema.js.map +1 -1
- package/lib/simple-tree/tree.d.ts +4 -1
- package/lib/simple-tree/tree.d.ts.map +1 -1
- package/lib/simple-tree/tree.js +44 -0
- package/lib/simple-tree/tree.js.map +1 -1
- package/lib/simple-tree/treeNodeApi.d.ts.map +1 -1
- package/lib/simple-tree/treeNodeApi.js +11 -11
- package/lib/simple-tree/treeNodeApi.js.map +1 -1
- package/lib/simple-tree/types.d.ts +22 -3
- package/lib/simple-tree/types.d.ts.map +1 -1
- package/lib/simple-tree/types.js +32 -21
- package/lib/simple-tree/types.js.map +1 -1
- package/lib/util/breakable.d.ts +83 -0
- package/lib/util/breakable.d.ts.map +1 -0
- package/lib/util/breakable.js +171 -0
- package/lib/util/breakable.js.map +1 -0
- package/lib/util/index.d.ts +3 -2
- package/lib/util/index.d.ts.map +1 -1
- package/lib/util/index.js +3 -2
- package/lib/util/index.js.map +1 -1
- package/lib/util/nestedMap.d.ts +23 -3
- package/lib/util/nestedMap.d.ts.map +1 -1
- package/lib/util/nestedMap.js +36 -6
- package/lib/util/nestedMap.js.map +1 -1
- package/lib/util/utils.d.ts +7 -0
- package/lib/util/utils.d.ts.map +1 -1
- package/lib/util/utils.js +13 -0
- package/lib/util/utils.js.map +1 -1
- package/package.json +29 -27
- package/src/core/forest/editableForest.ts +17 -4
- package/src/core/index.ts +2 -0
- package/src/core/rebase/index.ts +2 -0
- package/src/core/rebase/types.ts +17 -0
- package/src/core/tree/visitDelta.ts +1 -0
- package/src/events/events.ts +4 -2
- package/src/feature-libraries/default-schema/defaultEditBuilder.ts +1 -1
- package/src/feature-libraries/default-schema/defaultFieldKinds.ts +1 -0
- package/src/feature-libraries/flex-map-tree/mapTreeNode.ts +0 -30
- package/src/feature-libraries/flex-tree/flexTreeTypes.ts +0 -43
- package/src/feature-libraries/flex-tree/index.ts +0 -1
- package/src/feature-libraries/flex-tree/lazyField.ts +1 -21
- package/src/feature-libraries/flex-tree/lazyNode.ts +0 -6
- package/src/feature-libraries/index.ts +1 -2
- package/src/feature-libraries/modular-schema/crossFieldQueries.ts +18 -0
- package/src/feature-libraries/modular-schema/discrepancies.ts +395 -0
- package/src/feature-libraries/modular-schema/fieldChangeHandler.ts +10 -2
- package/src/feature-libraries/modular-schema/genericFieldKind.ts +3 -0
- package/src/feature-libraries/modular-schema/index.ts +2 -0
- package/src/feature-libraries/modular-schema/modularChangeCodecs.ts +81 -35
- package/src/feature-libraries/modular-schema/modularChangeFamily.ts +1529 -454
- package/src/feature-libraries/modular-schema/modularChangeFormat.ts +2 -0
- package/src/feature-libraries/modular-schema/modularChangeTypes.ts +51 -0
- package/src/feature-libraries/node-key/index.ts +0 -1
- package/src/feature-libraries/optional-field/optionalField.ts +1 -0
- package/src/feature-libraries/sequence-field/index.ts +0 -2
- package/src/feature-libraries/sequence-field/invert.ts +1 -1
- package/src/feature-libraries/sequence-field/rebase.ts +7 -1
- package/src/feature-libraries/sequence-field/sequenceFieldChangeHandler.ts +2 -1
- package/src/feature-libraries/sequence-field/utils.ts +37 -85
- package/src/feature-libraries/typed-schema/typedTreeSchema.ts +10 -0
- package/src/index.ts +0 -1
- package/src/packageVersion.ts +1 -1
- package/src/shared-tree/schematizingTreeView.ts +6 -2
- package/src/shared-tree/sharedTree.ts +5 -2
- package/src/shared-tree/treeCheckout.ts +6 -2
- package/src/shared-tree-core/branch.ts +1 -0
- package/src/shared-tree-core/sharedTreeCore.ts +18 -6
- package/src/simple-tree/arrayNode.ts +49 -22
- package/src/simple-tree/leafNodeSchema.ts +1 -1
- package/src/simple-tree/mapNode.ts +2 -2
- package/src/simple-tree/objectNode.ts +9 -3
- package/src/simple-tree/proxies.ts +2 -4
- package/src/simple-tree/schemaFactory.ts +45 -2
- package/src/simple-tree/schemaTypes.ts +36 -1
- package/src/simple-tree/toFlexSchema.ts +5 -4
- package/src/simple-tree/tree.ts +65 -4
- package/src/simple-tree/treeNodeApi.ts +15 -15
- package/src/simple-tree/types.ts +60 -30
- package/src/util/breakable.ts +214 -0
- package/src/util/index.ts +10 -0
- package/src/util/nestedMap.ts +49 -11
- package/src/util/utils.ts +17 -0
- package/dist/feature-libraries/node-key/nodeKeyIndex.d.ts +0 -41
- package/dist/feature-libraries/node-key/nodeKeyIndex.d.ts.map +0 -1
- package/dist/feature-libraries/node-key/nodeKeyIndex.js +0 -101
- package/dist/feature-libraries/node-key/nodeKeyIndex.js.map +0 -1
- package/lib/feature-libraries/node-key/nodeKeyIndex.d.ts +0 -41
- package/lib/feature-libraries/node-key/nodeKeyIndex.d.ts.map +0 -1
- package/lib/feature-libraries/node-key/nodeKeyIndex.js +0 -97
- package/lib/feature-libraries/node-key/nodeKeyIndex.js.map +0 -1
- package/src/feature-libraries/node-key/nodeKeyIndex.ts +0 -132
|
@@ -35,15 +35,17 @@ import {
|
|
|
35
35
|
makeAnonChange,
|
|
36
36
|
makeDetachedNodeId,
|
|
37
37
|
mapCursorField,
|
|
38
|
+
replaceAtomRevisions,
|
|
38
39
|
revisionMetadataSourceFromInfo,
|
|
40
|
+
setInChangeAtomIdMap,
|
|
41
|
+
areEqualChangeAtomIds,
|
|
42
|
+
getFromChangeAtomIdMap,
|
|
39
43
|
type ChangeAtomId,
|
|
40
44
|
} from "../../core/index.js";
|
|
41
45
|
import {
|
|
42
46
|
type IdAllocationState,
|
|
43
47
|
type IdAllocator,
|
|
44
48
|
type Mutable,
|
|
45
|
-
type NestedSet,
|
|
46
|
-
addToNestedSet,
|
|
47
49
|
brand,
|
|
48
50
|
deleteFromNestedMap,
|
|
49
51
|
fail,
|
|
@@ -53,10 +55,11 @@ import {
|
|
|
53
55
|
idAllocatorFromState,
|
|
54
56
|
nestedMapFromFlatList,
|
|
55
57
|
nestedMapToFlatList,
|
|
56
|
-
nestedSetContains,
|
|
57
58
|
populateNestedMap,
|
|
58
59
|
setInNestedMap,
|
|
59
60
|
tryGetFromNestedMap,
|
|
61
|
+
type NestedMap,
|
|
62
|
+
type RangeQueryResult,
|
|
60
63
|
} from "../../util/index.js";
|
|
61
64
|
import {
|
|
62
65
|
type TreeChunk,
|
|
@@ -80,19 +83,19 @@ import {
|
|
|
80
83
|
type RebaseRevisionMetadata,
|
|
81
84
|
} from "./fieldChangeHandler.js";
|
|
82
85
|
import { type FieldKindWithEditor, withEditor } from "./fieldKindWithEditor.js";
|
|
83
|
-
import {
|
|
84
|
-
convertGenericChange,
|
|
85
|
-
genericFieldKind,
|
|
86
|
-
newGenericChangeset,
|
|
87
|
-
} from "./genericFieldKind.js";
|
|
86
|
+
import { convertGenericChange, genericFieldKind } from "./genericFieldKind.js";
|
|
88
87
|
import type { GenericChangeset } from "./genericFieldKindTypes.js";
|
|
89
88
|
import type {
|
|
89
|
+
CrossFieldKeyRange,
|
|
90
|
+
CrossFieldKeyTable,
|
|
90
91
|
FieldChange,
|
|
91
92
|
FieldChangeMap,
|
|
92
93
|
FieldChangeset,
|
|
94
|
+
FieldId,
|
|
93
95
|
ModularChangeset,
|
|
94
96
|
NodeChangeset,
|
|
95
97
|
NodeId,
|
|
98
|
+
TupleBTree,
|
|
96
99
|
} from "./modularChangeTypes.js";
|
|
97
100
|
|
|
98
101
|
/**
|
|
@@ -127,56 +130,59 @@ export class ModularChangeFamily
|
|
|
127
130
|
* The returned `FieldChangeset`s may be a shallow copy of the input `FieldChange`s.
|
|
128
131
|
*/
|
|
129
132
|
private normalizeFieldChanges(
|
|
130
|
-
change1: FieldChange
|
|
131
|
-
change2: FieldChange
|
|
133
|
+
change1: FieldChange,
|
|
134
|
+
change2: FieldChange,
|
|
132
135
|
genId: IdAllocator,
|
|
133
136
|
revisionMetadata: RevisionMetadataSource,
|
|
134
137
|
): {
|
|
135
|
-
fieldKind:
|
|
136
|
-
|
|
137
|
-
|
|
138
|
+
fieldKind: FieldKindIdentifier;
|
|
139
|
+
changeHandler: FieldChangeHandler<unknown>;
|
|
140
|
+
change1: FieldChangeset;
|
|
141
|
+
change2: FieldChangeset;
|
|
138
142
|
} {
|
|
139
143
|
// TODO: Handle the case where changes have conflicting field kinds
|
|
140
144
|
const kind =
|
|
141
|
-
change1
|
|
145
|
+
change1.fieldKind !== genericFieldKind.identifier
|
|
142
146
|
? change1.fieldKind
|
|
143
|
-
: change2
|
|
147
|
+
: change2.fieldKind;
|
|
144
148
|
|
|
145
149
|
if (kind === genericFieldKind.identifier) {
|
|
146
|
-
//
|
|
150
|
+
// Both changes are generic
|
|
147
151
|
return {
|
|
148
|
-
fieldKind: genericFieldKind,
|
|
149
|
-
|
|
150
|
-
|
|
152
|
+
fieldKind: genericFieldKind.identifier,
|
|
153
|
+
changeHandler: genericFieldKind.changeHandler,
|
|
154
|
+
change1: change1.change,
|
|
155
|
+
change2: change2.change,
|
|
151
156
|
};
|
|
152
157
|
}
|
|
153
158
|
const fieldKind = getFieldKind(this.fieldKinds, kind);
|
|
154
|
-
const
|
|
159
|
+
const changeHandler = fieldKind.changeHandler;
|
|
155
160
|
const normalizedChange1 = this.normalizeFieldChange(
|
|
156
161
|
change1,
|
|
157
|
-
|
|
162
|
+
changeHandler,
|
|
158
163
|
genId,
|
|
159
164
|
revisionMetadata,
|
|
160
165
|
);
|
|
161
166
|
const normalizedChange2 = this.normalizeFieldChange(
|
|
162
167
|
change2,
|
|
163
|
-
|
|
168
|
+
changeHandler,
|
|
164
169
|
genId,
|
|
165
170
|
revisionMetadata,
|
|
166
171
|
);
|
|
167
|
-
return {
|
|
172
|
+
return {
|
|
173
|
+
fieldKind: kind,
|
|
174
|
+
changeHandler,
|
|
175
|
+
change1: normalizedChange1,
|
|
176
|
+
change2: normalizedChange2,
|
|
177
|
+
};
|
|
168
178
|
}
|
|
169
179
|
|
|
170
180
|
private normalizeFieldChange<T>(
|
|
171
|
-
fieldChange: FieldChange
|
|
181
|
+
fieldChange: FieldChange,
|
|
172
182
|
handler: FieldChangeHandler<T>,
|
|
173
183
|
genId: IdAllocator,
|
|
174
184
|
revisionMetadata: RevisionMetadataSource,
|
|
175
|
-
): FieldChangeset
|
|
176
|
-
if (fieldChange === undefined) {
|
|
177
|
-
return undefined;
|
|
178
|
-
}
|
|
179
|
-
|
|
185
|
+
): FieldChangeset {
|
|
180
186
|
if (fieldChange.fieldKind !== genericFieldKind.identifier) {
|
|
181
187
|
return fieldChange.change;
|
|
182
188
|
}
|
|
@@ -208,7 +214,13 @@ export class ModularChangeFamily
|
|
|
208
214
|
return changes.reduce(
|
|
209
215
|
(change1, change2) =>
|
|
210
216
|
makeAnonChange(this.composePair(change1, change2, revInfos, idState)),
|
|
211
|
-
makeAnonChange({
|
|
217
|
+
makeAnonChange({
|
|
218
|
+
fieldChanges: new Map(),
|
|
219
|
+
nodeChanges: new Map(),
|
|
220
|
+
nodeToParent: new Map(),
|
|
221
|
+
nodeAliases: new Map(),
|
|
222
|
+
crossFieldKeys: newCrossFieldKeyTable(),
|
|
223
|
+
}),
|
|
212
224
|
).change;
|
|
213
225
|
}
|
|
214
226
|
|
|
@@ -218,106 +230,283 @@ export class ModularChangeFamily
|
|
|
218
230
|
revInfos: RevisionInfo[],
|
|
219
231
|
idState: IdAllocationState,
|
|
220
232
|
): ModularChangeset {
|
|
233
|
+
const { fieldChanges, nodeChanges, nodeToParent, nodeAliases, crossFieldKeys } =
|
|
234
|
+
this.composeAllFields(change1.change, change2.change, revInfos, idState);
|
|
235
|
+
|
|
236
|
+
const { allBuilds, allDestroys, allRefreshers } = composeBuildsDestroysAndRefreshers([
|
|
237
|
+
change1,
|
|
238
|
+
change2,
|
|
239
|
+
]);
|
|
240
|
+
|
|
241
|
+
return makeModularChangeset(
|
|
242
|
+
this.pruneFieldMap(fieldChanges, nodeChanges),
|
|
243
|
+
nodeChanges,
|
|
244
|
+
nodeToParent,
|
|
245
|
+
nodeAliases,
|
|
246
|
+
crossFieldKeys,
|
|
247
|
+
idState.maxId,
|
|
248
|
+
revInfos,
|
|
249
|
+
undefined,
|
|
250
|
+
allBuilds,
|
|
251
|
+
allDestroys,
|
|
252
|
+
allRefreshers,
|
|
253
|
+
);
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
private composeAllFields(
|
|
257
|
+
change1: ModularChangeset,
|
|
258
|
+
change2: ModularChangeset,
|
|
259
|
+
revInfos: RevisionInfo[],
|
|
260
|
+
idState: IdAllocationState,
|
|
261
|
+
): ModularChangesetContent {
|
|
262
|
+
if (hasConflicts(change1) && hasConflicts(change2)) {
|
|
263
|
+
return {
|
|
264
|
+
fieldChanges: new Map(),
|
|
265
|
+
nodeChanges: new Map(),
|
|
266
|
+
nodeToParent: new Map(),
|
|
267
|
+
nodeAliases: new Map(),
|
|
268
|
+
crossFieldKeys: newBTree(),
|
|
269
|
+
};
|
|
270
|
+
} else if (hasConflicts(change1)) {
|
|
271
|
+
return change2;
|
|
272
|
+
} else if (hasConflicts(change2)) {
|
|
273
|
+
return change1;
|
|
274
|
+
}
|
|
275
|
+
|
|
221
276
|
const genId: IdAllocator = idAllocatorFromState(idState);
|
|
222
277
|
const revisionMetadata: RevisionMetadataSource = revisionMetadataSourceFromInfo(revInfos);
|
|
223
278
|
|
|
224
|
-
const crossFieldTable = newComposeTable();
|
|
279
|
+
const crossFieldTable = newComposeTable(change1, change2);
|
|
280
|
+
|
|
281
|
+
// We merge nodeChanges, nodeToParent, and nodeAliases from the two changesets.
|
|
282
|
+
// The merged tables will have correct entries for all nodes which are only referenced in one of the input changesets.
|
|
283
|
+
// During composeFieldMaps and processInvalidatedElements we will find all nodes referenced in both input changesets
|
|
284
|
+
// and adjust these tables as necessary.
|
|
285
|
+
// Note that when merging these tables we may encounter key collisions and will arbitrarily drop values in that case.
|
|
286
|
+
// A collision for a node ID means that that node is referenced in both changesets
|
|
287
|
+
// (since we assume that if two changesets use the same node ID they are referring to the same node),
|
|
288
|
+
// therefore all collisions will be addressed when processing the intersection of the changesets.
|
|
289
|
+
const composedNodeChanges: ChangeAtomIdMap<NodeChangeset> = mergeNestedMaps(
|
|
290
|
+
change1.nodeChanges,
|
|
291
|
+
change2.nodeChanges,
|
|
292
|
+
);
|
|
293
|
+
|
|
294
|
+
const composedNodeToParent = mergeNestedMaps(change1.nodeToParent, change2.nodeToParent);
|
|
295
|
+
const composedNodeAliases: ChangeAtomIdMap<NodeId> = mergeNestedMaps(
|
|
296
|
+
change1.nodeAliases,
|
|
297
|
+
change2.nodeAliases,
|
|
298
|
+
);
|
|
225
299
|
|
|
226
300
|
const composedFields = this.composeFieldMaps(
|
|
227
|
-
|
|
228
|
-
|
|
301
|
+
change1.fieldChanges,
|
|
302
|
+
change2.fieldChanges,
|
|
229
303
|
genId,
|
|
230
304
|
crossFieldTable,
|
|
231
305
|
revisionMetadata,
|
|
232
306
|
);
|
|
233
307
|
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
308
|
+
this.processInvalidatedElements(
|
|
309
|
+
crossFieldTable,
|
|
310
|
+
composedFields,
|
|
311
|
+
composedNodeChanges,
|
|
312
|
+
composedNodeToParent,
|
|
313
|
+
composedNodeAliases,
|
|
314
|
+
genId,
|
|
315
|
+
revisionMetadata,
|
|
316
|
+
);
|
|
317
|
+
|
|
318
|
+
// Currently no field kinds require making changes to cross-field keys during composition, so we can just merge the two tables.
|
|
319
|
+
const composedCrossFieldKeys = mergeBTrees(change1.crossFieldKeys, change2.crossFieldKeys);
|
|
320
|
+
return {
|
|
321
|
+
fieldChanges: composedFields,
|
|
322
|
+
nodeChanges: composedNodeChanges,
|
|
323
|
+
nodeToParent: composedNodeToParent,
|
|
324
|
+
nodeAliases: composedNodeAliases,
|
|
325
|
+
crossFieldKeys: brand(composedCrossFieldKeys),
|
|
326
|
+
};
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
private composeInvalidatedField(
|
|
330
|
+
fieldChange: FieldChange,
|
|
331
|
+
crossFieldTable: ComposeTable,
|
|
332
|
+
genId: IdAllocator,
|
|
333
|
+
revisionMetadata: RevisionMetadataSource,
|
|
334
|
+
): void {
|
|
335
|
+
const context = crossFieldTable.fieldToContext.get(fieldChange);
|
|
336
|
+
assert(context !== undefined, 0x8cc /* Should have context for every invalidated field */);
|
|
337
|
+
const { change1: fieldChange1, change2: fieldChange2, composedChange } = context;
|
|
338
|
+
|
|
339
|
+
const rebaser = getChangeHandler(this.fieldKinds, composedChange.fieldKind).rebaser;
|
|
340
|
+
const composeNodes = (child1: NodeId | undefined, child2: NodeId | undefined): NodeId => {
|
|
341
|
+
if (
|
|
342
|
+
child1 !== undefined &&
|
|
343
|
+
child2 !== undefined &&
|
|
344
|
+
getFromChangeAtomIdMap(crossFieldTable.newToBaseNodeId, child2) === undefined
|
|
345
|
+
) {
|
|
346
|
+
setInChangeAtomIdMap(crossFieldTable.newToBaseNodeId, child2, child1);
|
|
347
|
+
crossFieldTable.pendingCompositions.nodeIdsToCompose.push([child1, child2]);
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
return child1 ?? child2 ?? fail("Should not compose two undefined nodes");
|
|
351
|
+
};
|
|
352
|
+
|
|
353
|
+
const amendedChange = rebaser.compose(
|
|
354
|
+
fieldChange1,
|
|
355
|
+
fieldChange2,
|
|
356
|
+
composeNodes,
|
|
357
|
+
genId,
|
|
358
|
+
new ComposeManager(crossFieldTable, fieldChange, false),
|
|
359
|
+
revisionMetadata,
|
|
360
|
+
);
|
|
361
|
+
composedChange.change = brand(amendedChange);
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
/**
|
|
365
|
+
* Updates everything in the composed output which may no longer be valid.
|
|
366
|
+
* This could be due to
|
|
367
|
+
* - discovering that two node changesets refer to the same node (`nodeIdsToCompose`)
|
|
368
|
+
* - a previously composed field being invalidated by a cross field effect (`invalidatedFields`)
|
|
369
|
+
* - a field which was copied directly from an input changeset being invalidated by a cross field effect
|
|
370
|
+
* (`affectedBaseFields` and `affectedNewFields`)
|
|
371
|
+
*
|
|
372
|
+
* Updating an element may invalidate further elements. This function runs until there is no more invalidation.
|
|
373
|
+
*/
|
|
374
|
+
private processInvalidatedElements(
|
|
375
|
+
table: ComposeTable,
|
|
376
|
+
composedFields: FieldChangeMap,
|
|
377
|
+
composedNodes: ChangeAtomIdMap<NodeChangeset>,
|
|
378
|
+
composedNodeToParent: ChangeAtomIdMap<FieldId>,
|
|
379
|
+
nodeAliases: ChangeAtomIdMap<NodeId>,
|
|
380
|
+
genId: IdAllocator,
|
|
381
|
+
metadata: RevisionMetadataSource,
|
|
382
|
+
): void {
|
|
383
|
+
const pending = table.pendingCompositions;
|
|
384
|
+
while (
|
|
385
|
+
table.invalidatedFields.size > 0 ||
|
|
386
|
+
pending.nodeIdsToCompose.length > 0 ||
|
|
387
|
+
pending.affectedBaseFields.length > 0 ||
|
|
388
|
+
pending.affectedNewFields.length > 0
|
|
389
|
+
) {
|
|
390
|
+
// Note that the call to `composeNodesById` can add entries to `crossFieldTable.nodeIdPairs`.
|
|
391
|
+
for (const [id1, id2] of pending.nodeIdsToCompose) {
|
|
392
|
+
this.composeNodesById(
|
|
393
|
+
table.baseChange.nodeChanges,
|
|
394
|
+
table.newChange.nodeChanges,
|
|
395
|
+
composedNodes,
|
|
396
|
+
composedNodeToParent,
|
|
397
|
+
nodeAliases,
|
|
398
|
+
id1,
|
|
399
|
+
id2,
|
|
400
|
+
genId,
|
|
401
|
+
table,
|
|
402
|
+
metadata,
|
|
403
|
+
);
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
pending.nodeIdsToCompose.length = 0;
|
|
407
|
+
|
|
408
|
+
this.composeAffectedFields(
|
|
409
|
+
table,
|
|
410
|
+
table.baseChange,
|
|
411
|
+
true,
|
|
412
|
+
pending.affectedBaseFields,
|
|
413
|
+
composedFields,
|
|
414
|
+
composedNodes,
|
|
242
415
|
genId,
|
|
243
|
-
|
|
244
|
-
|
|
416
|
+
metadata,
|
|
417
|
+
);
|
|
418
|
+
|
|
419
|
+
this.composeAffectedFields(
|
|
420
|
+
table,
|
|
421
|
+
table.newChange,
|
|
422
|
+
false,
|
|
423
|
+
pending.affectedNewFields,
|
|
424
|
+
composedFields,
|
|
425
|
+
composedNodes,
|
|
426
|
+
genId,
|
|
427
|
+
metadata,
|
|
245
428
|
);
|
|
429
|
+
|
|
430
|
+
this.processInvalidatedCompositions(table, genId, metadata);
|
|
246
431
|
}
|
|
432
|
+
}
|
|
247
433
|
|
|
248
|
-
|
|
434
|
+
private processInvalidatedCompositions(
|
|
435
|
+
table: ComposeTable,
|
|
436
|
+
genId: IdAllocator,
|
|
437
|
+
metadata: RevisionMetadataSource,
|
|
438
|
+
): void {
|
|
439
|
+
const fieldsToUpdate = table.invalidatedFields;
|
|
440
|
+
table.invalidatedFields = new Set();
|
|
441
|
+
for (const fieldChange of fieldsToUpdate) {
|
|
442
|
+
this.composeInvalidatedField(fieldChange, table, genId, metadata);
|
|
443
|
+
}
|
|
444
|
+
}
|
|
249
445
|
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
446
|
+
/**
|
|
447
|
+
* Ensures that each field in `affectedFields` has been updated in the composition output.
|
|
448
|
+
* Any field which has already been composed is ignored.
|
|
449
|
+
* All other fields are optimistically assumed to not have any changes in the other input changeset.
|
|
450
|
+
*
|
|
451
|
+
* @param change - The changeset which contains the affected fields.
|
|
452
|
+
* This should be one of the two changesets being composed.
|
|
453
|
+
* @param areBaseFields - Whether the affected fields are part of the base changeset.
|
|
454
|
+
* If not, they are assumed to be part of the new changeset.
|
|
455
|
+
* @param affectedFields - The set of fields to process.
|
|
456
|
+
*/
|
|
457
|
+
private composeAffectedFields(
|
|
458
|
+
table: ComposeTable,
|
|
459
|
+
change: ModularChangeset,
|
|
460
|
+
areBaseFields: boolean,
|
|
461
|
+
affectedFields: BTree<FieldIdKey, true>,
|
|
462
|
+
composedFields: FieldChangeMap,
|
|
463
|
+
composedNodes: ChangeAtomIdMap<NodeChangeset>,
|
|
464
|
+
genId: IdAllocator,
|
|
465
|
+
metadata: RevisionMetadataSource,
|
|
466
|
+
): void {
|
|
467
|
+
for (const fieldIdKey of affectedFields.keys()) {
|
|
468
|
+
const fieldId = normalizeFieldId(fieldIdFromFieldIdKey(fieldIdKey), change.nodeAliases);
|
|
469
|
+
const fieldChange = fieldChangeFromId(change.fieldChanges, change.nodeChanges, fieldId);
|
|
470
|
+
|
|
471
|
+
if (
|
|
472
|
+
table.fieldToContext.has(fieldChange) ||
|
|
473
|
+
table.newFieldToBaseField.has(fieldChange)
|
|
474
|
+
) {
|
|
475
|
+
// This function handles fields which were not part of the intersection of the two changesets but which need to be updated anyway.
|
|
476
|
+
// If we've already processed this field then either it is up to date
|
|
477
|
+
// or there is pending inval which will be handled in processInvalidatedCompositions.
|
|
478
|
+
continue;
|
|
479
|
+
}
|
|
277
480
|
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
genId,
|
|
283
|
-
newCrossFieldManager(crossFieldTable, fieldChange, false),
|
|
284
|
-
revisionMetadata,
|
|
285
|
-
);
|
|
286
|
-
composedChange.change = brand(amendedChange);
|
|
287
|
-
|
|
288
|
-
// Process any newly discovered nodes.
|
|
289
|
-
for (const [taggedId1, taggedId2] of crossFieldTable.nodeIdPairs) {
|
|
290
|
-
this.composeNodesById(
|
|
291
|
-
change1.change.nodeChanges,
|
|
292
|
-
change2.change.nodeChanges,
|
|
293
|
-
composedNodeChanges,
|
|
294
|
-
taggedId1,
|
|
295
|
-
taggedId2,
|
|
296
|
-
genId,
|
|
297
|
-
crossFieldTable,
|
|
298
|
-
revisionMetadata,
|
|
299
|
-
);
|
|
300
|
-
}
|
|
481
|
+
const emptyChange = this.createEmptyFieldChange(fieldChange.fieldKind);
|
|
482
|
+
const [change1, change2] = areBaseFields
|
|
483
|
+
? [fieldChange, emptyChange]
|
|
484
|
+
: [emptyChange, fieldChange];
|
|
301
485
|
|
|
302
|
-
|
|
486
|
+
const composedField = this.composeFieldChanges(change1, change2, genId, table, metadata);
|
|
487
|
+
|
|
488
|
+
if (fieldId.nodeId === undefined) {
|
|
489
|
+
composedFields.set(fieldId.field, composedField);
|
|
490
|
+
continue;
|
|
303
491
|
}
|
|
304
|
-
}
|
|
305
492
|
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
change2,
|
|
309
|
-
]);
|
|
493
|
+
const nodeId =
|
|
494
|
+
getFromChangeAtomIdMap(table.newToBaseNodeId, fieldId.nodeId) ?? fieldId.nodeId;
|
|
310
495
|
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
496
|
+
let nodeChangeset = nodeChangeFromId(composedNodes, nodeId);
|
|
497
|
+
if (!table.composedNodes.has(nodeChangeset)) {
|
|
498
|
+
nodeChangeset = cloneNodeChangeset(nodeChangeset);
|
|
499
|
+
setInChangeAtomIdMap(composedNodes, nodeId, nodeChangeset);
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
if (nodeChangeset.fieldChanges === undefined) {
|
|
503
|
+
nodeChangeset.fieldChanges = new Map();
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
nodeChangeset.fieldChanges.set(fieldId.field, composedField);
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
affectedFields.clear();
|
|
321
510
|
}
|
|
322
511
|
|
|
323
512
|
private composeFieldMaps(
|
|
@@ -328,89 +517,106 @@ export class ModularChangeFamily
|
|
|
328
517
|
revisionMetadata: RevisionMetadataSource,
|
|
329
518
|
): FieldChangeMap {
|
|
330
519
|
const composedFields: FieldChangeMap = new Map();
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
fields.add(field);
|
|
520
|
+
if (change1 === undefined || change2 === undefined) {
|
|
521
|
+
return change1 ?? change2 ?? composedFields;
|
|
334
522
|
}
|
|
335
523
|
|
|
336
|
-
for (const field of
|
|
337
|
-
|
|
338
|
-
|
|
524
|
+
for (const [field, fieldChange1] of change1) {
|
|
525
|
+
const fieldChange2 = change2.get(field);
|
|
526
|
+
const composedField =
|
|
527
|
+
fieldChange2 !== undefined
|
|
528
|
+
? this.composeFieldChanges(
|
|
529
|
+
fieldChange1,
|
|
530
|
+
fieldChange2,
|
|
531
|
+
genId,
|
|
532
|
+
crossFieldTable,
|
|
533
|
+
revisionMetadata,
|
|
534
|
+
)
|
|
535
|
+
: fieldChange1;
|
|
339
536
|
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
const fieldChange2 = change2?.get(field);
|
|
537
|
+
composedFields.set(field, composedField);
|
|
538
|
+
}
|
|
343
539
|
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
const manager = newCrossFieldManager(crossFieldTable, fieldChange1 ?? fieldChange2);
|
|
351
|
-
const change1Normalized =
|
|
352
|
-
normalizedFieldChange1 ?? fieldKind.changeHandler.createEmpty();
|
|
353
|
-
const change2Normalized =
|
|
354
|
-
normalizedFieldChange2 ?? fieldKind.changeHandler.createEmpty();
|
|
355
|
-
|
|
356
|
-
const composedChange = fieldKind.changeHandler.rebaser.compose(
|
|
357
|
-
change1Normalized,
|
|
358
|
-
change2Normalized,
|
|
359
|
-
(child1, child2) => {
|
|
360
|
-
crossFieldTable.nodeIdPairs.push([child1, child2]);
|
|
361
|
-
if (child2 !== undefined) {
|
|
362
|
-
addToNestedSet(crossFieldTable.nodeIds, child2.revision, child2.localId);
|
|
363
|
-
}
|
|
364
|
-
return child1 ?? child2 ?? fail("Should not compose two undefined nodes");
|
|
365
|
-
},
|
|
366
|
-
genId,
|
|
367
|
-
manager,
|
|
368
|
-
revisionMetadata,
|
|
369
|
-
);
|
|
540
|
+
for (const [field, fieldChange2] of change2) {
|
|
541
|
+
if (change1 === undefined || !change1.has(field)) {
|
|
542
|
+
composedFields.set(field, fieldChange2);
|
|
543
|
+
}
|
|
544
|
+
}
|
|
370
545
|
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
change: brand(composedChange),
|
|
374
|
-
};
|
|
546
|
+
return composedFields;
|
|
547
|
+
}
|
|
375
548
|
|
|
376
|
-
|
|
377
|
-
|
|
549
|
+
/**
|
|
550
|
+
* Returns the composition of the two input fields.
|
|
551
|
+
*
|
|
552
|
+
* Any nodes in this field which were modified by both changesets
|
|
553
|
+
* will be added to `crossFieldTable.pendingCompositions.nodeIdsToCompose`.
|
|
554
|
+
*
|
|
555
|
+
* Any fields which had cross-field information sent to them as part of this field composition
|
|
556
|
+
* will be added to either `affectedBaseFields` or `affectedNewFields` in `crossFieldTable.pendingCompositions`.
|
|
557
|
+
*
|
|
558
|
+
* Any composed `FieldChange` which is invalidated by new cross-field information will be added to `crossFieldTable.invalidatedFields`.
|
|
559
|
+
*/
|
|
560
|
+
private composeFieldChanges(
|
|
561
|
+
change1: FieldChange,
|
|
562
|
+
change2: FieldChange,
|
|
563
|
+
idAllocator: IdAllocator,
|
|
564
|
+
crossFieldTable: ComposeTable,
|
|
565
|
+
revisionMetadata: RevisionMetadataSource,
|
|
566
|
+
): FieldChange {
|
|
567
|
+
const {
|
|
568
|
+
fieldKind,
|
|
569
|
+
changeHandler,
|
|
570
|
+
change1: change1Normalized,
|
|
571
|
+
change2: change2Normalized,
|
|
572
|
+
} = this.normalizeFieldChanges(change1, change2, idAllocator, revisionMetadata);
|
|
573
|
+
|
|
574
|
+
const manager = new ComposeManager(crossFieldTable, change1);
|
|
575
|
+
|
|
576
|
+
const composedChange = changeHandler.rebaser.compose(
|
|
577
|
+
change1Normalized,
|
|
578
|
+
change2Normalized,
|
|
579
|
+
(child1, child2) => {
|
|
580
|
+
if (child1 !== undefined && child2 !== undefined) {
|
|
581
|
+
setInChangeAtomIdMap(crossFieldTable.newToBaseNodeId, child2, child1);
|
|
582
|
+
crossFieldTable.pendingCompositions.nodeIdsToCompose.push([child1, child2]);
|
|
583
|
+
}
|
|
584
|
+
return child1 ?? child2 ?? fail("Should not compose two undefined nodes");
|
|
585
|
+
},
|
|
586
|
+
idAllocator,
|
|
587
|
+
manager,
|
|
588
|
+
revisionMetadata,
|
|
589
|
+
);
|
|
378
590
|
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
});
|
|
591
|
+
const composedField: FieldChange = {
|
|
592
|
+
fieldKind,
|
|
593
|
+
change: brand(composedChange),
|
|
594
|
+
};
|
|
384
595
|
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
596
|
+
crossFieldTable.fieldToContext.set(change1, {
|
|
597
|
+
change1: change1Normalized,
|
|
598
|
+
change2: change2Normalized,
|
|
599
|
+
composedChange: composedField,
|
|
600
|
+
});
|
|
388
601
|
|
|
389
|
-
|
|
602
|
+
crossFieldTable.newFieldToBaseField.set(change2, change1);
|
|
603
|
+
return composedField;
|
|
390
604
|
}
|
|
391
605
|
|
|
392
606
|
private composeNodesById(
|
|
393
607
|
nodeChanges1: ChangeAtomIdMap<NodeChangeset>,
|
|
394
608
|
nodeChanges2: ChangeAtomIdMap<NodeChangeset>,
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
609
|
+
composedNodes: ChangeAtomIdMap<NodeChangeset>,
|
|
610
|
+
composedNodeToParent: ChangeAtomIdMap<FieldId>,
|
|
611
|
+
nodeAliases: ChangeAtomIdMap<NodeId>,
|
|
612
|
+
id1: NodeId,
|
|
613
|
+
id2: NodeId,
|
|
398
614
|
idAllocator: IdAllocator,
|
|
399
615
|
crossFieldTable: ComposeTable,
|
|
400
616
|
revisionMetadata: RevisionMetadataSource,
|
|
401
617
|
): void {
|
|
402
|
-
const nodeChangeset1 =
|
|
403
|
-
|
|
404
|
-
? tryGetFromNestedMap(nodeChanges1, id1.revision, id1.localId) ??
|
|
405
|
-
fail("Unknown node ID")
|
|
406
|
-
: {};
|
|
407
|
-
|
|
408
|
-
const nodeChangeset2 =
|
|
409
|
-
id2 !== undefined
|
|
410
|
-
? tryGetFromNestedMap(nodeChanges2, id2.revision, id2.localId) ??
|
|
411
|
-
fail("Unknown node ID")
|
|
412
|
-
: {};
|
|
413
|
-
|
|
618
|
+
const nodeChangeset1 = nodeChangeFromId(nodeChanges1, id1);
|
|
619
|
+
const nodeChangeset2 = nodeChangeFromId(nodeChanges2, id2);
|
|
414
620
|
const composedNodeChangeset = this.composeNodeChanges(
|
|
415
621
|
nodeChangeset1,
|
|
416
622
|
nodeChangeset2,
|
|
@@ -419,32 +625,37 @@ export class ModularChangeFamily
|
|
|
419
625
|
revisionMetadata,
|
|
420
626
|
);
|
|
421
627
|
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
628
|
+
setInChangeAtomIdMap(composedNodes, id1, composedNodeChangeset);
|
|
629
|
+
|
|
630
|
+
if (!areEqualChangeAtomIds(id1, id2)) {
|
|
631
|
+
deleteFromNestedMap(composedNodes, id2.revision, id2.localId);
|
|
632
|
+
deleteFromNestedMap(composedNodeToParent, id2.revision, id2.localId);
|
|
633
|
+
setInChangeAtomIdMap(nodeAliases, id2, id1);
|
|
634
|
+
|
|
635
|
+
// We need to delete id1 to avoid forming a cycle in case id1 already had an alias.
|
|
636
|
+
deleteFromNestedMap(nodeAliases, id1.revision, id1.localId);
|
|
637
|
+
}
|
|
638
|
+
|
|
639
|
+
crossFieldTable.composedNodes.add(composedNodeChangeset);
|
|
429
640
|
}
|
|
430
641
|
|
|
431
642
|
private composeNodeChanges(
|
|
432
|
-
change1: NodeChangeset
|
|
433
|
-
change2: NodeChangeset
|
|
643
|
+
change1: NodeChangeset,
|
|
644
|
+
change2: NodeChangeset,
|
|
434
645
|
genId: IdAllocator,
|
|
435
646
|
crossFieldTable: ComposeTable,
|
|
436
647
|
revisionMetadata: RevisionMetadataSource,
|
|
437
648
|
): NodeChangeset {
|
|
438
|
-
const nodeExistsConstraint =
|
|
439
|
-
change1?.nodeExistsConstraint ?? change2?.nodeExistsConstraint;
|
|
649
|
+
const nodeExistsConstraint = change1.nodeExistsConstraint ?? change2.nodeExistsConstraint;
|
|
440
650
|
|
|
441
651
|
const composedFieldChanges = this.composeFieldMaps(
|
|
442
|
-
change1
|
|
443
|
-
change2
|
|
652
|
+
change1.fieldChanges,
|
|
653
|
+
change2.fieldChanges,
|
|
444
654
|
genId,
|
|
445
655
|
crossFieldTable,
|
|
446
656
|
revisionMetadata,
|
|
447
657
|
);
|
|
658
|
+
|
|
448
659
|
const composedNodeChange: NodeChangeset = {};
|
|
449
660
|
|
|
450
661
|
if (composedFieldChanges.size > 0) {
|
|
@@ -480,6 +691,9 @@ export class ModularChangeFamily
|
|
|
480
691
|
|
|
481
692
|
if ((change.change.constraintViolationCount ?? 0) > 0) {
|
|
482
693
|
return makeModularChangeset(
|
|
694
|
+
undefined,
|
|
695
|
+
undefined,
|
|
696
|
+
undefined,
|
|
483
697
|
undefined,
|
|
484
698
|
undefined,
|
|
485
699
|
change.change.maxId,
|
|
@@ -490,14 +704,13 @@ export class ModularChangeFamily
|
|
|
490
704
|
);
|
|
491
705
|
}
|
|
492
706
|
|
|
493
|
-
const
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
// TODO: add a getMax function to IdAllocator to make for a clearer contract.
|
|
497
|
-
const genId: IdAllocator = idAllocatorFromState(idState);
|
|
707
|
+
const genId: IdAllocator = idAllocatorFromMaxId(change.change.maxId ?? -1);
|
|
708
|
+
const invertedNodeToParent = cloneNestedMap(change.change.nodeToParent);
|
|
709
|
+
|
|
498
710
|
const crossFieldTable: InvertTable = {
|
|
499
711
|
...newCrossFieldTable<FieldChange>(),
|
|
500
712
|
originalFieldToContext: new Map(),
|
|
713
|
+
invertedNodeToParent,
|
|
501
714
|
};
|
|
502
715
|
|
|
503
716
|
const { revInfos } = getRevInfoFromTaggedChanges([change]);
|
|
@@ -505,6 +718,7 @@ export class ModularChangeFamily
|
|
|
505
718
|
|
|
506
719
|
const invertedFields = this.invertFieldMap(
|
|
507
720
|
change.change.fieldChanges,
|
|
721
|
+
undefined,
|
|
508
722
|
isRollback,
|
|
509
723
|
genId,
|
|
510
724
|
crossFieldTable,
|
|
@@ -519,6 +733,7 @@ export class ModularChangeFamily
|
|
|
519
733
|
localId,
|
|
520
734
|
this.invertNodeChange(
|
|
521
735
|
nodeChangeset,
|
|
736
|
+
{ revision, localId },
|
|
522
737
|
isRollback,
|
|
523
738
|
genId,
|
|
524
739
|
crossFieldTable,
|
|
@@ -537,7 +752,7 @@ export class ModularChangeFamily
|
|
|
537
752
|
context !== undefined,
|
|
538
753
|
0x851 /* Should have context for every invalidated field */,
|
|
539
754
|
);
|
|
540
|
-
const { invertedField } = context;
|
|
755
|
+
const { invertedField, fieldId } = context;
|
|
541
756
|
|
|
542
757
|
const amendedChange = getChangeHandler(
|
|
543
758
|
this.fieldKinds,
|
|
@@ -546,17 +761,22 @@ export class ModularChangeFamily
|
|
|
546
761
|
originalFieldChange,
|
|
547
762
|
isRollback,
|
|
548
763
|
genId,
|
|
549
|
-
|
|
764
|
+
new InvertManager(crossFieldTable, fieldChange, fieldId),
|
|
550
765
|
revisionMetadata,
|
|
551
766
|
);
|
|
552
767
|
invertedField.change = brand(amendedChange);
|
|
553
768
|
}
|
|
554
769
|
}
|
|
555
770
|
|
|
771
|
+
const crossFieldKeys = this.makeCrossFieldKeyTable(invertedFields, invertedNodes);
|
|
772
|
+
|
|
556
773
|
return makeModularChangeset(
|
|
557
774
|
invertedFields,
|
|
558
775
|
invertedNodes,
|
|
559
|
-
|
|
776
|
+
invertedNodeToParent,
|
|
777
|
+
change.change.nodeAliases,
|
|
778
|
+
crossFieldKeys,
|
|
779
|
+
genId.getMaxId(),
|
|
560
780
|
[],
|
|
561
781
|
change.change.constraintViolationCount,
|
|
562
782
|
undefined,
|
|
@@ -566,6 +786,7 @@ export class ModularChangeFamily
|
|
|
566
786
|
|
|
567
787
|
private invertFieldMap(
|
|
568
788
|
changes: FieldChangeMap,
|
|
789
|
+
parentId: NodeId | undefined,
|
|
569
790
|
isRollback: boolean,
|
|
570
791
|
genId: IdAllocator,
|
|
571
792
|
crossFieldTable: InvertTable,
|
|
@@ -574,7 +795,8 @@ export class ModularChangeFamily
|
|
|
574
795
|
const invertedFields: FieldChangeMap = new Map();
|
|
575
796
|
|
|
576
797
|
for (const [field, fieldChange] of changes) {
|
|
577
|
-
const
|
|
798
|
+
const fieldId = { nodeId: parentId, field };
|
|
799
|
+
const manager = new InvertManager(crossFieldTable, fieldChange, fieldId);
|
|
578
800
|
const invertedChange = getChangeHandler(
|
|
579
801
|
this.fieldKinds,
|
|
580
802
|
fieldChange.fieldKind,
|
|
@@ -587,6 +809,7 @@ export class ModularChangeFamily
|
|
|
587
809
|
invertedFields.set(field, invertedFieldChange);
|
|
588
810
|
|
|
589
811
|
crossFieldTable.originalFieldToContext.set(fieldChange, {
|
|
812
|
+
fieldId,
|
|
590
813
|
invertedField: invertedFieldChange,
|
|
591
814
|
});
|
|
592
815
|
}
|
|
@@ -596,6 +819,7 @@ export class ModularChangeFamily
|
|
|
596
819
|
|
|
597
820
|
private invertNodeChange(
|
|
598
821
|
change: NodeChangeset,
|
|
822
|
+
id: NodeId,
|
|
599
823
|
isRollback: boolean,
|
|
600
824
|
genId: IdAllocator,
|
|
601
825
|
crossFieldTable: InvertTable,
|
|
@@ -606,6 +830,7 @@ export class ModularChangeFamily
|
|
|
606
830
|
if (change.fieldChanges !== undefined) {
|
|
607
831
|
inverse.fieldChanges = this.invertFieldMap(
|
|
608
832
|
change.fieldChanges,
|
|
833
|
+
id,
|
|
609
834
|
isRollback,
|
|
610
835
|
genId,
|
|
611
836
|
crossFieldTable,
|
|
@@ -625,11 +850,18 @@ export class ModularChangeFamily
|
|
|
625
850
|
const maxId = Math.max(change.maxId ?? -1, over.change.maxId ?? -1);
|
|
626
851
|
const idState: IdAllocationState = { maxId };
|
|
627
852
|
const genId: IdAllocator = idAllocatorFromState(idState);
|
|
853
|
+
|
|
628
854
|
const crossFieldTable: RebaseTable = {
|
|
629
855
|
...newCrossFieldTable<FieldChange>(),
|
|
630
|
-
|
|
631
|
-
|
|
856
|
+
newChange: change,
|
|
857
|
+
baseChange: over.change,
|
|
858
|
+
baseFieldToContext: new Map(),
|
|
859
|
+
baseToRebasedNodeId: new Map(),
|
|
860
|
+
rebasedFields: new Set(),
|
|
861
|
+
rebasedNodeToParent: cloneNestedMap(change.nodeToParent),
|
|
862
|
+
rebasedCrossFieldKeys: brand(change.crossFieldKeys.clone()),
|
|
632
863
|
nodeIdPairs: [],
|
|
864
|
+
affectedBaseFields: newBTree(),
|
|
633
865
|
};
|
|
634
866
|
|
|
635
867
|
let constraintState = newConstraintState(change.constraintViolationCount ?? 0);
|
|
@@ -643,51 +875,33 @@ export class ModularChangeFamily
|
|
|
643
875
|
getBaseRevisions,
|
|
644
876
|
};
|
|
645
877
|
|
|
646
|
-
const
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
genId,
|
|
878
|
+
const rebasedNodes: ChangeAtomIdMap<NodeChangeset> = cloneNestedMap(change.nodeChanges);
|
|
879
|
+
|
|
880
|
+
const rebasedFields = this.rebaseIntersectingFields(
|
|
650
881
|
crossFieldTable,
|
|
882
|
+
rebasedNodes,
|
|
883
|
+
genId,
|
|
884
|
+
constraintState,
|
|
651
885
|
rebaseMetadata,
|
|
652
886
|
);
|
|
653
887
|
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
const baseNodeChange =
|
|
662
|
-
baseId !== undefined
|
|
663
|
-
? tryGetFromNestedMap(over.change.nodeChanges, baseId.revision, baseId.localId) ??
|
|
664
|
-
fail("Unknown node ID")
|
|
665
|
-
: {};
|
|
666
|
-
|
|
667
|
-
const rebasedNode = this.rebaseNodeChange(
|
|
668
|
-
newNodeChange,
|
|
669
|
-
baseNodeChange,
|
|
670
|
-
genId,
|
|
671
|
-
crossFieldTable,
|
|
672
|
-
rebaseMetadata,
|
|
673
|
-
);
|
|
674
|
-
|
|
675
|
-
if (rebasedNode !== undefined) {
|
|
676
|
-
const nodeId = newId ?? baseId ?? fail("Should not have two undefined IDs");
|
|
677
|
-
setInNestedMap(rebasedNodes, nodeId.revision, nodeId.localId, rebasedNode);
|
|
678
|
-
}
|
|
679
|
-
}
|
|
888
|
+
this.rebaseFieldsWithoutNewChanges(
|
|
889
|
+
rebasedFields,
|
|
890
|
+
rebasedNodes,
|
|
891
|
+
crossFieldTable,
|
|
892
|
+
genId,
|
|
893
|
+
rebaseMetadata,
|
|
894
|
+
);
|
|
680
895
|
|
|
681
896
|
if (crossFieldTable.invalidatedFields.size > 0) {
|
|
682
897
|
const fieldsToUpdate = crossFieldTable.invalidatedFields;
|
|
683
898
|
crossFieldTable.invalidatedFields = new Set();
|
|
684
899
|
constraintState = newConstraintState(change.constraintViolationCount ?? 0);
|
|
685
900
|
for (const field of fieldsToUpdate) {
|
|
686
|
-
|
|
687
|
-
const context = crossFieldTable.fieldToContext.get(field);
|
|
901
|
+
const context = crossFieldTable.baseFieldToContext.get(field);
|
|
688
902
|
assert(context !== undefined, 0x852 /* Every field should have a context */);
|
|
689
903
|
const {
|
|
690
|
-
|
|
904
|
+
changeHandler,
|
|
691
905
|
change1: fieldChangeset,
|
|
692
906
|
change2: baseChangeset,
|
|
693
907
|
} = this.normalizeFieldChanges(
|
|
@@ -697,13 +911,34 @@ export class ModularChangeFamily
|
|
|
697
911
|
revisionMetadata,
|
|
698
912
|
);
|
|
699
913
|
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
914
|
+
const rebaseChild = (
|
|
915
|
+
curr: NodeId | undefined,
|
|
916
|
+
base: NodeId | undefined,
|
|
917
|
+
): NodeId | undefined => {
|
|
918
|
+
if (curr !== undefined) {
|
|
919
|
+
return curr;
|
|
920
|
+
}
|
|
921
|
+
|
|
922
|
+
if (base !== undefined) {
|
|
923
|
+
for (const id of context.baseNodeIds) {
|
|
924
|
+
if (areEqualChangeAtomIds(base, id)) {
|
|
925
|
+
return base;
|
|
926
|
+
}
|
|
927
|
+
}
|
|
928
|
+
}
|
|
929
|
+
|
|
930
|
+
return undefined;
|
|
931
|
+
};
|
|
932
|
+
|
|
933
|
+
context.rebasedChange.change = brand(
|
|
934
|
+
changeHandler.rebaser.rebase(
|
|
935
|
+
fieldChangeset,
|
|
936
|
+
baseChangeset,
|
|
937
|
+
rebaseChild,
|
|
938
|
+
genId,
|
|
939
|
+
new RebaseManager(crossFieldTable, field, context.fieldId),
|
|
940
|
+
rebaseMetadata,
|
|
941
|
+
),
|
|
707
942
|
);
|
|
708
943
|
}
|
|
709
944
|
}
|
|
@@ -718,6 +953,9 @@ export class ModularChangeFamily
|
|
|
718
953
|
return makeModularChangeset(
|
|
719
954
|
this.pruneFieldMap(rebasedFields, rebasedNodes),
|
|
720
955
|
rebasedNodes,
|
|
956
|
+
crossFieldTable.rebasedNodeToParent,
|
|
957
|
+
change.nodeAliases,
|
|
958
|
+
crossFieldTable.rebasedCrossFieldKeys,
|
|
721
959
|
idState.maxId,
|
|
722
960
|
change.revisions,
|
|
723
961
|
constraintState.violationCount,
|
|
@@ -727,47 +965,280 @@ export class ModularChangeFamily
|
|
|
727
965
|
);
|
|
728
966
|
}
|
|
729
967
|
|
|
968
|
+
// This performs a first pass on all fields which have both new and base changes.
|
|
969
|
+
// TODO: Can we also handle additional passes in this method?
|
|
970
|
+
private rebaseIntersectingFields(
|
|
971
|
+
crossFieldTable: RebaseTable,
|
|
972
|
+
rebasedNodes: ChangeAtomIdMap<NodeChangeset>,
|
|
973
|
+
genId: IdAllocator,
|
|
974
|
+
constraintState: ConstraintState,
|
|
975
|
+
metadata: RebaseRevisionMetadata,
|
|
976
|
+
): FieldChangeMap {
|
|
977
|
+
const change = crossFieldTable.newChange;
|
|
978
|
+
const baseChange = crossFieldTable.baseChange;
|
|
979
|
+
const rebasedFields = this.rebaseFieldMap(
|
|
980
|
+
change.fieldChanges,
|
|
981
|
+
baseChange.fieldChanges,
|
|
982
|
+
undefined,
|
|
983
|
+
genId,
|
|
984
|
+
crossFieldTable,
|
|
985
|
+
metadata,
|
|
986
|
+
);
|
|
987
|
+
|
|
988
|
+
// This loop processes all fields which have both base and new changes.
|
|
989
|
+
// Note that the call to `rebaseNodeChange` can add entries to `crossFieldTable.nodeIdPairs`.
|
|
990
|
+
for (const [newId, baseId, _attachState] of crossFieldTable.nodeIdPairs) {
|
|
991
|
+
const rebasedNode = this.rebaseNodeChange(
|
|
992
|
+
newId,
|
|
993
|
+
baseId,
|
|
994
|
+
genId,
|
|
995
|
+
crossFieldTable,
|
|
996
|
+
metadata,
|
|
997
|
+
constraintState,
|
|
998
|
+
);
|
|
999
|
+
|
|
1000
|
+
setInChangeAtomIdMap(rebasedNodes, newId, rebasedNode);
|
|
1001
|
+
}
|
|
1002
|
+
|
|
1003
|
+
return rebasedFields;
|
|
1004
|
+
}
|
|
1005
|
+
|
|
1006
|
+
// This processes fields which have no new changes but have been invalidated by another field.
|
|
1007
|
+
private rebaseFieldsWithoutNewChanges(
|
|
1008
|
+
rebasedFields: FieldChangeMap,
|
|
1009
|
+
rebasedNodes: ChangeAtomIdMap<NodeChangeset>,
|
|
1010
|
+
crossFieldTable: RebaseTable,
|
|
1011
|
+
genId: IdAllocator,
|
|
1012
|
+
metadata: RebaseRevisionMetadata,
|
|
1013
|
+
): void {
|
|
1014
|
+
const baseChange = crossFieldTable.baseChange;
|
|
1015
|
+
for (const [revision, localId, fieldKey] of crossFieldTable.affectedBaseFields.keys()) {
|
|
1016
|
+
const baseNodeId =
|
|
1017
|
+
localId !== undefined
|
|
1018
|
+
? normalizeNodeId({ revision, localId }, baseChange.nodeAliases)
|
|
1019
|
+
: undefined;
|
|
1020
|
+
|
|
1021
|
+
const baseFieldChange = fieldMapFromNodeId(
|
|
1022
|
+
baseChange.fieldChanges,
|
|
1023
|
+
baseChange.nodeChanges,
|
|
1024
|
+
baseNodeId,
|
|
1025
|
+
).get(fieldKey);
|
|
1026
|
+
|
|
1027
|
+
assert(baseFieldChange !== undefined, "Cross field key registered for empty field");
|
|
1028
|
+
if (crossFieldTable.baseFieldToContext.has(baseFieldChange)) {
|
|
1029
|
+
// This field has already been processed because there were changes to rebase.
|
|
1030
|
+
continue;
|
|
1031
|
+
}
|
|
1032
|
+
|
|
1033
|
+
// This field has no changes in the new changeset, otherwise it would have been added to
|
|
1034
|
+
// `crossFieldTable.baseFieldToContext` when processing fields with both base and new changes.
|
|
1035
|
+
const rebaseChild = (
|
|
1036
|
+
child: NodeId | undefined,
|
|
1037
|
+
baseChild: NodeId | undefined,
|
|
1038
|
+
stateChange: NodeAttachState | undefined,
|
|
1039
|
+
): NodeId | undefined => {
|
|
1040
|
+
assert(child === undefined, "There should be no new changes in this field");
|
|
1041
|
+
return undefined;
|
|
1042
|
+
};
|
|
1043
|
+
|
|
1044
|
+
const handler = getChangeHandler(this.fieldKinds, baseFieldChange.fieldKind);
|
|
1045
|
+
const fieldChange: FieldChange = {
|
|
1046
|
+
...baseFieldChange,
|
|
1047
|
+
change: brand(handler.createEmpty()),
|
|
1048
|
+
};
|
|
1049
|
+
|
|
1050
|
+
const rebasedNodeId =
|
|
1051
|
+
baseNodeId !== undefined
|
|
1052
|
+
? rebasedNodeIdFromBaseNodeId(crossFieldTable, baseNodeId)
|
|
1053
|
+
: undefined;
|
|
1054
|
+
|
|
1055
|
+
const fieldId: FieldId = { nodeId: rebasedNodeId, field: fieldKey };
|
|
1056
|
+
const rebasedField: unknown = handler.rebaser.rebase(
|
|
1057
|
+
fieldChange.change,
|
|
1058
|
+
baseFieldChange.change,
|
|
1059
|
+
rebaseChild,
|
|
1060
|
+
genId,
|
|
1061
|
+
new RebaseManager(crossFieldTable, baseFieldChange, fieldId),
|
|
1062
|
+
metadata,
|
|
1063
|
+
);
|
|
1064
|
+
|
|
1065
|
+
const rebasedFieldChange: FieldChange = {
|
|
1066
|
+
...baseFieldChange,
|
|
1067
|
+
change: brand(rebasedField),
|
|
1068
|
+
};
|
|
1069
|
+
|
|
1070
|
+
// TODO: Deduplicate
|
|
1071
|
+
crossFieldTable.baseFieldToContext.set(baseFieldChange, {
|
|
1072
|
+
newChange: fieldChange,
|
|
1073
|
+
baseChange: baseFieldChange,
|
|
1074
|
+
rebasedChange: rebasedFieldChange,
|
|
1075
|
+
fieldId,
|
|
1076
|
+
baseNodeIds: [],
|
|
1077
|
+
});
|
|
1078
|
+
crossFieldTable.rebasedFields.add(rebasedFieldChange);
|
|
1079
|
+
|
|
1080
|
+
this.attachRebasedField(
|
|
1081
|
+
rebasedFields,
|
|
1082
|
+
rebasedNodes,
|
|
1083
|
+
crossFieldTable,
|
|
1084
|
+
rebasedFieldChange,
|
|
1085
|
+
fieldId,
|
|
1086
|
+
genId,
|
|
1087
|
+
metadata,
|
|
1088
|
+
);
|
|
1089
|
+
}
|
|
1090
|
+
}
|
|
1091
|
+
|
|
1092
|
+
private attachRebasedField(
|
|
1093
|
+
rebasedFields: FieldChangeMap,
|
|
1094
|
+
rebasedNodes: ChangeAtomIdMap<NodeChangeset>,
|
|
1095
|
+
table: RebaseTable,
|
|
1096
|
+
rebasedField: FieldChange,
|
|
1097
|
+
{ nodeId, field: fieldKey }: FieldId,
|
|
1098
|
+
idAllocator: IdAllocator,
|
|
1099
|
+
metadata: RebaseRevisionMetadata,
|
|
1100
|
+
): void {
|
|
1101
|
+
if (nodeId === undefined) {
|
|
1102
|
+
rebasedFields.set(fieldKey, rebasedField);
|
|
1103
|
+
return;
|
|
1104
|
+
}
|
|
1105
|
+
const rebasedNode = getFromChangeAtomIdMap(rebasedNodes, nodeId);
|
|
1106
|
+
if (rebasedNode !== undefined) {
|
|
1107
|
+
if (rebasedNode.fieldChanges === undefined) {
|
|
1108
|
+
rebasedNode.fieldChanges = new Map([[fieldKey, rebasedField]]);
|
|
1109
|
+
return;
|
|
1110
|
+
}
|
|
1111
|
+
|
|
1112
|
+
assert(!rebasedNode.fieldChanges.has(fieldKey), "Expected an empty field");
|
|
1113
|
+
rebasedNode.fieldChanges.set(fieldKey, rebasedField);
|
|
1114
|
+
return;
|
|
1115
|
+
}
|
|
1116
|
+
|
|
1117
|
+
const newNode: NodeChangeset = {
|
|
1118
|
+
fieldChanges: new Map([[fieldKey, rebasedField]]),
|
|
1119
|
+
};
|
|
1120
|
+
|
|
1121
|
+
setInChangeAtomIdMap(rebasedNodes, nodeId, newNode);
|
|
1122
|
+
setInChangeAtomIdMap(table.baseToRebasedNodeId, nodeId, nodeId);
|
|
1123
|
+
|
|
1124
|
+
const parentFieldId = getParentFieldId(table.baseChange, nodeId);
|
|
1125
|
+
|
|
1126
|
+
this.attachRebasedNode(
|
|
1127
|
+
rebasedFields,
|
|
1128
|
+
rebasedNodes,
|
|
1129
|
+
table,
|
|
1130
|
+
nodeId,
|
|
1131
|
+
parentFieldId,
|
|
1132
|
+
idAllocator,
|
|
1133
|
+
metadata,
|
|
1134
|
+
);
|
|
1135
|
+
}
|
|
1136
|
+
|
|
1137
|
+
private attachRebasedNode(
|
|
1138
|
+
rebasedFields: FieldChangeMap,
|
|
1139
|
+
rebasedNodes: ChangeAtomIdMap<NodeChangeset>,
|
|
1140
|
+
table: RebaseTable,
|
|
1141
|
+
baseNodeId: NodeId,
|
|
1142
|
+
parentFieldIdBase: FieldId,
|
|
1143
|
+
idAllocator: IdAllocator,
|
|
1144
|
+
metadata: RebaseRevisionMetadata,
|
|
1145
|
+
): void {
|
|
1146
|
+
const baseFieldChange = fieldChangeFromId(
|
|
1147
|
+
table.baseChange.fieldChanges,
|
|
1148
|
+
table.baseChange.nodeChanges,
|
|
1149
|
+
parentFieldIdBase,
|
|
1150
|
+
);
|
|
1151
|
+
|
|
1152
|
+
const rebasedFieldId = rebasedFieldIdFromBaseId(table, parentFieldIdBase);
|
|
1153
|
+
setInChangeAtomIdMap(table.rebasedNodeToParent, baseNodeId, rebasedFieldId);
|
|
1154
|
+
|
|
1155
|
+
const context = table.baseFieldToContext.get(baseFieldChange);
|
|
1156
|
+
if (context !== undefined) {
|
|
1157
|
+
// We've already processed this field.
|
|
1158
|
+
// The new child node can be attached when processing invalidated fields.
|
|
1159
|
+
context.baseNodeIds.push(baseNodeId);
|
|
1160
|
+
table.invalidatedFields.add(baseFieldChange);
|
|
1161
|
+
return;
|
|
1162
|
+
}
|
|
1163
|
+
|
|
1164
|
+
const handler = getChangeHandler(this.fieldKinds, baseFieldChange.fieldKind);
|
|
1165
|
+
|
|
1166
|
+
const fieldChange: FieldChange = {
|
|
1167
|
+
...baseFieldChange,
|
|
1168
|
+
change: brand(handler.createEmpty()),
|
|
1169
|
+
};
|
|
1170
|
+
|
|
1171
|
+
const rebasedChangeset = handler.rebaser.rebase(
|
|
1172
|
+
handler.createEmpty(),
|
|
1173
|
+
baseFieldChange.change,
|
|
1174
|
+
(_idNew, idBase) =>
|
|
1175
|
+
idBase !== undefined && areEqualChangeAtomIds(idBase, baseNodeId)
|
|
1176
|
+
? baseNodeId
|
|
1177
|
+
: undefined,
|
|
1178
|
+
idAllocator,
|
|
1179
|
+
new RebaseManager(table, baseFieldChange, rebasedFieldId),
|
|
1180
|
+
metadata,
|
|
1181
|
+
);
|
|
1182
|
+
|
|
1183
|
+
const rebasedField: FieldChange = { ...baseFieldChange, change: brand(rebasedChangeset) };
|
|
1184
|
+
table.rebasedFields.add(rebasedField);
|
|
1185
|
+
table.baseFieldToContext.set(baseFieldChange, {
|
|
1186
|
+
newChange: fieldChange,
|
|
1187
|
+
baseChange: baseFieldChange,
|
|
1188
|
+
rebasedChange: rebasedField,
|
|
1189
|
+
fieldId: rebasedFieldId,
|
|
1190
|
+
baseNodeIds: [],
|
|
1191
|
+
});
|
|
1192
|
+
|
|
1193
|
+
this.attachRebasedField(
|
|
1194
|
+
rebasedFields,
|
|
1195
|
+
rebasedNodes,
|
|
1196
|
+
table,
|
|
1197
|
+
rebasedField,
|
|
1198
|
+
rebasedFieldId,
|
|
1199
|
+
idAllocator,
|
|
1200
|
+
metadata,
|
|
1201
|
+
);
|
|
1202
|
+
}
|
|
1203
|
+
|
|
730
1204
|
private rebaseFieldMap(
|
|
731
1205
|
change: FieldChangeMap,
|
|
732
1206
|
over: FieldChangeMap,
|
|
1207
|
+
parentId: NodeId | undefined,
|
|
733
1208
|
genId: IdAllocator,
|
|
734
1209
|
crossFieldTable: RebaseTable,
|
|
735
1210
|
revisionMetadata: RebaseRevisionMetadata,
|
|
736
1211
|
): FieldChangeMap {
|
|
737
1212
|
const rebasedFields: FieldChangeMap = new Map();
|
|
1213
|
+
const rebaseChild = (
|
|
1214
|
+
child: NodeId | undefined,
|
|
1215
|
+
baseChild: NodeId | undefined,
|
|
1216
|
+
stateChange: NodeAttachState | undefined,
|
|
1217
|
+
): NodeId | undefined => {
|
|
1218
|
+
if (child !== undefined && baseChild !== undefined) {
|
|
1219
|
+
crossFieldTable.nodeIdPairs.push([child, baseChild, stateChange]);
|
|
1220
|
+
}
|
|
1221
|
+
return child;
|
|
1222
|
+
};
|
|
1223
|
+
|
|
1224
|
+
for (const [field, fieldChange] of change) {
|
|
1225
|
+
const fieldId: FieldId = { nodeId: parentId, field };
|
|
1226
|
+
const baseChange = over.get(field);
|
|
1227
|
+
if (baseChange === undefined) {
|
|
1228
|
+
rebasedFields.set(field, fieldChange);
|
|
1229
|
+
continue;
|
|
1230
|
+
}
|
|
738
1231
|
|
|
739
|
-
// Rebase fields contained in the base changeset
|
|
740
|
-
for (const [field, baseChanges] of over) {
|
|
741
|
-
const fieldChange: FieldChange = change.get(field) ?? {
|
|
742
|
-
fieldKind: genericFieldKind.identifier,
|
|
743
|
-
change: brand(newGenericChangeset()),
|
|
744
|
-
};
|
|
745
1232
|
const {
|
|
746
1233
|
fieldKind,
|
|
1234
|
+
changeHandler,
|
|
747
1235
|
change1: fieldChangeset,
|
|
748
1236
|
change2: baseChangeset,
|
|
749
|
-
} = this.normalizeFieldChanges(fieldChange,
|
|
750
|
-
|
|
751
|
-
const manager = newCrossFieldManager(crossFieldTable, fieldChange);
|
|
1237
|
+
} = this.normalizeFieldChanges(fieldChange, baseChange, genId, revisionMetadata);
|
|
752
1238
|
|
|
753
|
-
const
|
|
754
|
-
child: NodeId | undefined,
|
|
755
|
-
baseChild: NodeId | undefined,
|
|
756
|
-
_attachState: NodeAttachState | undefined,
|
|
757
|
-
): ChangeAtomId => {
|
|
758
|
-
crossFieldTable.nodeIdPairs.push([child, baseChild]);
|
|
759
|
-
return (
|
|
760
|
-
child ??
|
|
761
|
-
// The fact `child` is undefined means that the changeset to rebase does not include changes for
|
|
762
|
-
// this node or its descendants. However, it's possible that it will after rebasing.
|
|
763
|
-
// In that case, we will need a NodeId to represent these changes under in the rebased changeset.
|
|
764
|
-
// We adopt `baseChild` for this purpose.
|
|
765
|
-
baseChild ??
|
|
766
|
-
fail("Should not have two undefined node IDs")
|
|
767
|
-
);
|
|
768
|
-
};
|
|
1239
|
+
const manager = new RebaseManager(crossFieldTable, baseChange, fieldId);
|
|
769
1240
|
|
|
770
|
-
const rebasedField =
|
|
1241
|
+
const rebasedField = changeHandler.rebaser.rebase(
|
|
771
1242
|
fieldChangeset,
|
|
772
1243
|
baseChangeset,
|
|
773
1244
|
rebaseChild,
|
|
@@ -777,88 +1248,54 @@ export class ModularChangeFamily
|
|
|
777
1248
|
);
|
|
778
1249
|
|
|
779
1250
|
const rebasedFieldChange: FieldChange = {
|
|
780
|
-
fieldKind
|
|
1251
|
+
fieldKind,
|
|
781
1252
|
change: brand(rebasedField),
|
|
782
1253
|
};
|
|
783
1254
|
|
|
784
1255
|
rebasedFields.set(field, rebasedFieldChange);
|
|
785
1256
|
|
|
786
|
-
crossFieldTable.
|
|
787
|
-
baseChange
|
|
1257
|
+
crossFieldTable.baseFieldToContext.set(baseChange, {
|
|
1258
|
+
baseChange,
|
|
788
1259
|
newChange: fieldChange,
|
|
789
1260
|
rebasedChange: rebasedFieldChange,
|
|
1261
|
+
fieldId,
|
|
1262
|
+
baseNodeIds: [],
|
|
790
1263
|
});
|
|
791
|
-
}
|
|
792
|
-
|
|
793
|
-
// Rebase the fields of the new changeset which don't have a corresponding base field.
|
|
794
|
-
for (const [field, fieldChange] of change) {
|
|
795
|
-
if (!over?.has(field)) {
|
|
796
|
-
const baseChanges: FieldChange = {
|
|
797
|
-
fieldKind: genericFieldKind.identifier,
|
|
798
|
-
change: brand(newGenericChangeset()),
|
|
799
|
-
};
|
|
800
|
-
|
|
801
|
-
const {
|
|
802
|
-
fieldKind,
|
|
803
|
-
change1: fieldChangeset,
|
|
804
|
-
change2: baseChangeset,
|
|
805
|
-
} = this.normalizeFieldChanges(fieldChange, baseChanges, genId, revisionMetadata);
|
|
806
|
-
|
|
807
|
-
// TODO: Don't we need to add an entry in the context table?
|
|
808
|
-
const manager = newCrossFieldManager(crossFieldTable, fieldChange);
|
|
809
|
-
const rebasedChangeset = fieldKind.changeHandler.rebaser.rebase(
|
|
810
|
-
fieldChangeset,
|
|
811
|
-
baseChangeset,
|
|
812
|
-
(child, baseChild) => {
|
|
813
|
-
assert(
|
|
814
|
-
baseChild === undefined,
|
|
815
|
-
0x5b6 /* This field should not have any base changes */,
|
|
816
|
-
);
|
|
817
|
-
|
|
818
|
-
crossFieldTable.nodeIdPairs.push([child, undefined]);
|
|
819
1264
|
|
|
820
|
-
|
|
821
|
-
},
|
|
822
|
-
genId,
|
|
823
|
-
manager,
|
|
824
|
-
revisionMetadata,
|
|
825
|
-
);
|
|
826
|
-
const rebasedFieldChange: FieldChange = {
|
|
827
|
-
fieldKind: fieldKind.identifier,
|
|
828
|
-
change: brand(rebasedChangeset),
|
|
829
|
-
};
|
|
830
|
-
rebasedFields.set(field, rebasedFieldChange);
|
|
831
|
-
}
|
|
1265
|
+
crossFieldTable.rebasedFields.add(rebasedFieldChange);
|
|
832
1266
|
}
|
|
833
1267
|
|
|
834
1268
|
return rebasedFields;
|
|
835
1269
|
}
|
|
836
1270
|
|
|
837
1271
|
private rebaseNodeChange(
|
|
838
|
-
|
|
839
|
-
|
|
1272
|
+
newId: NodeId,
|
|
1273
|
+
baseId: NodeId,
|
|
840
1274
|
genId: IdAllocator,
|
|
841
1275
|
crossFieldTable: RebaseTable,
|
|
842
1276
|
revisionMetadata: RebaseRevisionMetadata,
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
}
|
|
1277
|
+
constraintState: ConstraintState,
|
|
1278
|
+
): NodeChangeset {
|
|
1279
|
+
const change = nodeChangeFromId(crossFieldTable.newChange.nodeChanges, newId);
|
|
1280
|
+
const over = nodeChangeFromId(crossFieldTable.baseChange.nodeChanges, baseId);
|
|
848
1281
|
|
|
849
1282
|
const baseMap: FieldChangeMap = over?.fieldChanges ?? new Map();
|
|
850
1283
|
|
|
851
|
-
const fieldChanges =
|
|
852
|
-
change
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
1284
|
+
const fieldChanges =
|
|
1285
|
+
change.fieldChanges !== undefined && over.fieldChanges !== undefined
|
|
1286
|
+
? this.rebaseFieldMap(
|
|
1287
|
+
change?.fieldChanges ?? new Map(),
|
|
1288
|
+
baseMap,
|
|
1289
|
+
newId,
|
|
1290
|
+
genId,
|
|
1291
|
+
crossFieldTable,
|
|
1292
|
+
revisionMetadata,
|
|
1293
|
+
)
|
|
1294
|
+
: change.fieldChanges;
|
|
858
1295
|
|
|
859
1296
|
const rebasedChange: NodeChangeset = {};
|
|
860
1297
|
|
|
861
|
-
if (fieldChanges.size > 0) {
|
|
1298
|
+
if (fieldChanges !== undefined && fieldChanges.size > 0) {
|
|
862
1299
|
rebasedChange.fieldChanges = fieldChanges;
|
|
863
1300
|
}
|
|
864
1301
|
|
|
@@ -866,7 +1303,7 @@ export class ModularChangeFamily
|
|
|
866
1303
|
rebasedChange.nodeExistsConstraint = change.nodeExistsConstraint;
|
|
867
1304
|
}
|
|
868
1305
|
|
|
869
|
-
crossFieldTable.
|
|
1306
|
+
setInChangeAtomIdMap(crossFieldTable.baseToRebasedNodeId, baseId, newId);
|
|
870
1307
|
return rebasedChange;
|
|
871
1308
|
}
|
|
872
1309
|
|
|
@@ -915,9 +1352,13 @@ export class ModularChangeFamily
|
|
|
915
1352
|
}
|
|
916
1353
|
|
|
917
1354
|
private pruneFieldMap(
|
|
918
|
-
changeset: FieldChangeMap,
|
|
1355
|
+
changeset: FieldChangeMap | undefined,
|
|
919
1356
|
nodeMap: ChangeAtomIdMap<NodeChangeset>,
|
|
920
1357
|
): FieldChangeMap | undefined {
|
|
1358
|
+
if (changeset === undefined) {
|
|
1359
|
+
return undefined;
|
|
1360
|
+
}
|
|
1361
|
+
|
|
921
1362
|
const prunedChangeset: FieldChangeMap = new Map();
|
|
922
1363
|
for (const [field, fieldChange] of changeset) {
|
|
923
1364
|
const handler = getChangeHandler(this.fieldKinds, fieldChange.fieldKind);
|
|
@@ -938,9 +1379,7 @@ export class ModularChangeFamily
|
|
|
938
1379
|
nodeId: NodeId,
|
|
939
1380
|
nodeMap: ChangeAtomIdMap<NodeChangeset>,
|
|
940
1381
|
): NodeId | undefined {
|
|
941
|
-
const changeset =
|
|
942
|
-
assert(changeset !== undefined, 0x930 /* Unknown node ID */);
|
|
943
|
-
|
|
1382
|
+
const changeset = nodeChangeFromId(nodeMap, nodeId);
|
|
944
1383
|
const prunedFields =
|
|
945
1384
|
changeset.fieldChanges !== undefined
|
|
946
1385
|
? this.pruneFieldMap(changeset.fieldChanges, nodeMap)
|
|
@@ -955,7 +1394,7 @@ export class ModularChangeFamily
|
|
|
955
1394
|
deleteFromNestedMap(nodeMap, nodeId.revision, nodeId.localId);
|
|
956
1395
|
return undefined;
|
|
957
1396
|
} else {
|
|
958
|
-
|
|
1397
|
+
setInChangeAtomIdMap(nodeMap, nodeId, prunedChange);
|
|
959
1398
|
return nodeId;
|
|
960
1399
|
}
|
|
961
1400
|
}
|
|
@@ -984,10 +1423,32 @@ export class ModularChangeFamily
|
|
|
984
1423
|
]),
|
|
985
1424
|
);
|
|
986
1425
|
|
|
1426
|
+
const updatedNodeToParent: ChangeAtomIdMap<FieldId> = nestedMapFromFlatList(
|
|
1427
|
+
nestedMapToFlatList(change.nodeToParent).map(([revision, id, fieldId]) => [
|
|
1428
|
+
replaceRevision(revision, oldRevisions, newRevision),
|
|
1429
|
+
id,
|
|
1430
|
+
replaceFieldIdRevision(
|
|
1431
|
+
normalizeFieldId(fieldId, change.nodeAliases),
|
|
1432
|
+
oldRevisions,
|
|
1433
|
+
newRevision,
|
|
1434
|
+
),
|
|
1435
|
+
]),
|
|
1436
|
+
);
|
|
1437
|
+
|
|
987
1438
|
const updated: Mutable<ModularChangeset> = {
|
|
988
1439
|
...change,
|
|
989
1440
|
fieldChanges: updatedFields,
|
|
990
1441
|
nodeChanges: updatedNodes,
|
|
1442
|
+
nodeToParent: updatedNodeToParent,
|
|
1443
|
+
|
|
1444
|
+
// We've updated all references to old node IDs, so we no longer need an alias table.
|
|
1445
|
+
nodeAliases: new Map(),
|
|
1446
|
+
crossFieldKeys: replaceCrossFieldKeyTableRevisions(
|
|
1447
|
+
change.crossFieldKeys,
|
|
1448
|
+
oldRevisions,
|
|
1449
|
+
newRevision,
|
|
1450
|
+
change.nodeAliases,
|
|
1451
|
+
),
|
|
991
1452
|
};
|
|
992
1453
|
|
|
993
1454
|
if (change.builds !== undefined) {
|
|
@@ -1030,30 +1491,100 @@ export class ModularChangeFamily
|
|
|
1030
1491
|
);
|
|
1031
1492
|
}
|
|
1032
1493
|
|
|
1033
|
-
return updated;
|
|
1494
|
+
return updated;
|
|
1495
|
+
}
|
|
1496
|
+
|
|
1497
|
+
private replaceFieldMapRevisions(
|
|
1498
|
+
fields: FieldChangeMap,
|
|
1499
|
+
oldRevisions: Set<RevisionTag | undefined>,
|
|
1500
|
+
newRevision: RevisionTag | undefined,
|
|
1501
|
+
): FieldChangeMap {
|
|
1502
|
+
const updatedFields: FieldChangeMap = new Map();
|
|
1503
|
+
for (const [field, fieldChange] of fields) {
|
|
1504
|
+
const updatedFieldChange = getChangeHandler(
|
|
1505
|
+
this.fieldKinds,
|
|
1506
|
+
fieldChange.fieldKind,
|
|
1507
|
+
).rebaser.replaceRevisions(fieldChange.change, oldRevisions, newRevision);
|
|
1508
|
+
|
|
1509
|
+
updatedFields.set(field, { ...fieldChange, change: brand(updatedFieldChange) });
|
|
1510
|
+
}
|
|
1511
|
+
|
|
1512
|
+
return updatedFields;
|
|
1513
|
+
}
|
|
1514
|
+
|
|
1515
|
+
private makeCrossFieldKeyTable(
|
|
1516
|
+
fields: FieldChangeMap,
|
|
1517
|
+
nodes: ChangeAtomIdMap<NodeChangeset>,
|
|
1518
|
+
): CrossFieldKeyTable {
|
|
1519
|
+
const keys: CrossFieldKeyTable = newCrossFieldKeyTable();
|
|
1520
|
+
this.populateCrossFieldKeyTableForFieldMap(keys, fields, undefined);
|
|
1521
|
+
forEachInNestedMap(nodes, (node, revision, localId) => {
|
|
1522
|
+
if (node.fieldChanges !== undefined) {
|
|
1523
|
+
this.populateCrossFieldKeyTableForFieldMap(keys, node.fieldChanges, {
|
|
1524
|
+
revision,
|
|
1525
|
+
localId,
|
|
1526
|
+
});
|
|
1527
|
+
}
|
|
1528
|
+
});
|
|
1529
|
+
|
|
1530
|
+
return keys;
|
|
1531
|
+
}
|
|
1532
|
+
|
|
1533
|
+
private populateCrossFieldKeyTableForFieldMap(
|
|
1534
|
+
table: CrossFieldKeyTable,
|
|
1535
|
+
fields: FieldChangeMap,
|
|
1536
|
+
parent: NodeId | undefined,
|
|
1537
|
+
): void {
|
|
1538
|
+
for (const [fieldKey, fieldChange] of fields) {
|
|
1539
|
+
const keys = getChangeHandler(this.fieldKinds, fieldChange.fieldKind).getCrossFieldKeys(
|
|
1540
|
+
fieldChange.change,
|
|
1541
|
+
);
|
|
1542
|
+
for (const key of keys) {
|
|
1543
|
+
table.set(key, { nodeId: parent, field: fieldKey });
|
|
1544
|
+
}
|
|
1545
|
+
}
|
|
1546
|
+
}
|
|
1547
|
+
|
|
1548
|
+
public buildEditor(changeReceiver: (change: ModularChangeset) => void): ModularEditBuilder {
|
|
1549
|
+
return new ModularEditBuilder(this, this.fieldKinds, changeReceiver);
|
|
1034
1550
|
}
|
|
1035
1551
|
|
|
1036
|
-
private
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
const updatedFields: FieldChangeMap = new Map();
|
|
1042
|
-
for (const [field, fieldChange] of fields) {
|
|
1043
|
-
const updatedFieldChange = getFieldKind(
|
|
1044
|
-
this.fieldKinds,
|
|
1045
|
-
fieldChange.fieldKind,
|
|
1046
|
-
).changeHandler.rebaser.replaceRevisions(fieldChange.change, oldRevisions, newRevision);
|
|
1552
|
+
private createEmptyFieldChange(fieldKind: FieldKindIdentifier): FieldChange {
|
|
1553
|
+
const emptyChange = getChangeHandler(this.fieldKinds, fieldKind).createEmpty();
|
|
1554
|
+
return { fieldKind, change: brand(emptyChange) };
|
|
1555
|
+
}
|
|
1556
|
+
}
|
|
1047
1557
|
|
|
1048
|
-
|
|
1049
|
-
|
|
1558
|
+
function replaceCrossFieldKeyTableRevisions(
|
|
1559
|
+
table: CrossFieldKeyTable,
|
|
1560
|
+
oldRevisions: Set<RevisionTag | undefined>,
|
|
1561
|
+
newRevision: RevisionTag | undefined,
|
|
1562
|
+
nodeAliases: ChangeAtomIdMap<NodeId>,
|
|
1563
|
+
): CrossFieldKeyTable {
|
|
1564
|
+
const updated: CrossFieldKeyTable = newBTree();
|
|
1565
|
+
table.forEachPair(([target, revision, id, count], field) => {
|
|
1566
|
+
const updatedKey: CrossFieldKeyRange = [
|
|
1567
|
+
target,
|
|
1568
|
+
replaceRevision(revision, oldRevisions, newRevision),
|
|
1569
|
+
id,
|
|
1570
|
+
count,
|
|
1571
|
+
];
|
|
1050
1572
|
|
|
1051
|
-
|
|
1052
|
-
|
|
1573
|
+
const normalizedFieldId = normalizeFieldId(field, nodeAliases);
|
|
1574
|
+
const updatedNodeId =
|
|
1575
|
+
normalizedFieldId.nodeId !== undefined
|
|
1576
|
+
? replaceAtomRevisions(normalizedFieldId.nodeId, oldRevisions, newRevision)
|
|
1577
|
+
: undefined;
|
|
1053
1578
|
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1579
|
+
const updatedValue: FieldId = {
|
|
1580
|
+
...normalizedFieldId,
|
|
1581
|
+
nodeId: updatedNodeId,
|
|
1582
|
+
};
|
|
1583
|
+
|
|
1584
|
+
updated.set(updatedKey, updatedValue);
|
|
1585
|
+
});
|
|
1586
|
+
|
|
1587
|
+
return updated;
|
|
1057
1588
|
}
|
|
1058
1589
|
|
|
1059
1590
|
function replaceRevision(
|
|
@@ -1215,8 +1746,7 @@ function* relevantRemovedRootsFromFields(
|
|
|
1215
1746
|
for (const [_, fieldChange] of change) {
|
|
1216
1747
|
const handler = getChangeHandler(fieldKinds, fieldChange.fieldKind);
|
|
1217
1748
|
const delegate = function* (node: NodeId): Iterable<DeltaDetachedNodeId> {
|
|
1218
|
-
const nodeChangeset =
|
|
1219
|
-
assert(nodeChangeset !== undefined, 0x931 /* Unknown node ID */);
|
|
1749
|
+
const nodeChangeset = nodeChangeFromId(nodeChanges, node);
|
|
1220
1750
|
if (nodeChangeset.fieldChanges !== undefined) {
|
|
1221
1751
|
yield* relevantRemovedRootsFromFields(
|
|
1222
1752
|
nodeChangeset.fieldChanges,
|
|
@@ -1297,6 +1827,9 @@ export function updateRefreshers(
|
|
|
1297
1827
|
return makeModularChangeset(
|
|
1298
1828
|
fieldChanges,
|
|
1299
1829
|
nodeChanges,
|
|
1830
|
+
change.nodeToParent,
|
|
1831
|
+
change.nodeAliases,
|
|
1832
|
+
change.crossFieldKeys,
|
|
1300
1833
|
maxId,
|
|
1301
1834
|
revisions,
|
|
1302
1835
|
constraintViolationCount,
|
|
@@ -1383,13 +1916,7 @@ function intoDeltaImpl(
|
|
|
1383
1916
|
const deltaField = getChangeHandler(fieldKinds, fieldChange.fieldKind).intoDelta(
|
|
1384
1917
|
fieldChange.change,
|
|
1385
1918
|
(childChange): DeltaFieldMap => {
|
|
1386
|
-
const nodeChange =
|
|
1387
|
-
nodeChanges,
|
|
1388
|
-
childChange.revision,
|
|
1389
|
-
childChange.localId,
|
|
1390
|
-
);
|
|
1391
|
-
|
|
1392
|
-
assert(nodeChange !== undefined, 0x932 /* Unknown node ID */);
|
|
1919
|
+
const nodeChange = nodeChangeFromId(nodeChanges, childChange);
|
|
1393
1920
|
return deltaFromNodeChange(nodeChange, nodeChanges, idAllocator, fieldKinds);
|
|
1394
1921
|
},
|
|
1395
1922
|
idAllocator,
|
|
@@ -1482,68 +2009,100 @@ interface CrossFieldTable<TFieldData> {
|
|
|
1482
2009
|
|
|
1483
2010
|
interface InvertTable extends CrossFieldTable<FieldChange> {
|
|
1484
2011
|
originalFieldToContext: Map<FieldChange, InvertContext>;
|
|
2012
|
+
invertedNodeToParent: ChangeAtomIdMap<FieldId>;
|
|
1485
2013
|
}
|
|
1486
2014
|
|
|
1487
2015
|
interface InvertContext {
|
|
2016
|
+
fieldId: FieldId;
|
|
1488
2017
|
invertedField: FieldChange;
|
|
1489
2018
|
}
|
|
1490
2019
|
|
|
1491
2020
|
interface RebaseTable extends CrossFieldTable<FieldChange> {
|
|
2021
|
+
readonly baseChange: ModularChangeset;
|
|
2022
|
+
readonly newChange: ModularChangeset;
|
|
2023
|
+
|
|
1492
2024
|
/**
|
|
1493
|
-
* Maps from the FieldChange key used for the CrossFieldTable (which is the FieldChange
|
|
1494
|
-
* to context for the field.
|
|
1495
|
-
*/
|
|
1496
|
-
fieldToContext: Map<FieldChange, RebaseFieldContext>;
|
|
1497
|
-
/**
|
|
1498
|
-
* This map caches the output of a prior rebasing computation for a node, keyed on that computation's input.
|
|
1499
|
-
* The input for such a computation is characterized by a pair of node changesets:
|
|
1500
|
-
* - The node changeset from the input changeset being rebased
|
|
1501
|
-
* - The corresponding node changeset from the changeset being rebased over.
|
|
1502
|
-
*
|
|
1503
|
-
* Either of these may be undefined so we adopt the following convention:
|
|
1504
|
-
* - If the node changeset from the changeset being rebased is defined, then we use that as the key
|
|
1505
|
-
* - Otherwise, if the node changeset from the changeset being rebased over is defined, then we use that as the key
|
|
1506
|
-
* - Otherwise, we don't cache the output (which will be undefined anyway).
|
|
1507
|
-
*
|
|
1508
|
-
* This map is needed once we switch from the initial pass (which generates a new changeset) to the second pass which
|
|
1509
|
-
* performs surgery on the changeset generated in the first pass: we don't want to re-run the rebasing of nested
|
|
1510
|
-
* changes. Instead we want to keep using the objects generated in the first pass and mutate them where needed.
|
|
2025
|
+
* Maps from the FieldChange key used for the CrossFieldTable (which is the base FieldChange)
|
|
2026
|
+
* to the context for the field.
|
|
1511
2027
|
*/
|
|
1512
|
-
|
|
2028
|
+
readonly baseFieldToContext: Map<FieldChange, RebaseFieldContext>;
|
|
2029
|
+
readonly baseToRebasedNodeId: ChangeAtomIdMap<NodeId>;
|
|
2030
|
+
readonly rebasedFields: Set<FieldChange>;
|
|
2031
|
+
readonly rebasedNodeToParent: ChangeAtomIdMap<FieldId>;
|
|
2032
|
+
readonly rebasedCrossFieldKeys: CrossFieldKeyTable;
|
|
1513
2033
|
|
|
1514
2034
|
/**
|
|
1515
|
-
* List of (newId, baseId) pairs encountered so far.
|
|
2035
|
+
* List of unprocessed (newId, baseId) pairs encountered so far.
|
|
1516
2036
|
*/
|
|
1517
|
-
nodeIdPairs: [NodeId
|
|
2037
|
+
readonly nodeIdPairs: [NodeId, NodeId, NodeAttachState | undefined][];
|
|
2038
|
+
readonly affectedBaseFields: TupleBTree<FieldIdKey, boolean>;
|
|
1518
2039
|
}
|
|
1519
2040
|
|
|
2041
|
+
type FieldIdKey = [RevisionTag | undefined, ChangesetLocalId | undefined, FieldKey];
|
|
2042
|
+
|
|
1520
2043
|
interface RebaseFieldContext {
|
|
1521
2044
|
baseChange: FieldChange;
|
|
1522
2045
|
newChange: FieldChange;
|
|
1523
2046
|
rebasedChange: FieldChange;
|
|
2047
|
+
fieldId: FieldId;
|
|
2048
|
+
|
|
2049
|
+
/**
|
|
2050
|
+
* The set of node IDs in the base changeset which should be included in the rebased field,
|
|
2051
|
+
* even if there is no corresponding node changeset in the new change.
|
|
2052
|
+
*/
|
|
2053
|
+
baseNodeIds: NodeId[];
|
|
1524
2054
|
}
|
|
1525
2055
|
|
|
1526
|
-
function newComposeTable(
|
|
2056
|
+
function newComposeTable(
|
|
2057
|
+
baseChange: ModularChangeset,
|
|
2058
|
+
newChange: ModularChangeset,
|
|
2059
|
+
): ComposeTable {
|
|
1527
2060
|
return {
|
|
1528
2061
|
...newCrossFieldTable<FieldChange>(),
|
|
2062
|
+
baseChange,
|
|
2063
|
+
newChange,
|
|
1529
2064
|
fieldToContext: new Map(),
|
|
1530
|
-
|
|
1531
|
-
|
|
2065
|
+
newFieldToBaseField: new Map(),
|
|
2066
|
+
newToBaseNodeId: new Map(),
|
|
2067
|
+
composedNodes: new Set(),
|
|
2068
|
+
pendingCompositions: {
|
|
2069
|
+
nodeIdsToCompose: [],
|
|
2070
|
+
affectedBaseFields: newBTree(),
|
|
2071
|
+
affectedNewFields: newBTree(),
|
|
2072
|
+
},
|
|
1532
2073
|
};
|
|
1533
2074
|
}
|
|
1534
2075
|
|
|
1535
2076
|
interface ComposeTable extends CrossFieldTable<FieldChange> {
|
|
2077
|
+
readonly baseChange: ModularChangeset;
|
|
2078
|
+
readonly newChange: ModularChangeset;
|
|
2079
|
+
|
|
1536
2080
|
/**
|
|
1537
2081
|
* Maps from an input changeset for a field (from change1 if it has one, from change2 otherwise) to the context for that field.
|
|
1538
2082
|
*/
|
|
1539
|
-
fieldToContext: Map<FieldChange, ComposeFieldContext>;
|
|
2083
|
+
readonly fieldToContext: Map<FieldChange, ComposeFieldContext>;
|
|
2084
|
+
readonly newFieldToBaseField: Map<FieldChange, FieldChange>;
|
|
2085
|
+
readonly newToBaseNodeId: ChangeAtomIdMap<NodeId>;
|
|
2086
|
+
readonly composedNodes: Set<NodeChangeset>;
|
|
2087
|
+
readonly pendingCompositions: PendingCompositions;
|
|
2088
|
+
}
|
|
2089
|
+
|
|
2090
|
+
interface PendingCompositions {
|
|
2091
|
+
/**
|
|
2092
|
+
* Each entry in this list represents a node with both base and new changes which have not yet been composed.
|
|
2093
|
+
* Entries are of the form [baseId, newId].
|
|
2094
|
+
*/
|
|
2095
|
+
readonly nodeIdsToCompose: [NodeId, NodeId][];
|
|
1540
2096
|
|
|
1541
2097
|
/**
|
|
1542
|
-
* The set of
|
|
2098
|
+
* The set of fields in the base changeset which have been affected by a cross field effect.
|
|
1543
2099
|
*/
|
|
1544
|
-
|
|
2100
|
+
readonly affectedBaseFields: BTree<FieldIdKey, true>;
|
|
1545
2101
|
|
|
1546
|
-
|
|
2102
|
+
/**
|
|
2103
|
+
* The set of fields in the new changeset which have been affected by a cross field effect.
|
|
2104
|
+
*/
|
|
2105
|
+
readonly affectedNewFields: BTree<FieldIdKey, true>;
|
|
1547
2106
|
}
|
|
1548
2107
|
|
|
1549
2108
|
interface ComposeFieldContext {
|
|
@@ -1575,77 +2134,266 @@ function newConstraintState(violationCount: number): ConstraintState {
|
|
|
1575
2134
|
};
|
|
1576
2135
|
}
|
|
1577
2136
|
|
|
1578
|
-
|
|
1579
|
-
|
|
1580
|
-
|
|
1581
|
-
|
|
1582
|
-
|
|
1583
|
-
|
|
1584
|
-
|
|
1585
|
-
|
|
1586
|
-
|
|
1587
|
-
|
|
1588
|
-
|
|
1589
|
-
|
|
1590
|
-
|
|
1591
|
-
|
|
1592
|
-
|
|
1593
|
-
|
|
1594
|
-
|
|
1595
|
-
|
|
1596
|
-
|
|
1597
|
-
|
|
1598
|
-
|
|
1599
|
-
|
|
1600
|
-
|
|
1601
|
-
|
|
1602
|
-
|
|
1603
|
-
|
|
1604
|
-
|
|
1605
|
-
|
|
1606
|
-
|
|
1607
|
-
|
|
1608
|
-
|
|
1609
|
-
|
|
1610
|
-
|
|
1611
|
-
|
|
1612
|
-
|
|
1613
|
-
|
|
1614
|
-
|
|
2137
|
+
abstract class CrossFieldManagerI<T> implements CrossFieldManager {
|
|
2138
|
+
public constructor(
|
|
2139
|
+
protected readonly crossFieldTable: CrossFieldTable<T>,
|
|
2140
|
+
private readonly currentFieldKey: T,
|
|
2141
|
+
protected readonly allowInval = true,
|
|
2142
|
+
) {}
|
|
2143
|
+
|
|
2144
|
+
public set(
|
|
2145
|
+
target: CrossFieldTarget,
|
|
2146
|
+
revision: RevisionTag | undefined,
|
|
2147
|
+
id: ChangesetLocalId,
|
|
2148
|
+
count: number,
|
|
2149
|
+
newValue: unknown,
|
|
2150
|
+
invalidateDependents: boolean,
|
|
2151
|
+
): void {
|
|
2152
|
+
if (invalidateDependents && this.allowInval) {
|
|
2153
|
+
const lastChangedId = (id as number) + count - 1;
|
|
2154
|
+
let firstId = id;
|
|
2155
|
+
while (firstId <= lastChangedId) {
|
|
2156
|
+
const dependentEntry = getFirstFromCrossFieldMap(
|
|
2157
|
+
this.getDependents(target),
|
|
2158
|
+
revision,
|
|
2159
|
+
firstId,
|
|
2160
|
+
lastChangedId - firstId + 1,
|
|
2161
|
+
);
|
|
2162
|
+
if (dependentEntry.value !== undefined) {
|
|
2163
|
+
this.crossFieldTable.invalidatedFields.add(dependentEntry.value);
|
|
2164
|
+
}
|
|
2165
|
+
|
|
2166
|
+
firstId = brand(firstId + dependentEntry.length);
|
|
2167
|
+
}
|
|
2168
|
+
}
|
|
2169
|
+
setInCrossFieldMap(this.getMap(target), revision, id, count, newValue);
|
|
2170
|
+
}
|
|
2171
|
+
|
|
2172
|
+
public get(
|
|
2173
|
+
target: CrossFieldTarget,
|
|
2174
|
+
revision: RevisionTag | undefined,
|
|
2175
|
+
id: ChangesetLocalId,
|
|
2176
|
+
count: number,
|
|
2177
|
+
addDependency: boolean,
|
|
2178
|
+
): RangeQueryResult<unknown> {
|
|
2179
|
+
if (addDependency) {
|
|
2180
|
+
// We assume that if there is already an entry for this ID it is because
|
|
2181
|
+
// a field handler has called compose on the same node multiple times.
|
|
2182
|
+
// In this case we only want to update the latest version, so we overwrite the dependency.
|
|
2183
|
+
setInCrossFieldMap(
|
|
2184
|
+
this.getDependents(target),
|
|
2185
|
+
revision,
|
|
2186
|
+
id,
|
|
2187
|
+
count,
|
|
2188
|
+
this.currentFieldKey,
|
|
2189
|
+
);
|
|
2190
|
+
}
|
|
2191
|
+
return getFirstFromCrossFieldMap(this.getMap(target), revision, id, count);
|
|
2192
|
+
}
|
|
2193
|
+
|
|
2194
|
+
public abstract onMoveIn(id: NodeId): void;
|
|
2195
|
+
|
|
2196
|
+
public abstract moveKey(
|
|
2197
|
+
target: CrossFieldTarget,
|
|
2198
|
+
revision: RevisionTag | undefined,
|
|
2199
|
+
id: ChangesetLocalId,
|
|
2200
|
+
count: number,
|
|
2201
|
+
): void;
|
|
2202
|
+
|
|
2203
|
+
private getMap(target: CrossFieldTarget): CrossFieldMap<unknown> {
|
|
2204
|
+
return target === CrossFieldTarget.Source
|
|
2205
|
+
? this.crossFieldTable.srcTable
|
|
2206
|
+
: this.crossFieldTable.dstTable;
|
|
2207
|
+
}
|
|
2208
|
+
|
|
2209
|
+
private getDependents(target: CrossFieldTarget): CrossFieldMap<T> {
|
|
2210
|
+
return target === CrossFieldTarget.Source
|
|
2211
|
+
? this.crossFieldTable.srcDependents
|
|
2212
|
+
: this.crossFieldTable.dstDependents;
|
|
2213
|
+
}
|
|
2214
|
+
}
|
|
2215
|
+
|
|
2216
|
+
class InvertManager extends CrossFieldManagerI<FieldChange> {
|
|
2217
|
+
public constructor(
|
|
2218
|
+
table: InvertTable,
|
|
2219
|
+
field: FieldChange,
|
|
2220
|
+
private readonly fieldId: FieldId,
|
|
2221
|
+
allowInval = true,
|
|
2222
|
+
) {
|
|
2223
|
+
super(table, field, allowInval);
|
|
2224
|
+
}
|
|
2225
|
+
|
|
2226
|
+
public override onMoveIn(id: ChangeAtomId): void {
|
|
2227
|
+
setInChangeAtomIdMap(this.table.invertedNodeToParent, id, this.fieldId);
|
|
2228
|
+
}
|
|
2229
|
+
|
|
2230
|
+
public override moveKey(
|
|
2231
|
+
target: CrossFieldTarget,
|
|
2232
|
+
revision: RevisionTag | undefined,
|
|
2233
|
+
id: ChangesetLocalId,
|
|
2234
|
+
count: number,
|
|
2235
|
+
): void {
|
|
2236
|
+
assert(false, "Keys should not be moved manually during invert");
|
|
2237
|
+
}
|
|
2238
|
+
|
|
2239
|
+
private get table(): InvertTable {
|
|
2240
|
+
return this.crossFieldTable as InvertTable;
|
|
2241
|
+
}
|
|
2242
|
+
}
|
|
2243
|
+
|
|
2244
|
+
class RebaseManager extends CrossFieldManagerI<FieldChange> {
|
|
2245
|
+
public constructor(
|
|
2246
|
+
table: RebaseTable,
|
|
2247
|
+
currentField: FieldChange,
|
|
2248
|
+
private readonly fieldId: FieldId,
|
|
2249
|
+
allowInval = true,
|
|
2250
|
+
) {
|
|
2251
|
+
super(table, currentField, allowInval);
|
|
2252
|
+
}
|
|
2253
|
+
|
|
2254
|
+
public override set(
|
|
2255
|
+
target: CrossFieldTarget,
|
|
2256
|
+
revision: RevisionTag | undefined,
|
|
2257
|
+
id: ChangesetLocalId,
|
|
2258
|
+
count: number,
|
|
2259
|
+
newValue: unknown,
|
|
2260
|
+
invalidateDependents: boolean,
|
|
2261
|
+
): void {
|
|
2262
|
+
if (invalidateDependents && this.allowInval) {
|
|
2263
|
+
const newFieldIds = getFieldsForCrossFieldKey(this.table.newChange, [
|
|
2264
|
+
target,
|
|
2265
|
+
revision,
|
|
2266
|
+
id,
|
|
2267
|
+
count,
|
|
2268
|
+
]);
|
|
2269
|
+
|
|
2270
|
+
assert(
|
|
2271
|
+
newFieldIds.length === 0,
|
|
2272
|
+
"TODO: Modifying a cross-field key from the new changeset is currently unsupported",
|
|
2273
|
+
);
|
|
2274
|
+
|
|
2275
|
+
const baseFieldIds = getFieldsForCrossFieldKey(this.table.baseChange, [
|
|
2276
|
+
target,
|
|
2277
|
+
revision,
|
|
2278
|
+
id,
|
|
2279
|
+
count,
|
|
2280
|
+
]);
|
|
2281
|
+
|
|
2282
|
+
assert(baseFieldIds.length > 0, "Cross field key not registered in base or new change");
|
|
2283
|
+
|
|
2284
|
+
for (const baseFieldId of baseFieldIds) {
|
|
2285
|
+
this.table.affectedBaseFields.set(
|
|
2286
|
+
[baseFieldId.nodeId?.revision, baseFieldId.nodeId?.localId, baseFieldId.field],
|
|
2287
|
+
true,
|
|
2288
|
+
);
|
|
2289
|
+
}
|
|
2290
|
+
}
|
|
2291
|
+
|
|
2292
|
+
super.set(target, revision, id, count, newValue, invalidateDependents);
|
|
2293
|
+
}
|
|
2294
|
+
|
|
2295
|
+
public override onMoveIn(id: ChangeAtomId): void {
|
|
2296
|
+
setInChangeAtomIdMap(this.table.rebasedNodeToParent, id, this.fieldId);
|
|
2297
|
+
}
|
|
2298
|
+
|
|
2299
|
+
public override moveKey(
|
|
2300
|
+
target: CrossFieldTarget,
|
|
2301
|
+
revision: RevisionTag | undefined,
|
|
2302
|
+
id: ChangesetLocalId,
|
|
2303
|
+
count: number,
|
|
2304
|
+
): void {
|
|
2305
|
+
setInCrossFieldKeyTable(
|
|
2306
|
+
this.table.rebasedCrossFieldKeys,
|
|
2307
|
+
target,
|
|
2308
|
+
revision,
|
|
2309
|
+
id,
|
|
2310
|
+
count,
|
|
2311
|
+
this.fieldId,
|
|
2312
|
+
);
|
|
2313
|
+
}
|
|
2314
|
+
|
|
2315
|
+
private get table(): RebaseTable {
|
|
2316
|
+
return this.crossFieldTable as RebaseTable;
|
|
2317
|
+
}
|
|
2318
|
+
}
|
|
2319
|
+
|
|
2320
|
+
// TODO: Deduplicate this with RebaseTable
|
|
2321
|
+
class ComposeManager extends CrossFieldManagerI<FieldChange> {
|
|
2322
|
+
public constructor(table: ComposeTable, currentField: FieldChange, allowInval = true) {
|
|
2323
|
+
super(table, currentField, allowInval);
|
|
2324
|
+
}
|
|
2325
|
+
|
|
2326
|
+
public override set(
|
|
2327
|
+
target: CrossFieldTarget,
|
|
2328
|
+
revision: RevisionTag | undefined,
|
|
2329
|
+
id: ChangesetLocalId,
|
|
2330
|
+
count: number,
|
|
2331
|
+
newValue: unknown,
|
|
2332
|
+
invalidateDependents: boolean,
|
|
2333
|
+
): void {
|
|
2334
|
+
if (invalidateDependents && this.allowInval) {
|
|
2335
|
+
const newFieldIds = getFieldsForCrossFieldKey(this.table.newChange, [
|
|
2336
|
+
target,
|
|
2337
|
+
revision,
|
|
2338
|
+
id,
|
|
2339
|
+
count,
|
|
2340
|
+
]);
|
|
2341
|
+
|
|
2342
|
+
if (newFieldIds.length > 0) {
|
|
2343
|
+
for (const newFieldId of newFieldIds) {
|
|
2344
|
+
this.table.pendingCompositions.affectedNewFields.set(
|
|
2345
|
+
[newFieldId.nodeId?.revision, newFieldId.nodeId?.localId, newFieldId.field],
|
|
2346
|
+
true,
|
|
1615
2347
|
);
|
|
1616
|
-
|
|
1617
|
-
|
|
1618
|
-
|
|
2348
|
+
}
|
|
2349
|
+
} else {
|
|
2350
|
+
const baseFieldIds = getFieldsForCrossFieldKey(this.table.baseChange, [
|
|
2351
|
+
target,
|
|
2352
|
+
revision,
|
|
2353
|
+
id,
|
|
2354
|
+
count,
|
|
2355
|
+
]);
|
|
1619
2356
|
|
|
1620
|
-
|
|
2357
|
+
assert(
|
|
2358
|
+
baseFieldIds.length > 0,
|
|
2359
|
+
"Cross field key not registered in base or new change",
|
|
2360
|
+
);
|
|
2361
|
+
|
|
2362
|
+
for (const baseFieldId of baseFieldIds) {
|
|
2363
|
+
this.table.pendingCompositions.affectedBaseFields.set(
|
|
2364
|
+
[baseFieldId.nodeId?.revision, baseFieldId.nodeId?.localId, baseFieldId.field],
|
|
2365
|
+
true,
|
|
2366
|
+
);
|
|
1621
2367
|
}
|
|
1622
2368
|
}
|
|
1623
|
-
|
|
1624
|
-
},
|
|
2369
|
+
}
|
|
1625
2370
|
|
|
1626
|
-
|
|
1627
|
-
|
|
1628
|
-
|
|
1629
|
-
|
|
1630
|
-
|
|
1631
|
-
|
|
1632
|
-
|
|
1633
|
-
|
|
1634
|
-
|
|
1635
|
-
|
|
1636
|
-
|
|
1637
|
-
|
|
1638
|
-
|
|
1639
|
-
|
|
1640
|
-
},
|
|
1641
|
-
};
|
|
2371
|
+
super.set(target, revision, id, count, newValue, invalidateDependents);
|
|
2372
|
+
}
|
|
2373
|
+
|
|
2374
|
+
public override onMoveIn(id: ChangeAtomId): void {
|
|
2375
|
+
throw new Error("Method not implemented.");
|
|
2376
|
+
}
|
|
2377
|
+
public override moveKey(
|
|
2378
|
+
target: CrossFieldTarget,
|
|
2379
|
+
revision: RevisionTag | undefined,
|
|
2380
|
+
id: ChangesetLocalId,
|
|
2381
|
+
count: number,
|
|
2382
|
+
): void {
|
|
2383
|
+
throw new Error("Method not implemented.");
|
|
2384
|
+
}
|
|
1642
2385
|
|
|
1643
|
-
|
|
2386
|
+
private get table(): ComposeTable {
|
|
2387
|
+
return this.crossFieldTable as ComposeTable;
|
|
2388
|
+
}
|
|
1644
2389
|
}
|
|
1645
2390
|
|
|
1646
2391
|
function makeModularChangeset(
|
|
1647
2392
|
fieldChanges: FieldChangeMap | undefined = undefined,
|
|
1648
2393
|
nodeChanges: ChangeAtomIdMap<NodeChangeset> | undefined = undefined,
|
|
2394
|
+
nodeToParent: ChangeAtomIdMap<FieldId> | undefined = undefined,
|
|
2395
|
+
nodeAliases: ChangeAtomIdMap<NodeId> | undefined = undefined,
|
|
2396
|
+
crossFieldKeys: CrossFieldKeyTable | undefined = undefined,
|
|
1649
2397
|
maxId: number = -1,
|
|
1650
2398
|
revisions: readonly RevisionInfo[] | undefined = undefined,
|
|
1651
2399
|
constraintViolationCount: number | undefined = undefined,
|
|
@@ -1656,7 +2404,11 @@ function makeModularChangeset(
|
|
|
1656
2404
|
const changeset: Mutable<ModularChangeset> = {
|
|
1657
2405
|
fieldChanges: fieldChanges ?? new Map(),
|
|
1658
2406
|
nodeChanges: nodeChanges ?? new Map(),
|
|
2407
|
+
nodeToParent: nodeToParent ?? new Map(),
|
|
2408
|
+
nodeAliases: nodeAliases ?? new Map(),
|
|
2409
|
+
crossFieldKeys: crossFieldKeys ?? newCrossFieldKeyTable(),
|
|
1659
2410
|
};
|
|
2411
|
+
|
|
1660
2412
|
if (revisions !== undefined && revisions.length > 0) {
|
|
1661
2413
|
changeset.revisions = revisions;
|
|
1662
2414
|
}
|
|
@@ -1684,6 +2436,7 @@ export class ModularEditBuilder extends EditBuilder<ModularChangeset> {
|
|
|
1684
2436
|
|
|
1685
2437
|
public constructor(
|
|
1686
2438
|
family: ChangeFamily<ChangeFamilyEditor, ModularChangeset>,
|
|
2439
|
+
private readonly fieldKinds: ReadonlyMap<FieldKindIdentifier, FieldKindWithEditor>,
|
|
1687
2440
|
changeReceiver: (change: ModularChangeset) => void,
|
|
1688
2441
|
) {
|
|
1689
2442
|
super(family, changeReceiver);
|
|
@@ -1744,11 +2497,18 @@ export class ModularEditBuilder extends EditBuilder<ModularChangeset> {
|
|
|
1744
2497
|
fieldKind: FieldKindIdentifier,
|
|
1745
2498
|
change: FieldChangeset,
|
|
1746
2499
|
): void {
|
|
2500
|
+
const crossFieldKeys = getChangeHandler(this.fieldKinds, fieldKind).getCrossFieldKeys(
|
|
2501
|
+
change,
|
|
2502
|
+
);
|
|
2503
|
+
|
|
1747
2504
|
const modularChange = buildModularChangesetFromField(
|
|
1748
2505
|
field,
|
|
1749
2506
|
{ fieldKind, change },
|
|
1750
2507
|
new Map(),
|
|
2508
|
+
new Map(),
|
|
2509
|
+
newCrossFieldKeyTable(),
|
|
1751
2510
|
this.idAllocator,
|
|
2511
|
+
crossFieldKeys,
|
|
1752
2512
|
);
|
|
1753
2513
|
this.applyChange(modularChange);
|
|
1754
2514
|
}
|
|
@@ -1763,6 +2523,9 @@ export class ModularEditBuilder extends EditBuilder<ModularChangeset> {
|
|
|
1763
2523
|
makeAnonChange(
|
|
1764
2524
|
change.type === "global"
|
|
1765
2525
|
? makeModularChangeset(
|
|
2526
|
+
undefined,
|
|
2527
|
+
undefined,
|
|
2528
|
+
undefined,
|
|
1766
2529
|
undefined,
|
|
1767
2530
|
undefined,
|
|
1768
2531
|
this.idAllocator.getMaxId(),
|
|
@@ -1777,7 +2540,12 @@ export class ModularEditBuilder extends EditBuilder<ModularChangeset> {
|
|
|
1777
2540
|
change: change.change,
|
|
1778
2541
|
},
|
|
1779
2542
|
new Map(),
|
|
2543
|
+
new Map(),
|
|
2544
|
+
newCrossFieldKeyTable(),
|
|
1780
2545
|
this.idAllocator,
|
|
2546
|
+
getChangeHandler(this.fieldKinds, change.fieldKind).getCrossFieldKeys(
|
|
2547
|
+
change.change,
|
|
2548
|
+
),
|
|
1781
2549
|
),
|
|
1782
2550
|
),
|
|
1783
2551
|
);
|
|
@@ -1801,7 +2569,14 @@ export class ModularEditBuilder extends EditBuilder<ModularChangeset> {
|
|
|
1801
2569
|
};
|
|
1802
2570
|
|
|
1803
2571
|
this.applyChange(
|
|
1804
|
-
buildModularChangesetFromNode(
|
|
2572
|
+
buildModularChangesetFromNode(
|
|
2573
|
+
path,
|
|
2574
|
+
nodeChange,
|
|
2575
|
+
new Map(),
|
|
2576
|
+
new Map(),
|
|
2577
|
+
newCrossFieldKeyTable(),
|
|
2578
|
+
this.idAllocator,
|
|
2579
|
+
),
|
|
1805
2580
|
);
|
|
1806
2581
|
}
|
|
1807
2582
|
}
|
|
@@ -1810,29 +2585,74 @@ function buildModularChangesetFromField(
|
|
|
1810
2585
|
path: FieldUpPath,
|
|
1811
2586
|
fieldChange: FieldChange,
|
|
1812
2587
|
nodeChanges: ChangeAtomIdMap<NodeChangeset>,
|
|
2588
|
+
nodeToParent: ChangeAtomIdMap<FieldId>,
|
|
2589
|
+
crossFieldKeys: CrossFieldKeyTable,
|
|
1813
2590
|
idAllocator: IdAllocator = idAllocatorFromMaxId(),
|
|
2591
|
+
localCrossFieldKeys: CrossFieldKeyRange[] = [],
|
|
2592
|
+
childId: NodeId | undefined = undefined,
|
|
1814
2593
|
): ModularChangeset {
|
|
1815
2594
|
const fieldChanges: FieldChangeMap = new Map([[path.field, fieldChange]]);
|
|
1816
2595
|
|
|
1817
2596
|
if (path.parent === undefined) {
|
|
1818
|
-
|
|
2597
|
+
for (const key of localCrossFieldKeys) {
|
|
2598
|
+
crossFieldKeys.set(key, { nodeId: undefined, field: path.field });
|
|
2599
|
+
}
|
|
2600
|
+
|
|
2601
|
+
if (childId !== undefined) {
|
|
2602
|
+
setInChangeAtomIdMap(nodeToParent, childId, {
|
|
2603
|
+
nodeId: undefined,
|
|
2604
|
+
field: path.field,
|
|
2605
|
+
});
|
|
2606
|
+
}
|
|
2607
|
+
|
|
2608
|
+
return makeModularChangeset(
|
|
2609
|
+
fieldChanges,
|
|
2610
|
+
nodeChanges,
|
|
2611
|
+
nodeToParent,
|
|
2612
|
+
undefined,
|
|
2613
|
+
crossFieldKeys,
|
|
2614
|
+
idAllocator.getMaxId(),
|
|
2615
|
+
);
|
|
1819
2616
|
}
|
|
1820
2617
|
|
|
1821
2618
|
const nodeChangeset: NodeChangeset = {
|
|
1822
2619
|
fieldChanges,
|
|
1823
2620
|
};
|
|
1824
2621
|
|
|
1825
|
-
|
|
2622
|
+
const parentId: NodeId = { localId: brand(idAllocator.allocate()) };
|
|
2623
|
+
|
|
2624
|
+
for (const key of localCrossFieldKeys) {
|
|
2625
|
+
crossFieldKeys.set(key, { nodeId: parentId, field: path.field });
|
|
2626
|
+
}
|
|
2627
|
+
|
|
2628
|
+
if (childId !== undefined) {
|
|
2629
|
+
setInChangeAtomIdMap(nodeToParent, childId, {
|
|
2630
|
+
nodeId: parentId,
|
|
2631
|
+
field: path.field,
|
|
2632
|
+
});
|
|
2633
|
+
}
|
|
2634
|
+
|
|
2635
|
+
return buildModularChangesetFromNode(
|
|
2636
|
+
path.parent,
|
|
2637
|
+
nodeChangeset,
|
|
2638
|
+
nodeChanges,
|
|
2639
|
+
nodeToParent,
|
|
2640
|
+
crossFieldKeys,
|
|
2641
|
+
idAllocator,
|
|
2642
|
+
parentId,
|
|
2643
|
+
);
|
|
1826
2644
|
}
|
|
1827
2645
|
|
|
1828
2646
|
function buildModularChangesetFromNode(
|
|
1829
2647
|
path: UpPath,
|
|
1830
2648
|
nodeChange: NodeChangeset,
|
|
1831
2649
|
nodeChanges: ChangeAtomIdMap<NodeChangeset>,
|
|
2650
|
+
nodeToParent: ChangeAtomIdMap<FieldId>,
|
|
2651
|
+
crossFieldKeys: CrossFieldKeyTable,
|
|
1832
2652
|
idAllocator: IdAllocator,
|
|
2653
|
+
nodeId: NodeId = { localId: brand(idAllocator.allocate()) },
|
|
1833
2654
|
): ModularChangeset {
|
|
1834
|
-
|
|
1835
|
-
setInNestedMap(nodeChanges, nodeId.revision, nodeId.localId, nodeChange);
|
|
2655
|
+
setInChangeAtomIdMap(nodeChanges, nodeId, nodeChange);
|
|
1836
2656
|
const fieldChangeset = genericFieldKind.changeHandler.editor.buildChildChange(
|
|
1837
2657
|
path.parentIndex,
|
|
1838
2658
|
nodeId,
|
|
@@ -1847,7 +2667,11 @@ function buildModularChangesetFromNode(
|
|
|
1847
2667
|
{ parent: path.parent, field: path.parentField },
|
|
1848
2668
|
fieldChange,
|
|
1849
2669
|
nodeChanges,
|
|
2670
|
+
nodeToParent,
|
|
2671
|
+
crossFieldKeys,
|
|
1850
2672
|
idAllocator,
|
|
2673
|
+
[],
|
|
2674
|
+
nodeId,
|
|
1851
2675
|
);
|
|
1852
2676
|
}
|
|
1853
2677
|
|
|
@@ -1936,8 +2760,259 @@ function revisionFromRevInfos(
|
|
|
1936
2760
|
return revInfos[0].revision;
|
|
1937
2761
|
}
|
|
1938
2762
|
|
|
1939
|
-
function
|
|
1940
|
-
|
|
1941
|
-
|
|
1942
|
-
|
|
2763
|
+
function mergeBTrees<K, V>(tree1: BTree<K, V>, tree2: BTree<K, V>): BTree<K, V> {
|
|
2764
|
+
const result = tree1.clone();
|
|
2765
|
+
tree2.forEachPair((k, v) => {
|
|
2766
|
+
result.set(k, v);
|
|
2767
|
+
});
|
|
2768
|
+
|
|
2769
|
+
return result;
|
|
2770
|
+
}
|
|
2771
|
+
|
|
2772
|
+
function mergeNestedMaps<K1, K2, V>(
|
|
2773
|
+
map1: NestedMap<K1, K2, V>,
|
|
2774
|
+
map2: NestedMap<K1, K2, V>,
|
|
2775
|
+
): NestedMap<K1, K2, V> {
|
|
2776
|
+
const merged: NestedMap<K1, K2, V> = new Map();
|
|
2777
|
+
populateNestedMap(map1, merged, true);
|
|
2778
|
+
populateNestedMap(map2, merged, true);
|
|
2779
|
+
return merged;
|
|
2780
|
+
}
|
|
2781
|
+
|
|
2782
|
+
function fieldChangeFromId(
|
|
2783
|
+
fields: FieldChangeMap,
|
|
2784
|
+
nodes: ChangeAtomIdMap<NodeChangeset>,
|
|
2785
|
+
id: FieldId,
|
|
2786
|
+
): FieldChange {
|
|
2787
|
+
const fieldMap = fieldMapFromNodeId(fields, nodes, id.nodeId);
|
|
2788
|
+
return fieldMap.get(id.field) ?? fail("No field exists for the given ID");
|
|
2789
|
+
}
|
|
2790
|
+
|
|
2791
|
+
function fieldMapFromNodeId(
|
|
2792
|
+
rootFieldMap: FieldChangeMap,
|
|
2793
|
+
nodes: ChangeAtomIdMap<NodeChangeset>,
|
|
2794
|
+
nodeId: NodeId | undefined,
|
|
2795
|
+
): FieldChangeMap {
|
|
2796
|
+
if (nodeId === undefined) {
|
|
2797
|
+
return rootFieldMap;
|
|
2798
|
+
}
|
|
2799
|
+
|
|
2800
|
+
const node = nodeChangeFromId(nodes, nodeId);
|
|
2801
|
+
assert(node.fieldChanges !== undefined, "Expected node to have field changes");
|
|
2802
|
+
return node.fieldChanges;
|
|
2803
|
+
}
|
|
2804
|
+
|
|
2805
|
+
function rebasedFieldIdFromBaseId(table: RebaseTable, baseId: FieldId): FieldId {
|
|
2806
|
+
if (baseId.nodeId === undefined) {
|
|
2807
|
+
return baseId;
|
|
2808
|
+
}
|
|
2809
|
+
|
|
2810
|
+
return { ...baseId, nodeId: rebasedNodeIdFromBaseNodeId(table, baseId.nodeId) };
|
|
2811
|
+
}
|
|
2812
|
+
|
|
2813
|
+
function rebasedNodeIdFromBaseNodeId(table: RebaseTable, baseId: NodeId): NodeId {
|
|
2814
|
+
return getFromChangeAtomIdMap(table.baseToRebasedNodeId, baseId) ?? baseId;
|
|
2815
|
+
}
|
|
2816
|
+
|
|
2817
|
+
function nodeChangeFromId(nodes: ChangeAtomIdMap<NodeChangeset>, id: NodeId): NodeChangeset {
|
|
2818
|
+
const node = getFromChangeAtomIdMap(nodes, id);
|
|
2819
|
+
assert(node !== undefined, "Unknown node ID");
|
|
2820
|
+
return node;
|
|
2821
|
+
}
|
|
2822
|
+
|
|
2823
|
+
function fieldIdFromFieldIdKey([revision, localId, field]: FieldIdKey): FieldId {
|
|
2824
|
+
const nodeId = localId !== undefined ? { revision, localId } : undefined;
|
|
2825
|
+
return { nodeId, field };
|
|
2826
|
+
}
|
|
2827
|
+
|
|
2828
|
+
function cloneNodeChangeset(nodeChangeset: NodeChangeset): NodeChangeset {
|
|
2829
|
+
if (nodeChangeset.fieldChanges !== undefined) {
|
|
2830
|
+
return { ...nodeChangeset, fieldChanges: new Map(nodeChangeset.fieldChanges) };
|
|
2831
|
+
}
|
|
2832
|
+
|
|
2833
|
+
return { ...nodeChangeset };
|
|
2834
|
+
}
|
|
2835
|
+
|
|
2836
|
+
function replaceFieldIdRevision(
|
|
2837
|
+
fieldId: FieldId,
|
|
2838
|
+
oldRevisions: Set<RevisionTag | undefined>,
|
|
2839
|
+
newRevision: RevisionTag | undefined,
|
|
2840
|
+
): FieldId {
|
|
2841
|
+
if (fieldId.nodeId === undefined) {
|
|
2842
|
+
return fieldId;
|
|
2843
|
+
}
|
|
2844
|
+
|
|
2845
|
+
return {
|
|
2846
|
+
...fieldId,
|
|
2847
|
+
nodeId: replaceAtomRevisions(fieldId.nodeId, oldRevisions, newRevision),
|
|
2848
|
+
};
|
|
2849
|
+
}
|
|
2850
|
+
|
|
2851
|
+
export function getParentFieldId(changeset: ModularChangeset, nodeId: NodeId): FieldId {
|
|
2852
|
+
const parentId = getFromChangeAtomIdMap(changeset.nodeToParent, nodeId);
|
|
2853
|
+
assert(parentId !== undefined, "Parent field should be defined");
|
|
2854
|
+
return normalizeFieldId(parentId, changeset.nodeAliases);
|
|
2855
|
+
}
|
|
2856
|
+
|
|
2857
|
+
export function getFieldsForCrossFieldKey(
|
|
2858
|
+
changeset: ModularChangeset,
|
|
2859
|
+
[target, revision, id, count]: CrossFieldKeyRange,
|
|
2860
|
+
): FieldId[] {
|
|
2861
|
+
let firstLocalId: number = id;
|
|
2862
|
+
const lastLocalId = id + count - 1;
|
|
2863
|
+
|
|
2864
|
+
const fields: FieldId[] = [];
|
|
2865
|
+
|
|
2866
|
+
// eslint-disable-next-line no-constant-condition
|
|
2867
|
+
while (true) {
|
|
2868
|
+
const entry = getFirstIntersectingCrossFieldEntry(changeset.crossFieldKeys, [
|
|
2869
|
+
target,
|
|
2870
|
+
revision,
|
|
2871
|
+
brand(firstLocalId),
|
|
2872
|
+
count,
|
|
2873
|
+
]);
|
|
2874
|
+
|
|
2875
|
+
if (entry === undefined) {
|
|
2876
|
+
return fields;
|
|
2877
|
+
}
|
|
2878
|
+
|
|
2879
|
+
const [[_target, _revision, entryId, entryCount], fieldId] = entry;
|
|
2880
|
+
fields.push(normalizeFieldId(fieldId, changeset.nodeAliases));
|
|
2881
|
+
|
|
2882
|
+
const entryLastId = entryId + entryCount - 1;
|
|
2883
|
+
if (entryLastId >= lastLocalId) {
|
|
2884
|
+
return fields;
|
|
2885
|
+
}
|
|
2886
|
+
|
|
2887
|
+
firstLocalId = entryLastId + 1;
|
|
2888
|
+
}
|
|
2889
|
+
}
|
|
2890
|
+
|
|
2891
|
+
function getFirstIntersectingCrossFieldEntry(
|
|
2892
|
+
table: CrossFieldKeyTable,
|
|
2893
|
+
[target, revision, id, count]: CrossFieldKeyRange,
|
|
2894
|
+
): [CrossFieldKeyRange, FieldId] | undefined {
|
|
2895
|
+
const entry = table.nextLowerPair([target, revision, id, Infinity]);
|
|
2896
|
+
if (entry === undefined) {
|
|
2897
|
+
return undefined;
|
|
2898
|
+
}
|
|
2899
|
+
|
|
2900
|
+
const [entryTarget, entryRevision, entryId, entryCount] = entry[0];
|
|
2901
|
+
if (entryTarget !== target || entryRevision !== revision) {
|
|
2902
|
+
return undefined;
|
|
2903
|
+
}
|
|
2904
|
+
|
|
2905
|
+
const lastQueryId = id + count - 1;
|
|
2906
|
+
const entryLastId = entryId + entryCount - 1;
|
|
2907
|
+
if (entryId > lastQueryId || entryLastId < id) {
|
|
2908
|
+
return undefined;
|
|
2909
|
+
}
|
|
2910
|
+
|
|
2911
|
+
return entry;
|
|
2912
|
+
}
|
|
2913
|
+
|
|
2914
|
+
function setInCrossFieldKeyTable(
|
|
2915
|
+
table: CrossFieldKeyTable,
|
|
2916
|
+
target: CrossFieldTarget,
|
|
2917
|
+
revision: RevisionTag | undefined,
|
|
2918
|
+
id: ChangesetLocalId,
|
|
2919
|
+
count: number,
|
|
2920
|
+
value: FieldId,
|
|
2921
|
+
): void {
|
|
2922
|
+
let entry = getFirstIntersectingCrossFieldEntry(table, [target, revision, id, count]);
|
|
2923
|
+
const lastQueryId = id + count - 1;
|
|
2924
|
+
while (entry !== undefined) {
|
|
2925
|
+
const [entryKey, entryValue] = entry;
|
|
2926
|
+
table.delete(entryKey);
|
|
2927
|
+
|
|
2928
|
+
const [_target, _revision, entryId, entryCount] = entryKey;
|
|
2929
|
+
if (entryId < id) {
|
|
2930
|
+
table.set([target, revision, entryId, id - entryId], entryValue);
|
|
2931
|
+
}
|
|
2932
|
+
|
|
2933
|
+
const lastEntryId = entryId + entryCount - 1;
|
|
2934
|
+
if (lastEntryId > lastQueryId) {
|
|
2935
|
+
table.set(
|
|
2936
|
+
[target, revision, brand(lastQueryId + 1), lastEntryId - lastQueryId],
|
|
2937
|
+
entryValue,
|
|
2938
|
+
);
|
|
2939
|
+
break;
|
|
2940
|
+
}
|
|
2941
|
+
|
|
2942
|
+
const nextId: ChangesetLocalId = brand(lastEntryId + 1);
|
|
2943
|
+
entry = getFirstIntersectingCrossFieldEntry(table, [
|
|
2944
|
+
target,
|
|
2945
|
+
revision,
|
|
2946
|
+
nextId,
|
|
2947
|
+
lastQueryId - nextId + 1,
|
|
2948
|
+
]);
|
|
2949
|
+
}
|
|
2950
|
+
|
|
2951
|
+
table.set([target, revision, id, count], value);
|
|
2952
|
+
}
|
|
2953
|
+
|
|
2954
|
+
function normalizeFieldId(fieldId: FieldId, nodeAliases: ChangeAtomIdMap<NodeId>): FieldId {
|
|
2955
|
+
return fieldId.nodeId !== undefined
|
|
2956
|
+
? { ...fieldId, nodeId: normalizeNodeId(fieldId.nodeId, nodeAliases) }
|
|
2957
|
+
: fieldId;
|
|
2958
|
+
}
|
|
2959
|
+
|
|
2960
|
+
/**
|
|
2961
|
+
* @returns The canonical form of nodeId, according to nodeAliases
|
|
2962
|
+
*/
|
|
2963
|
+
function normalizeNodeId(nodeId: NodeId, nodeAliases: ChangeAtomIdMap<NodeId>): NodeId {
|
|
2964
|
+
let currentId = nodeId;
|
|
2965
|
+
|
|
2966
|
+
// eslint-disable-next-line no-constant-condition
|
|
2967
|
+
while (true) {
|
|
2968
|
+
const dealiased = getFromChangeAtomIdMap(nodeAliases, currentId);
|
|
2969
|
+
if (dealiased === undefined) {
|
|
2970
|
+
return currentId;
|
|
2971
|
+
}
|
|
2972
|
+
|
|
2973
|
+
currentId = dealiased;
|
|
2974
|
+
}
|
|
2975
|
+
}
|
|
2976
|
+
|
|
2977
|
+
function hasConflicts(change: ModularChangeset): boolean {
|
|
2978
|
+
return (change.constraintViolationCount ?? 0) > 0;
|
|
2979
|
+
}
|
|
2980
|
+
|
|
2981
|
+
export function newCrossFieldKeyTable(): CrossFieldKeyTable {
|
|
2982
|
+
return newBTree();
|
|
2983
|
+
}
|
|
2984
|
+
|
|
2985
|
+
function newBTree<K extends readonly unknown[], V>(): TupleBTree<K, V> {
|
|
2986
|
+
return brand(new BTree<K, V>(undefined, compareTuples));
|
|
2987
|
+
}
|
|
2988
|
+
|
|
2989
|
+
// This assumes that the arrays are the same length.
|
|
2990
|
+
function compareTuples(arrayA: readonly unknown[], arrayB: readonly unknown[]): number {
|
|
2991
|
+
for (let i = 0; i < arrayA.length; i++) {
|
|
2992
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
2993
|
+
const a = arrayA[i] as any;
|
|
2994
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
2995
|
+
const b = arrayB[i] as any;
|
|
2996
|
+
if (a < b) {
|
|
2997
|
+
return -1;
|
|
2998
|
+
} else if (a > b) {
|
|
2999
|
+
return 1;
|
|
3000
|
+
}
|
|
3001
|
+
}
|
|
3002
|
+
|
|
3003
|
+
return 0;
|
|
3004
|
+
}
|
|
3005
|
+
|
|
3006
|
+
interface ModularChangesetContent {
|
|
3007
|
+
fieldChanges: FieldChangeMap;
|
|
3008
|
+
nodeChanges: ChangeAtomIdMap<NodeChangeset>;
|
|
3009
|
+
nodeToParent: ChangeAtomIdMap<FieldId>;
|
|
3010
|
+
nodeAliases: ChangeAtomIdMap<NodeId>;
|
|
3011
|
+
crossFieldKeys: CrossFieldKeyTable;
|
|
3012
|
+
}
|
|
3013
|
+
|
|
3014
|
+
function cloneNestedMap<K1, K2, V>(map: NestedMap<K1, K2, V>): NestedMap<K1, K2, V> {
|
|
3015
|
+
const cloned: NestedMap<K1, K2, V> = new Map();
|
|
3016
|
+
populateNestedMap(map, cloned, true);
|
|
3017
|
+
return cloned;
|
|
1943
3018
|
}
|