From a67a0d2c75fdc1313f2376a93e08c6fd095c2067 Mon Sep 17 00:00:00 2001 From: alex-pardes <105307164+alex-pardes@users.noreply.github.com> Date: Wed, 19 Jul 2023 15:22:03 -0700 Subject: [PATCH] Tree: Include lineage as part of cell IDs (#16434) ## Description This change creates a new type, `CellId`, which is `ChangeAtomId` with optional lineage. Sequence field marks targeting existing cells now use `CellId` instead of `ChangeAtomId` to identify empty cells, and lineage is stored there instead of directly on the mark. Lineage is still stored directly on new attach marks. --------- Co-authored-by: yann-achard-MS <97201204+yann-achard-MS@users.noreply.github.com> --- api-report/tree2.api.md | 1 - .../defaultChangeFamily.ts | 42 --- .../dds/tree2/src/feature-libraries/index.ts | 1 + .../sequence-field/compose.ts | 57 ++-- .../sequence-field/format.ts | 107 +++---- .../feature-libraries/sequence-field/index.ts | 3 +- .../sequence-field/invert.ts | 26 +- .../sequence-field/rebase.ts | 86 +++--- .../sequence-field/sequenceFieldEditor.ts | 34 +-- .../feature-libraries/sequence-field/utils.ts | 143 +++++---- .../sequence-field/compose.spec.ts | 202 +++++++------ .../sequence-field/invert.spec.ts | 43 ++- .../sequence-field/markListFactory.spec.ts | 10 +- .../sequence-field/rebase.spec.ts | 286 ++++++++++++------ .../sequenceChangeRebaser.spec.ts | 33 +- .../sequenceFieldEncoder.spec.ts | 5 +- .../sequenceFieldToDelta.spec.ts | 16 +- .../sequence-field/testEdits.ts | 93 ++---- 18 files changed, 654 insertions(+), 534 deletions(-) diff --git a/api-report/tree2.api.md b/api-report/tree2.api.md index f84d583ce90d..9282d8f91c13 100644 --- a/api-report/tree2.api.md +++ b/api-report/tree2.api.md @@ -1805,7 +1805,6 @@ export interface SequenceFieldEditBuilder { delete(index: number, count: number): void; insert(index: number, newContent: ITreeCursor | readonly ITreeCursor[]): void; move(sourceIndex: number, count: number, destIndex: number): void; - revive(index: number, count: number, detachedBy: RevisionTag, detachId: ChangesetLocalId, reviver: NodeReviver, isIntention?: true): void; } // @alpha diff --git a/experimental/dds/tree2/src/feature-libraries/default-field-kinds/defaultChangeFamily.ts b/experimental/dds/tree2/src/feature-libraries/default-field-kinds/defaultChangeFamily.ts index 2128877d1438..6fbddb0a2d78 100644 --- a/experimental/dds/tree2/src/feature-libraries/default-field-kinds/defaultChangeFamily.ts +++ b/experimental/dds/tree2/src/feature-libraries/default-field-kinds/defaultChangeFamily.ts @@ -11,7 +11,6 @@ import { Delta, UpPath, ITreeCursor, - RevisionTag, ChangeFamilyEditor, FieldUpPath, } from "../../core"; @@ -21,8 +20,6 @@ import { ModularEditBuilder, FieldChangeset, ModularChangeset, - NodeReviver, - ChangesetLocalId, } from "../modular-schema"; import { fieldKinds, optional, sequence, value as valueFieldKind } from "./defaultFieldKinds"; @@ -243,26 +240,6 @@ export class DefaultEditBuilder implements ChangeFamilyEditor, IDefaultEditBuild moveId, ); }, - revive: ( - index: number, - count: number, - detachedBy: RevisionTag, - detachId: ChangesetLocalId, - reviver: NodeReviver, - isIntention?: true, - ): void => { - const change: FieldChangeset = brand( - sequence.changeHandler.editor.revive( - index, - count, - detachedBy, - detachId, - reviver, - isIntention, - ), - ); - this.modularBuilder.submitChange(field, sequence.identifier, change); - }, }; } } @@ -315,23 +292,4 @@ export interface SequenceFieldEditBuilder { * @param destIndex - the index the elements are moved to, interpreted after removing the moving elements. */ move(sourceIndex: number, count: number, destIndex: number): void; - - /** - * Revives a contiguous range of deleted nodes. - * @param index - The index at which to revive the node (this will become the index of the first revived node). - * @param count - The number of nodes to revive. - * @param detachedBy - The revision of the edit that deleted the nodes. - * @param reviver - The NodeReviver used to retrieve repair data. - * @param detachIndex - The index of the first node to revive in the input context of edit `detachedBy`. - * @param isIntention - If true, the node will be revived even if edit `detachedBy` did not ultimately - * delete them. If false, only those nodes that were deleted by `detachedBy` (and not revived) will be revived. - */ - revive( - index: number, - count: number, - detachedBy: RevisionTag, - detachId: ChangesetLocalId, - reviver: NodeReviver, - isIntention?: true, - ): void; } diff --git a/experimental/dds/tree2/src/feature-libraries/index.ts b/experimental/dds/tree2/src/feature-libraries/index.ts index 0ceed44cb14d..79a320918a12 100644 --- a/experimental/dds/tree2/src/feature-libraries/index.ts +++ b/experimental/dds/tree2/src/feature-libraries/index.ts @@ -142,6 +142,7 @@ export { NodeExistsConstraint, NodeExistenceState, BrandedFieldKind, + ChangeAtomId, } from "./modular-schema"; export { diff --git a/experimental/dds/tree2/src/feature-libraries/sequence-field/compose.ts b/experimental/dds/tree2/src/feature-libraries/sequence-field/compose.ts index a9ef2d516974..6b6e108cf50b 100644 --- a/experimental/dds/tree2/src/feature-libraries/sequence-field/compose.ts +++ b/experimental/dds/tree2/src/feature-libraries/sequence-field/compose.ts @@ -22,6 +22,7 @@ import { Modify, MoveId, NoopMarkType, + CellId, } from "./format"; import { MarkListFactory } from "./markListFactory"; import { MarkQueue } from "./markQueue"; @@ -192,10 +193,7 @@ function composeMarks( // Modify and Placeholder marks must be muted because the node they target has been deleted. // Detach marks must be muted because the cell is empty. if (newMark.type === "Modify" || newMark.type === "Placeholder" || isDetachMark(newMark)) { - assert( - newMark.detachEvent !== undefined, - "Invalid node-targeting mark after transient", - ); + assert(newMark.cellId !== undefined, "Invalid node-targeting mark after transient"); return baseMark; } if (newMark.type === "ReturnTo") { @@ -363,7 +361,7 @@ function createModifyMark( assert(length === 1, 0x692 /* A mark with a node change must have length one */); const mark: Modify = { type: "Modify", changes: nodeChange }; if (cellId !== undefined) { - mark.detachEvent = cellId; + mark.cellId = cellId; } return mark; } @@ -585,7 +583,7 @@ export class ComposeQueue { isExistingCellMark(baseMark) && areInputCellsEmpty(baseMark), 0x696 /* Mark with empty output must either be a detach or also have input empty */, ); - baseCellId = baseMark.detachEvent; + baseCellId = baseMark.cellId; } const cmp = compareCellPositions( baseCellId, @@ -805,11 +803,11 @@ function areInverseMovesAtIntermediateLocation( 0x6d0 /* baseMark should be an attach and newMark should be a detach */, ); - if (baseMark.type === "ReturnTo" && baseMark.detachEvent?.revision === newIntention) { + if (baseMark.type === "ReturnTo" && baseMark.cellId?.revision === newIntention) { return true; } - if (newMark.type === "ReturnFrom" && newMark.detachEvent?.revision === baseIntention) { + if (newMark.type === "ReturnFrom" && newMark.cellId?.revision === baseIntention) { return true; } @@ -826,14 +824,15 @@ function areInverseMovesAtIntermediateLocation( * are before the first cell of `newMark`. */ function compareCellPositions( - baseCellId: ChangeAtomId, + baseCellId: CellId, baseMark: Mark, newMark: EmptyInputCellMark, newIntention: RevisionTag | undefined, cancelledInserts: Set, ): number { const newCellId = getCellId(newMark, newIntention); - if (newCellId !== undefined && baseCellId.revision === newCellId.revision) { + assert(newCellId !== undefined, "Should have cell ID"); + if (baseCellId.revision === newCellId.revision) { if (isNewAttach(newMark)) { // There is some change foo that is being cancelled out as part of a rebase sandwich. // The marks that make up this change (and its inverse) may be broken up differently between the base @@ -859,31 +858,27 @@ function compareCellPositions( } } - if (newCellId !== undefined) { - const offset = getOffsetInCellRange( - baseMark.lineage, - newCellId.revision, - newCellId.localId, - getMarkLength(newMark), - ); - if (offset !== undefined) { - return offset > 0 ? offset : -Infinity; - } + const offsetInBase = getOffsetInCellRange( + baseCellId.lineage, + newCellId.revision, + newCellId.localId, + getMarkLength(newMark), + ); + if (offsetInBase !== undefined) { + return offsetInBase > 0 ? offsetInBase : -Infinity; } - { - const offset = getOffsetInCellRange( - newMark.lineage, - baseCellId.revision, - baseCellId.localId, - getMarkLength(baseMark), - ); - if (offset !== undefined) { - return offset > 0 ? -offset : Infinity; - } + const offsetInNew = getOffsetInCellRange( + newCellId.lineage, + baseCellId.revision, + baseCellId.localId, + getMarkLength(baseMark), + ); + if (offsetInNew !== undefined) { + return offsetInNew > 0 ? -offsetInNew : Infinity; } - const cmp = compareLineages(baseMark.lineage, newMark.lineage); + const cmp = compareLineages(baseCellId.lineage, newCellId.lineage); if (cmp !== 0) { return Math.sign(cmp) * Infinity; } diff --git a/experimental/dds/tree2/src/feature-libraries/sequence-field/format.ts b/experimental/dds/tree2/src/feature-libraries/sequence-field/format.ts index 3ffaa8703206..81efa81b61fe 100644 --- a/experimental/dds/tree2/src/feature-libraries/sequence-field/format.ts +++ b/experimental/dds/tree2/src/feature-libraries/sequence-field/format.ts @@ -74,6 +74,7 @@ export enum Effects { * Note that `LineageEvent`s with the same revision are not necessarily referring to the same detach. * `LineageEvent`s for a given revision can only be meaningfully compared if it is known that they must refer to the * same detach. + * @alpha */ export interface LineageEvent { readonly revision: RevisionTag; @@ -93,13 +94,32 @@ export const LineageEvent = Type.Object( noAdditionalProps, ); +/** + * @alpha + */ +export interface HasLineage { + /** + * History of detaches adjacent to the cells described by this `ChangeAtomId`. + */ + lineage?: LineageEvent[]; +} + +export const HasLineage = Type.Object({ lineage: Type.Optional(Type.Array(LineageEvent)) }); + +/** + * @alpha + */ +export interface CellId extends ChangeAtomId, HasLineage {} + +export const CellId = Type.Composite([EncodedChangeAtomId, HasLineage]); + export interface HasChanges { changes?: TNodeChange; } export const HasChanges = (tNodeChange: TNodeChange) => Type.Object({ changes: Type.Optional(tNodeChange) }); -export interface HasPlaceFields { +export interface HasPlaceFields extends HasLineage { /** * Describes which kinds of concurrent slice operations should affect the target place. * @@ -116,32 +136,20 @@ export interface HasPlaceFields { heed?: Effects | [Effects, Effects]; /** - * Record of relevant information about changes this mark has been rebased over. - * Events are stored in the order in which they were rebased over. + * Omit if `Tiebreak.Right` for terseness. */ - lineage?: LineageEvent[]; + tiebreak?: Tiebreak; } const EffectsSchema = Type.Enum(Effects); -export const HasPlaceFields = Type.Object({ - heed: Type.Optional(Type.Union([EffectsSchema, Type.Tuple([EffectsSchema, EffectsSchema])])), - lineage: Type.Optional(Type.Array(LineageEvent)), -}); - -export interface HasReattachFields extends HasPlaceFields { - /** - * The revision this mark is inverting a detach from. - * If defined this mark is a revert-only inverse, - * meaning that it will only reattach nodes if those nodes were last detached by `inverseOf`. - * If `inverseOf` is undefined, this mark will reattach nodes regardless of when they were last detached. - */ - inverseOf?: RevisionTag; -} -export const HasReattachFields = Type.Composite([ - HasPlaceFields, +export const HasPlaceFields = Type.Composite([ Type.Object({ - inverseOf: Type.Optional(RevisionTagSchema), + heed: Type.Optional( + Type.Union([EffectsSchema, Type.Tuple([EffectsSchema, EffectsSchema])]), + ), + tiebreak: Type.Optional(Type.Enum(Tiebreak)), }), + HasLineage, ]); /** @@ -149,21 +157,31 @@ export const HasReattachFields = Type.Composite([ */ export interface CellTargetingMark { /** - * Describes the detach which last emptied target cells. + * Describes the detach which last emptied the target cells, + * or the attach which allocated the cells if the cells have never been filled. * Undefined if the target cells are not empty in this mark's input context. */ - detachEvent?: ChangeAtomId; + cellId?: CellId; +} +export const CellTargetingMark = Type.Object({ + cellId: Type.Optional(CellId), +}); +export interface HasReattachFields extends CellTargetingMark { /** - * Lineage of detaches adjacent to the cells since `detachEvent`. - * Should be empty if the cells are full in this mark's input context. + * The revision this mark is inverting a detach from. + * If defined this mark is a revert-only inverse, + * meaning that it will only reattach nodes if those nodes were last detached by `inverseOf`. + * If `inverseOf` is undefined, this mark will reattach nodes regardless of when they were last detached. */ - lineage?: LineageEvent[]; + inverseOf?: RevisionTag; } -export const CellTargetingMark = Type.Object({ - detachEvent: Type.Optional(EncodedChangeAtomId), - lineage: Type.Optional(Type.Array(LineageEvent)), -}); +export const HasReattachFields = Type.Composite([ + Type.Object({ + inverseOf: Type.Optional(RevisionTagSchema), + }), + CellTargetingMark, +]); export interface NoopMark extends CellTargetingMark { /** @@ -183,24 +201,11 @@ export const NoopMark = Type.Composite( ); export interface DetachedCellMark extends CellTargetingMark { - detachEvent: ChangeAtomId; + cellId: CellId; } export const DetachedCellMark = Type.Composite([ CellTargetingMark, - Type.Object({ detachEvent: EncodedChangeAtomId }), -]); - -export interface HasTiebreakPolicy extends HasPlaceFields { - /** - * Omit if `Tiebreak.Right` for terseness. - */ - tiebreak?: Tiebreak; -} -export const HasTiebreakPolicy = Type.Composite([ - HasPlaceFields, - Type.Object({ - tiebreak: Type.Optional(Type.Enum(Tiebreak)), - }), + Type.Object({ cellId: CellId }), ]); export enum RangeType { @@ -229,7 +234,7 @@ export type CanBeTransient = Partial; export const CanBeTransient = Type.Partial(Transient); export interface Insert - extends HasTiebreakPolicy, + extends HasPlaceFields, HasRevisionTag, CanBeTransient, HasChanges { @@ -245,9 +250,8 @@ export interface Insert export const Insert = (tNodeChange: Schema) => Type.Composite( [ - HasTiebreakPolicy, + HasPlaceFields, HasRevisionTag, - CanBeTransient, HasChanges(tNodeChange), Type.Object({ type: Type.Literal("Insert"), @@ -337,8 +341,7 @@ export interface Revive extends HasReattachFields, HasRevisionTag, CanBeTransient, - HasChanges, - CellTargetingMark { + HasChanges { type: "Revive"; content: ITreeCursorSynchronous[]; count: NodeCount; @@ -350,7 +353,6 @@ export const Revive = (tNodeChange: Schema) => HasRevisionTag, CanBeTransient, HasChanges(tNodeChange), - CellTargetingMark, Type.Object({ type: Type.Literal("Revive"), content: Type.Array(ProtoNode), @@ -360,7 +362,7 @@ export const Revive = (tNodeChange: Schema) => noAdditionalProps, ); -export interface ReturnTo extends HasReattachFields, HasRevisionTag, HasMoveId, CellTargetingMark { +export interface ReturnTo extends HasReattachFields, HasRevisionTag, HasMoveId { type: "ReturnTo"; count: NodeCount; @@ -375,7 +377,6 @@ export const ReturnTo = Type.Composite( HasReattachFields, HasRevisionTag, HasMoveId, - CellTargetingMark, Type.Object({ type: Type.Literal("ReturnTo"), count: NodeCount, diff --git a/experimental/dds/tree2/src/feature-libraries/sequence-field/index.ts b/experimental/dds/tree2/src/feature-libraries/sequence-field/index.ts index e7c40f25df7b..cdd7e3bb54fb 100644 --- a/experimental/dds/tree2/src/feature-libraries/sequence-field/index.ts +++ b/experimental/dds/tree2/src/feature-libraries/sequence-field/index.ts @@ -14,7 +14,6 @@ export { HasMoveId, HasPlaceFields, HasRevisionTag, - HasTiebreakPolicy, Insert, Mark, MarkList, @@ -35,6 +34,8 @@ export { LineageEvent, HasReattachFields, CellSpanningMark, + CellId, + HasLineage, } from "./format"; export { SequenceFieldChangeHandler, diff --git a/experimental/dds/tree2/src/feature-libraries/sequence-field/invert.ts b/experimental/dds/tree2/src/feature-libraries/sequence-field/invert.ts index bbedec286659..f3511eb36b75 100644 --- a/experimental/dds/tree2/src/feature-libraries/sequence-field/invert.ts +++ b/experimental/dds/tree2/src/feature-libraries/sequence-field/invert.ts @@ -112,7 +112,7 @@ function invertMark( withNodeChange( { type: "Revive", - detachEvent: { + cellId: { revision: mark.transientDetach.revision ?? revision, localId: mark.transientDetach.localId, }, @@ -137,11 +137,11 @@ function invertMark( } case "Delete": { assert(revision !== undefined, 0x5a1 /* Unable to revert to undefined revision */); - if (mark.detachEvent === undefined) { + if (mark.cellId === undefined) { const inverse = withNodeChange( { type: "Revive", - detachEvent: { revision: mark.revision ?? revision, localId: mark.id }, + cellId: { revision: mark.revision ?? revision, localId: mark.id }, content: reviver(revision, inputIndex, mark.count), count: mark.count, inverseOf: mark.revision ?? revision, @@ -157,7 +157,7 @@ function invertMark( case "Revive": { if (!isReattachConflicted(mark)) { assert( - mark.detachEvent !== undefined, + mark.cellId !== undefined, 0x707 /* Active reattach should have a detach event */, ); if (mark.transientDetach !== undefined) { @@ -166,7 +166,7 @@ function invertMark( withNodeChange( { type: "Revive", - detachEvent: { + cellId: { revision: mark.transientDetach.revision ?? revision, localId: mark.transientDetach.localId, }, @@ -175,7 +175,7 @@ function invertMark( inverseOf: mark.revision ?? revision, transientDetach: { revision: mark.revision ?? revision, - localId: mark.detachEvent.localId, + localId: mark.cellId.localId, }, }, invertNodeChange(mark.changes, inputIndex, invertChild), @@ -186,7 +186,7 @@ function invertMark( { type: "Delete", count: mark.count, - id: mark.detachEvent.localId, + id: mark.cellId.localId, }, invertNodeChange(mark.changes, inputIndex, invertChild), ); @@ -213,12 +213,12 @@ function invertMark( mark.changes, inputIndex, invertChild, - mark.detachEvent, + mark.cellId, ), ]; } case "Modify": { - if (mark.detachEvent === undefined) { + if (mark.cellId === undefined) { return [ { type: "Modify", @@ -258,7 +258,7 @@ function invertMark( type: "ReturnTo", id: mark.id, count: mark.count, - detachEvent: { + cellId: { revision: mark.revision ?? revision ?? fail("Revision must be defined"), localId: mark.id, }, @@ -268,12 +268,12 @@ function invertMark( case "MoveIn": case "ReturnTo": { if (mark.isSrcConflicted) { - return mark.type === "ReturnTo" && mark.detachEvent === undefined + return mark.type === "ReturnTo" && mark.cellId === undefined ? [{ count: mark.count }] : []; } if (mark.type === "ReturnTo") { - if (mark.detachEvent === undefined) { + if (mark.cellId === undefined) { // The nodes were already attached, so the mark did not affect them. return [{ count: mark.count }]; } else if (isConflictedReattach(mark)) { @@ -355,7 +355,7 @@ function invertModifyOrSkip( assert(length === 1, 0x66c /* A modify mark must have length equal to one */); const modify: Modify = { type: "Modify", changes: inverter(changes, index) }; if (detachEvent !== undefined) { - modify.detachEvent = detachEvent; + modify.cellId = detachEvent; } return modify; } diff --git a/experimental/dds/tree2/src/feature-libraries/sequence-field/rebase.ts b/experimental/dds/tree2/src/feature-libraries/sequence-field/rebase.ts index 304b54b6e391..bda684d23f4c 100644 --- a/experimental/dds/tree2/src/feature-libraries/sequence-field/rebase.ts +++ b/experimental/dds/tree2/src/feature-libraries/sequence-field/rebase.ts @@ -45,6 +45,7 @@ import { Modify, EmptyInputCellMark, NoopMarkType, + HasLineage, } from "./format"; import { MarkListFactory } from "./markListFactory"; import { ComposeQueue } from "./compose"; @@ -401,11 +402,11 @@ function rebaseMark( ); } } - rebasedMark = withoutDetachEvent(rebasedMark); + rebasedMark = withoutCellId(rebasedMark); } else if ( nodeExistenceState === NodeExistenceState.Alive && (rebasedMark.type === "MoveOut" || rebasedMark.type === "ReturnFrom") && - rebasedMark.detachEvent === undefined + rebasedMark.cellId === undefined ) { setPairedMarkStatus( moveEffects, @@ -547,14 +548,13 @@ function makeDetachedMark( return { count: 0 }; } - assert(mark.detachEvent === undefined, 0x69f /* Expected mark to be attached */); - return { ...mark, detachEvent: { revision: detachIntention, localId: detachId } }; + assert(mark.cellId === undefined, 0x69f /* Expected mark to be attached */); + return { ...mark, cellId: { revision: detachIntention, localId: detachId } }; } -function withoutDetachEvent>(mark: TMark): TMark { +function withoutCellId>(mark: TMark): TMark { const newMark = { ...mark }; - delete newMark.detachEvent; - delete newMark.lineage; + delete newMark.cellId; return newMark; } @@ -684,10 +684,11 @@ function handleLineage( // TODO: Handle cases where the base changeset is a composition of multiple revisions. // TODO: Don't remove the lineage event in cases where the event isn't actually inverted by the base changeset, // e.g., if the inverse of the lineage event is muted after rebasing. - tryRemoveLineageEvents(rebasedMark, baseIntention); + const lineageHolder = getLineageHolder(rebasedMark); + tryRemoveLineageEvents(lineageHolder, baseIntention); for (const entry of lineageEntries) { - addLineageEntry(rebasedMark, baseIntention, entry.id, entry.count, entry.count); + addLineageEntry(lineageHolder, baseIntention, entry.id, entry.count, entry.count); } lineageRecipients.push(rebasedMark); @@ -700,33 +701,33 @@ function addLineageToRecipients( count: number, ) { for (const mark of recipients) { - addLineageEntry(mark, revision, id, count, 0); + addLineageEntry(getLineageHolder(mark), revision, id, count, 0); } } function addLineageEntry( - mark: Mark, + lineageHolder: HasLineage, revision: RevisionTag, id: ChangesetLocalId, count: number, offset: number, ) { - if (mark.lineage === undefined) { - mark.lineage = []; + if (lineageHolder.lineage === undefined) { + lineageHolder.lineage = []; } - if (mark.lineage.length > 0) { - const lastEntry = mark.lineage[mark.lineage.length - 1]; + if (lineageHolder.lineage.length > 0) { + const lastEntry = lineageHolder.lineage[lineageHolder.lineage.length - 1]; if (lastEntry.revision === revision && (lastEntry.id as number) + lastEntry.count === id) { if (lastEntry.offset === lastEntry.count) { - mark.lineage[mark.lineage.length - 1] = { + lineageHolder.lineage[lineageHolder.lineage.length - 1] = { ...lastEntry, count: lastEntry.count + count, offset: lastEntry.offset + offset, }; return; } else if (offset === 0) { - mark.lineage[mark.lineage.length - 1] = { + lineageHolder.lineage[lineageHolder.lineage.length - 1] = { ...lastEntry, count: lastEntry.count + count, }; @@ -735,18 +736,29 @@ function addLineageEntry( } } - mark.lineage.push({ revision, id, count, offset }); + lineageHolder.lineage.push({ revision, id, count, offset }); } -function tryRemoveLineageEvents(mark: Mark, revisionToRemove: RevisionTag) { - if (mark.lineage === undefined) { +function tryRemoveLineageEvents(lineageHolder: HasLineage, revisionToRemove: RevisionTag) { + if (lineageHolder.lineage === undefined) { return; } - mark.lineage = mark.lineage.filter((event) => event.revision !== revisionToRemove); - if (mark.lineage.length === 0) { - delete mark.lineage; + lineageHolder.lineage = lineageHolder.lineage.filter( + (event) => event.revision !== revisionToRemove, + ); + if (lineageHolder.lineage.length === 0) { + delete lineageHolder.lineage; + } +} + +function getLineageHolder(mark: Mark): HasLineage { + if (isNewAttach(mark)) { + return mark; } + + assert(mark.cellId !== undefined, "Attached cells cannot have lineage"); + return mark.cellId; } /** @@ -775,7 +787,7 @@ function compareCellPositions( if (newId !== undefined) { const offset = getOffsetInCellRange( - baseMark.lineage, + baseId.lineage, newId.revision, newId.localId, newLength, @@ -783,21 +795,23 @@ function compareCellPositions( if (offset !== undefined) { return offset > 0 ? offset : -Infinity; } - } - const newOffset = getOffsetInCellRange( - newMark.lineage, - baseId.revision, - baseId.localId, - baseLength, - ); - if (newOffset !== undefined) { - return newOffset > 0 ? -newOffset : Infinity; + const newOffset = getOffsetInCellRange( + newId.lineage, + baseId.revision, + baseId.localId, + baseLength, + ); + if (newOffset !== undefined) { + return newOffset > 0 ? -newOffset : Infinity; + } } - const cmp = compareLineages(baseMark.lineage, newMark.lineage); - if (cmp !== 0) { - return Math.sign(cmp) * Infinity; + if (newId !== undefined) { + const cmp = compareLineages(baseId.lineage, newId.lineage); + if (cmp !== 0) { + return Math.sign(cmp) * Infinity; + } } if (isNewAttach(newMark)) { diff --git a/experimental/dds/tree2/src/feature-libraries/sequence-field/sequenceFieldEditor.ts b/experimental/dds/tree2/src/feature-libraries/sequence-field/sequenceFieldEditor.ts index 0ccba525a706..8310dae3758f 100644 --- a/experimental/dds/tree2/src/feature-libraries/sequence-field/sequenceFieldEditor.ts +++ b/experimental/dds/tree2/src/feature-libraries/sequence-field/sequenceFieldEditor.ts @@ -3,14 +3,15 @@ * Licensed under the MIT License. */ +import { assert } from "@fluidframework/common-utils"; import { jsonableTreeFromCursor } from "../treeTextCursor"; -import { ITreeCursor, RevisionTag } from "../../core"; +import { ITreeCursor } from "../../core"; import { ChangesetLocalId, FieldEditor, NodeReviver } from "../modular-schema"; import { brand } from "../../util"; import { + CellId, Changeset, Insert, - LineageEvent, Mark, MoveId, NodeChangeType, @@ -26,8 +27,7 @@ export interface SequenceFieldEditor extends FieldEditor { revive( index: number, count: number, - detachedBy: RevisionTag, - detachId: ChangesetLocalId, + detachEvent: CellId, reviver: NodeReviver, isIntention?: true, ): Changeset; @@ -49,8 +49,7 @@ export interface SequenceFieldEditor extends FieldEditor { sourceIndex: number, count: number, destIndex: number, - detachedBy: RevisionTag, - detachId: ChangesetLocalId, + detachEvent: CellId, ): Changeset; } @@ -73,23 +72,23 @@ export const sequenceFieldEditor = { }, delete: (index: number, count: number, id: ChangesetLocalId): Changeset => count === 0 ? [] : markAtIndex(index, { type: "Delete", count, id }), + revive: ( index: number, count: number, - detachedBy: RevisionTag, - detachId: ChangesetLocalId, + detachEvent: CellId, reviver: NodeReviver, isIntention: boolean = false, ): Changeset => { - const detachEvent = { revision: detachedBy, localId: detachId }; + assert(detachEvent.revision !== undefined, "Detach event must have a revision"); const mark: Reattach = { type: "Revive", - content: reviver(detachedBy, detachId, count), + content: reviver(detachEvent.revision, detachEvent.localId, count), count, - detachEvent, + cellId: detachEvent, }; if (!isIntention) { - mark.inverseOf = detachedBy; + mark.inverseOf = detachEvent.revision; } return count === 0 ? [] : markAtIndex(index, mark); }, @@ -119,15 +118,12 @@ export const sequenceFieldEditor = { sourceIndex: number, count: number, destIndex: number, - detachedBy: RevisionTag, - detachId: ChangesetLocalId, - lineage?: LineageEvent[], + detachEvent: CellId, ): Changeset { if (count === 0) { return []; } - const detachEvent = { revision: detachedBy, localId: detachId }; const id = brand(0); const returnFrom: ReturnFrom = { type: "ReturnFrom", @@ -139,13 +135,9 @@ export const sequenceFieldEditor = { type: "ReturnTo", id, count, - detachEvent, + cellId: detachEvent, }; - if (lineage !== undefined) { - returnTo.lineage = lineage; - } - const factory = new MarkListFactory(); if (sourceIndex < destIndex) { factory.pushOffset(sourceIndex); diff --git a/experimental/dds/tree2/src/feature-libraries/sequence-field/utils.ts b/experimental/dds/tree2/src/feature-libraries/sequence-field/utils.ts index 8be5475972cb..cdfe330c38d9 100644 --- a/experimental/dds/tree2/src/feature-libraries/sequence-field/utils.ts +++ b/experimental/dds/tree2/src/feature-libraries/sequence-field/utils.ts @@ -20,7 +20,6 @@ import { Detach, HasChanges, HasRevisionTag, - HasTiebreakPolicy, Insert, LineageEvent, Mark, @@ -42,6 +41,9 @@ import { Transient, DetachedCellMark, CellTargetingMark, + CellId, + HasPlaceFields, + HasReattachFields, } from "./format"; import { MarkListFactory } from "./markListFactory"; import { isMoveMark, MoveEffectTable } from "./moveEffectTable"; @@ -91,8 +93,8 @@ export function isConflictedReattach( // TODO: Name is misleading export function isReattachConflicted(mark: Reattach): boolean { return ( - mark.detachEvent === undefined || - (mark.inverseOf !== undefined && mark.inverseOf !== mark.detachEvent.revision) + mark.cellId === undefined || + (mark.inverseOf !== undefined && mark.inverseOf !== mark.cellId.revision) ); } @@ -100,23 +102,25 @@ export function isReturnMuted(mark: ReturnTo): boolean { return mark.isSrcConflicted ?? isReattachConflicted(mark); } -export function areEqualDetachEvents(a: ChangeAtomId, b: ChangeAtomId): boolean { - return a.localId === b.localId && a.revision === b.revision; +export function areEqualCellIds(a: CellId | undefined, b: CellId | undefined): boolean { + if (a === undefined || b === undefined) { + return a === b; + } + return ( + a.localId === b.localId && a.revision === b.revision && areSameLineage(a.lineage, b.lineage) + ); } export function getCellId( mark: Mark, revision: RevisionTag | undefined, -): ChangeAtomId | undefined { +): CellId | undefined { if (isNewAttach(mark)) { const rev = mark.revision ?? revision; - if (rev !== undefined) { - return { revision: rev, localId: mark.id }; - } - return undefined; + return { revision: rev, localId: mark.id, lineage: mark.lineage }; } - return mark.detachEvent; + return mark.cellId; } export function cloneMark, TNodeChange>(mark: TMark): TMark { @@ -124,27 +128,52 @@ export function cloneMark, TNodeChange>(mark: TM if (clone.type === "Insert" || clone.type === "Revive") { clone.content = [...clone.content]; } - if (isAttach(clone) && clone.lineage !== undefined) { - clone.lineage = [...clone.lineage]; + if (isNewAttach(clone)) { + if (clone.lineage !== undefined) { + clone.lineage = [...clone.lineage]; + } + } else if (clone.cellId !== undefined) { + clone.cellId = { ...clone.cellId }; + if (clone.cellId.lineage !== undefined) { + clone.cellId.lineage = [...clone.cellId.lineage]; + } } return clone; } /** - * @returns `true` iff `lhs` and `rhs`'s `HasTiebreakPolicy` fields are structurally equal. + * @returns `true` iff `lhs` and `rhs`'s `HasPlaceFields` fields are structurally equal. */ export function isEqualPlace( - lhs: Readonly, - rhs: Readonly, + lhs: Readonly, + rhs: Readonly, ): boolean { return ( lhs.heed === rhs.heed && lhs.tiebreak === rhs.tiebreak && - areSameLineage(lhs.lineage ?? [], rhs.lineage ?? []) + areSameLineage(lhs.lineage, rhs.lineage) ); } -function areSameLineage(lineage1: LineageEvent[], lineage2: LineageEvent[]): boolean { +function haveEqualReattachFields( + lhs: Readonly, + rhs: Readonly, +): boolean { + return lhs.inverseOf === rhs.inverseOf && areEqualCellIds(lhs.cellId, rhs.cellId); +} + +function areSameLineage( + lineage1: LineageEvent[] | undefined, + lineage2: LineageEvent[] | undefined, +): boolean { + if (lineage1 === undefined && lineage2 === undefined) { + return true; + } + + if (lineage1 === undefined || lineage2 === undefined) { + return false; + } + if (lineage1.length !== lineage2.length) { return false; } @@ -219,7 +248,7 @@ export function areInputCellsEmpty(mark: Mark): mark is EmptyInputCellMark return true; } - return mark.detachEvent !== undefined; + return mark.cellId !== undefined; } export function areOutputCellsEmpty(mark: Mark): boolean { @@ -236,17 +265,17 @@ export function areOutputCellsEmpty(mark: Mark): boolean { return true; case "Modify": case "Placeholder": - return mark.detachEvent !== undefined; + return mark.cellId !== undefined; case "ReturnFrom": - return mark.detachEvent !== undefined || !mark.isDstConflicted; + return mark.cellId !== undefined || !mark.isDstConflicted; case "ReturnTo": return ( - mark.detachEvent !== undefined && + mark.cellId !== undefined && ((mark.isSrcConflicted ?? false) || isReattachConflicted(mark)) ); case "Revive": return ( - (mark.detachEvent !== undefined && isReattachConflicted(mark)) || + (mark.cellId !== undefined && isReattachConflicted(mark)) || mark.transientDetach !== undefined ); default: @@ -369,13 +398,13 @@ export function tryExtendMark(lhs: Mark, rhs: Readonly>): boolean if (isExistingCellMark(lhs)) { assert(isExistingCellMark(rhs), 0x6a6 /* Should be existing cell mark */); - if (lhs.detachEvent?.revision !== rhs.detachEvent?.revision) { + if (lhs.cellId?.revision !== rhs.cellId?.revision) { return false; } if ( - lhs.detachEvent !== undefined && - (lhs.detachEvent.localId as number) + getMarkLength(lhs) !== rhs.detachEvent?.localId + lhs.cellId !== undefined && + (lhs.cellId.localId as number) + getMarkLength(lhs) !== rhs.cellId?.localId ) { return false; } @@ -398,9 +427,8 @@ export function tryExtendMark(lhs: Mark, rhs: Readonly>): boolean } break; } - case "MoveIn": - case "ReturnTo": { - const lhsMoveIn = lhs as MoveIn | ReturnTo; + case "MoveIn": { + const lhsMoveIn = lhs as MoveIn; if ( isEqualPlace(lhsMoveIn, rhs) && lhsMoveIn.isSrcConflicted === rhs.isSrcConflicted && @@ -411,6 +439,17 @@ export function tryExtendMark(lhs: Mark, rhs: Readonly>): boolean } break; } + case "ReturnTo": { + const lhsReturnTo = lhs as ReturnTo; + if ( + haveEqualReattachFields(lhsReturnTo, rhs) && + lhsReturnTo.isSrcConflicted === rhs.isSrcConflicted && + (lhsReturnTo.id as number) + lhsReturnTo.count === rhs.id + ) { + lhsReturnTo.count += rhs.count; + return true; + } + } case "Delete": { const lhsDetach = lhs as Detach; if ((lhsDetach.id as number) + lhsDetach.count === rhs.id) { @@ -461,8 +500,8 @@ export function tryExtendMark(lhs: Mark, rhs: Readonly>): boolean */ export class DetachedNodeTracker { // Maps the index for a node to its last characterization as a reattached node. - private nodes: Map = new Map(); - private readonly equivalences: { old: ChangeAtomId; new: ChangeAtomId }[] = []; + private nodes: Map = new Map(); + private readonly equivalences: { old: CellId; new: CellId }[] = []; public constructor() {} @@ -477,7 +516,7 @@ export class DetachedNodeTracker { const inputLength: number = getInputLength(mark); if (markEmptiesCells(mark)) { assert(isDetachMark(mark), 0x70d /* Only detach marks should empty cells */); - const newNodes: Map = new Map(); + const newNodes: Map = new Map(); const after = index + inputLength; for (const [k, v] of this.nodes) { if (k >= index) { @@ -509,7 +548,7 @@ export class DetachedNodeTracker { for (const mark of change.change) { const inputLength: number = getInputLength(mark); if (isActiveReattach(mark)) { - const newNodes: Map = new Map(); + const newNodes: Map = new Map(); for (const [k, v] of this.nodes) { if (k >= index) { newNodes.set(k + inputLength, v); @@ -517,7 +556,7 @@ export class DetachedNodeTracker { newNodes.set(k, v); } } - const detachEvent = mark.detachEvent ?? fail("Unable to track detached nodes"); + const detachEvent = mark.cellId ?? fail("Unable to track detached nodes"); for (let i = 0; i < mark.count; ++i) { newNodes.set(index + i, { revision: detachEvent.revision, @@ -541,11 +580,11 @@ export class DetachedNodeTracker { public isApplicable(change: Changeset): boolean { for (const mark of change) { if (isActiveReattach(mark)) { - const detachEvent = mark.detachEvent ?? fail("Unable to track detached nodes"); + const detachEvent = mark.cellId ?? fail("Unable to track detached nodes"); const revision = detachEvent.revision; for (let i = 0; i < mark.count; ++i) { const localId = brand((detachEvent.localId as number) + i); - const original: ChangeAtomId = { revision, localId }; + const original: CellId = { revision, localId }; const updated = this.getUpdatedDetach(original); for (const detached of this.nodes.values()) { if ( @@ -598,15 +637,15 @@ export class DetachedNodeTracker { } private updateMark(mark: CellTargetingMark & DetachedCellMark): void { - const detachEvent = mark.detachEvent; + const detachEvent = mark.cellId; const original = { revision: detachEvent.revision, localId: detachEvent.localId }; const updated = this.getUpdatedDetach(original); if (updated.revision !== original.revision || updated.localId !== original.localId) { - mark.detachEvent = { ...updated }; + mark.cellId = { ...updated }; } } - private getUpdatedDetach(detach: ChangeAtomId): ChangeAtomId { + private getUpdatedDetach(detach: CellId): CellId { let curr = detach; for (const eq of this.equivalences) { if (curr.revision === eq.old.revision && curr.localId === eq.old.localId) { @@ -637,8 +676,8 @@ export function areRebasable(branch: Changeset, target: Changeset, target: Changeset>(mark: TMark, length: number) const mark1: TMark = { ...mark, count: length }; const mark2: TMark = { ...mark, id: (mark.id as number) + length, count: remainder }; if (mark.type === "ReturnTo") { - if (mark.detachEvent !== undefined) { - (mark2 as ReturnTo).detachEvent = splitDetachEvent(mark.detachEvent, length); + if (mark.cellId !== undefined) { + (mark2 as ReturnTo).cellId = splitDetachEvent(mark.cellId, length); } return [mark1, mark2]; @@ -853,8 +892,8 @@ export function splitMark>(mark: TMark, length: number) count: remainder, }; - if (mark.detachEvent !== undefined) { - (mark2 as Revive).detachEvent = splitDetachEvent(mark.detachEvent, length); + if (mark.cellId !== undefined) { + (mark2 as Revive).cellId = splitDetachEvent(mark.cellId, length); } if (mark.transientDetach !== undefined) { (mark2 as Transient).transientDetach = { @@ -868,12 +907,12 @@ export function splitMark>(mark: TMark, length: number) const mark1 = { ...mark, count: length }; const id2: ChangesetLocalId = brand((mark.id as number) + length); const mark2 = - mark.detachEvent !== undefined + mark.cellId !== undefined ? { ...mark, id: id2, count: remainder, - detachEvent: splitDetachEvent(mark.detachEvent, length), + cellId: splitDetachEvent(mark.cellId, length), } : { ...mark, @@ -892,8 +931,8 @@ export function splitMark>(mark: TMark, length: number) id: (mark.id as number) + length, count: remainder, }; - if (mark.detachEvent !== undefined) { - (mark2 as Detach).detachEvent = splitDetachEvent(mark.detachEvent, length); + if (mark.cellId !== undefined) { + (mark2 as Detach).cellId = splitDetachEvent(mark.cellId, length); } return [mark1, mark2]; } @@ -904,7 +943,7 @@ export function splitMark>(mark: TMark, length: number) } } -function splitDetachEvent(detachEvent: ChangeAtomId, length: number): ChangeAtomId { +function splitDetachEvent(detachEvent: CellId, length: number): CellId { return { ...detachEvent, localId: brand((detachEvent.localId as number) + length) }; } diff --git a/experimental/dds/tree2/src/test/feature-libraries/sequence-field/compose.spec.ts b/experimental/dds/tree2/src/test/feature-libraries/sequence-field/compose.spec.ts index 3dad043f0cb0..7d87ece742c8 100644 --- a/experimental/dds/tree2/src/test/feature-libraries/sequence-field/compose.spec.ts +++ b/experimental/dds/tree2/src/test/feature-libraries/sequence-field/compose.spec.ts @@ -103,7 +103,11 @@ describe("SequenceField - Compose", () => { it("delete ○ revive => Noop", () => { const deletion = tagChange(Change.delete(0, 1), tag1); - const insertion = tagRollbackInverse(Change.revive(0, 1, tag1), tag2, tag1); + const insertion = tagRollbackInverse( + Change.revive(0, 1, { revision: tag1, localId: brand(0) }), + tag2, + tag1, + ); const actual = shallowCompose([deletion, insertion]); assert.deepEqual(actual, cases.no_change); }); @@ -141,7 +145,7 @@ describe("SequenceField - Compose", () => { const modify: SF.Modify = { type: "Modify", changes: TestChange.mint([], 42), - detachEvent: detach, + cellId: detach, }; const actual = compose([makeAnonChange([insert]), makeAnonChange([modify])], revInfos); assert.deepEqual(actual, [insert]); @@ -154,7 +158,7 @@ describe("SequenceField - Compose", () => { }; const revive: SF.Revive = { type: "Revive", - detachEvent: { + cellId: { revision: tag1, localId: brand(0), }, @@ -165,7 +169,7 @@ describe("SequenceField - Compose", () => { const modify: SF.Modify = { type: "Modify", changes: TestChange.mint([], 42), - detachEvent: detach, + cellId: detach, }; const actual = compose([makeAnonChange([revive]), makeAnonChange([modify])], revInfos); assert.deepEqual(actual, [revive]); @@ -185,7 +189,7 @@ describe("SequenceField - Compose", () => { const revive: SF.Revive = { type: "Revive", changes: TestChange.mint([], 42), - detachEvent: detach, + cellId: detach, count: 1, content: fakeRepair(tag2, 0, 1), }; @@ -245,7 +249,7 @@ describe("SequenceField - Compose", () => { }); it("revive ○ modify", () => { - const revive = Change.revive(0, 3, tag1, brand(0)); + const revive = Change.revive(0, 3, { revision: tag1, localId: brand(0) }); const childChange = TestChange.mint([0, 1], 2); const modify = Change.modify(0, childChange); const expected: TestChangeset = [ @@ -253,7 +257,7 @@ describe("SequenceField - Compose", () => { type: "Revive", content: fakeRepair(tag1, 0, 1), count: 1, - detachEvent: { revision: tag1, localId: brand(0) }, + cellId: { revision: tag1, localId: brand(0) }, changes: childChange, inverseOf: tag1, }, @@ -261,7 +265,7 @@ describe("SequenceField - Compose", () => { type: "Revive", content: fakeRepair(tag1, 1, 2), count: 2, - detachEvent: { revision: tag1, localId: brand(1) }, + cellId: { revision: tag1, localId: brand(1) }, inverseOf: tag1, }, ]; @@ -281,7 +285,7 @@ describe("SequenceField - Compose", () => { type: "Revive", content: fakeRepair(tag1, 0, 1), count: 1, - detachEvent: { revision: tag1, localId: brand(0) }, + cellId: { revision: tag1, localId: brand(0) }, changes: childChangeA, }, ]; @@ -296,7 +300,7 @@ describe("SequenceField - Compose", () => { type: "Revive", content: fakeRepair(tag1, 0, 1), count: 1, - detachEvent: { revision: tag1, localId: brand(0) }, + cellId: { revision: tag1, localId: brand(0) }, changes: childChangeAB, }, ]; @@ -557,7 +561,7 @@ describe("SequenceField - Compose", () => { }); it("revive ○ delete", () => { - const revive = Change.revive(0, 5, tag1, brand(0)); + const revive = Change.revive(0, 5, { revision: tag1, localId: brand(0) }); const deletion: SF.Changeset = [ { count: 1 }, { type: "Delete", id: brand(0), count: 1 }, @@ -570,14 +574,14 @@ describe("SequenceField - Compose", () => { type: "Revive", content: fakeRepair(tag1, 0, 1), count: 1, - detachEvent: { revision: tag1, localId: brand(0) }, + cellId: { revision: tag1, localId: brand(0) }, inverseOf: tag1, }, { type: "Revive", content: fakeRepair(tag1, 1, 1), count: 1, - detachEvent: { revision: tag1, localId: brand(1) }, + cellId: { revision: tag1, localId: brand(1) }, inverseOf: tag1, transientDetach: { revision: tag2, localId: brand(0) }, }, @@ -585,14 +589,14 @@ describe("SequenceField - Compose", () => { type: "Revive", content: fakeRepair(tag1, 2, 1), count: 1, - detachEvent: { revision: tag1, localId: brand(2) }, + cellId: { revision: tag1, localId: brand(2) }, inverseOf: tag1, }, { type: "Revive", content: fakeRepair(tag1, 3, 2), count: 2, - detachEvent: { revision: tag1, localId: brand(3) }, + cellId: { revision: tag1, localId: brand(3) }, inverseOf: tag1, transientDetach: { revision: tag2, localId: brand(1) }, }, @@ -609,7 +613,7 @@ describe("SequenceField - Compose", () => { type: "Revive", content: fakeRepair(tag1, 0, 1), count: 1, - detachEvent, + cellId: detachEvent, changes: childChange, }, ]; @@ -620,7 +624,7 @@ describe("SequenceField - Compose", () => { type: "Revive", content: fakeRepair(tag1, 0, 1), count: 1, - detachEvent, + cellId: detachEvent, changes: childChange, revision: tag2, transientDetach: { revision: tag3, localId: brand(0) }, @@ -658,7 +662,7 @@ describe("SequenceField - Compose", () => { }); it("revive ○ insert", () => { - const revive = Change.revive(0, 5, tag1, brand(0)); + const revive = Change.revive(0, 5, { revision: tag1, localId: brand(0) }); const insert = Change.insert(0, 1, 2); // TODO: test with merge-right policy as well const expected: SF.Changeset = [ @@ -667,7 +671,7 @@ describe("SequenceField - Compose", () => { type: "Revive", content: fakeRepair(tag1, 0, 5), count: 5, - detachEvent: { revision: tag1, localId: brand(0) }, + cellId: { revision: tag1, localId: brand(0) }, inverseOf: tag1, }, ]; @@ -710,13 +714,13 @@ describe("SequenceField - Compose", () => { it("modify ○ revive", () => { const childChange = TestChange.mint([0, 1], 2); const modify = Change.modify(0, childChange); - const revive = Change.revive(0, 2, tag1, brand(0)); + const revive = Change.revive(0, 2, { revision: tag1, localId: brand(0) }); const expected: TestChangeset = [ { type: "Revive", content: fakeRepair(tag1, 0, 2), count: 2, - detachEvent: { revision: tag1, localId: brand(0) }, + cellId: { revision: tag1, localId: brand(0) }, inverseOf: tag1, }, { @@ -731,15 +735,16 @@ describe("SequenceField - Compose", () => { it("delete ○ revive (different earlier nodes)", () => { const deletion = tagChange(Change.delete(0, 2), tag1); const lineage: SF.LineageEvent[] = [{ revision: tag1, id: brand(0), count: 2, offset: 0 }]; - const revive = makeAnonChange(Change.revive(0, 2, tag2, brand(0), undefined, lineage)); + const revive = makeAnonChange( + Change.revive(0, 2, { revision: tag2, localId: brand(0), lineage }), + ); const expected: SF.Changeset = [ { type: "Revive", content: fakeRepair(tag2, 0, 2), count: 2, - detachEvent: { revision: tag2, localId: brand(0) }, + cellId: { revision: tag2, localId: brand(0), lineage }, inverseOf: tag2, - lineage, }, { type: "Delete", id: brand(0), count: 2, revision: tag1 }, ]; @@ -750,16 +755,17 @@ describe("SequenceField - Compose", () => { it("delete ○ revive (different in-between nodes)", () => { const deletion = tagChange(Change.delete(0, 2), tag1); const lineage: SF.LineageEvent[] = [{ revision: tag1, id: brand(0), count: 2, offset: 1 }]; - const revive = makeAnonChange(Change.revive(0, 2, tag2, brand(0), undefined, lineage)); + const revive = makeAnonChange( + Change.revive(0, 2, { revision: tag2, localId: brand(0), lineage }), + ); const expected: SF.Changeset = [ { type: "Delete", id: brand(0), count: 1, revision: tag1 }, { type: "Revive", content: fakeRepair(tag2, 0, 2), count: 2, - detachEvent: { revision: tag2, localId: brand(0) }, + cellId: { revision: tag2, localId: brand(0), lineage }, inverseOf: tag2, - lineage, }, { type: "Delete", id: brand(1), count: 1, revision: tag1 }, ]; @@ -770,16 +776,17 @@ describe("SequenceField - Compose", () => { it("delete ○ revive (different later nodes)", () => { const deletion = tagChange(Change.delete(0, 2), tag1); const lineage: SF.LineageEvent[] = [{ revision: tag1, id: brand(0), count: 2, offset: 2 }]; - const revive = makeAnonChange(Change.revive(0, 2, tag2, brand(0), undefined, lineage)); + const revive = makeAnonChange( + Change.revive(0, 2, { revision: tag2, localId: brand(0), lineage }), + ); const expected: SF.Changeset = [ { type: "Delete", id: brand(0), count: 2, revision: tag1 }, { type: "Revive", content: fakeRepair(tag2, 0, 2), count: 2, - detachEvent: { revision: tag2, localId: brand(0) }, + cellId: { revision: tag2, localId: brand(0), lineage }, inverseOf: tag2, - lineage, }, ]; const actual = shallowCompose([deletion, revive]); @@ -791,9 +798,11 @@ describe("SequenceField - Compose", () => { const delete2 = Change.delete(0, 2); // The revive needs lineage to describe the precise gap in which it is reviving the nodes. // Such lineage would normally be acquired by rebasing the revive over the second delete. - const revive = Change.revive(0, 1, tag1, brand(1), undefined, [ - { revision: tag2, id: brand(0), count: 2, offset: 1 }, - ]); + const revive = Change.revive(0, 1, { + revision: tag1, + localId: brand(1), + lineage: [{ revision: tag2, id: brand(0), count: 2, offset: 1 }], + }); const expected: SF.Changeset = [ { type: "Delete", id: brand(0), count: 1, revision: tag2 }, { type: "Delete", id: brand(0), count: 1, revision: tag1 }, @@ -812,7 +821,7 @@ describe("SequenceField - Compose", () => { it("delete1 ○ delete2 ○ revive (delete2)", () => { const delete1 = Change.delete(1, 3); const delete2 = Change.delete(0, 2); - const revive = Change.revive(0, 2, tag2, brand(0)); + const revive = Change.revive(0, 2, { revision: tag2, localId: brand(0) }); const expected: SF.Changeset = [ { count: 1 }, { type: "Delete", id: brand(0), count: 3, revision: tag1 }, @@ -827,23 +836,22 @@ describe("SequenceField - Compose", () => { it("reviveAA ○ reviveB => BAA", () => { const lineage: SF.LineageEvent[] = [{ revision: tag2, id: brand(0), count: 1, offset: 1 }]; - const reviveAA = Change.revive(0, 2, tag1, brand(1), undefined, lineage); - const reviveB = Change.revive(0, 1, tag2, brand(0)); + const reviveAA = Change.revive(0, 2, { revision: tag1, localId: brand(1), lineage }); + const reviveB = Change.revive(0, 1, { revision: tag2, localId: brand(0) }); const expected: SF.Changeset = [ { type: "Revive", content: fakeRepair(tag2, 0, 1), count: 1, - detachEvent: { revision: tag2, localId: brand(0) }, + cellId: { revision: tag2, localId: brand(0) }, inverseOf: tag2, }, { type: "Revive", content: fakeRepair(tag1, 1, 2), count: 2, - detachEvent: { revision: tag1, localId: brand(1) }, + cellId: { revision: tag1, localId: brand(1), lineage }, inverseOf: tag1, - lineage, }, ]; const actual = shallowCompose([makeAnonChange(reviveAA), makeAnonChange(reviveB)]); @@ -852,30 +860,29 @@ describe("SequenceField - Compose", () => { it("reviveA ○ reviveBB => BAB", () => { const lineage: SF.LineageEvent[] = [{ revision: tag2, id: brand(0), count: 2, offset: 1 }]; - const reviveA = Change.revive(0, 1, tag1, brand(1), undefined, lineage); - const reviveB1 = Change.revive(0, 1, tag2, brand(0)); - const reviveB2 = Change.revive(2, 1, tag2, brand(1)); + const reviveA = Change.revive(0, 1, { revision: tag1, localId: brand(1), lineage }); + const reviveB1 = Change.revive(0, 1, { revision: tag2, localId: brand(0) }); + const reviveB2 = Change.revive(2, 1, { revision: tag2, localId: brand(1) }); const expected: SF.Changeset = [ { type: "Revive", content: fakeRepair(tag2, 0, 1), count: 1, - detachEvent: { revision: tag2, localId: brand(0) }, + cellId: { revision: tag2, localId: brand(0) }, inverseOf: tag2, }, { type: "Revive", content: fakeRepair(tag1, 1, 1), count: 1, - detachEvent: { revision: tag1, localId: brand(1) }, + cellId: { revision: tag1, localId: brand(1), lineage }, inverseOf: tag1, - lineage, }, { type: "Revive", content: fakeRepair(tag2, 1, 1), count: 1, - detachEvent: { revision: tag2, localId: brand(1) }, + cellId: { revision: tag2, localId: brand(1) }, inverseOf: tag2, }, ]; @@ -889,22 +896,21 @@ describe("SequenceField - Compose", () => { it("reviveAA ○ reviveB => AAB", () => { const lineage: SF.LineageEvent[] = [{ revision: tag2, id: brand(0), count: 1, offset: 0 }]; - const reviveA = Change.revive(0, 2, tag1, brand(0), undefined, lineage); - const reviveB = Change.revive(2, 1, tag2, brand(0)); + const reviveA = Change.revive(0, 2, { revision: tag1, localId: brand(0), lineage }); + const reviveB = Change.revive(2, 1, { revision: tag2, localId: brand(0) }); const expected: SF.Changeset = [ { type: "Revive", content: fakeRepair(tag1, 0, 2), count: 2, - detachEvent: { revision: tag1, localId: brand(0) }, + cellId: { revision: tag1, localId: brand(0), lineage }, inverseOf: tag1, - lineage, }, { type: "Revive", content: fakeRepair(tag2, 0, 1), count: 1, - detachEvent: { revision: tag2, localId: brand(0) }, + cellId: { revision: tag2, localId: brand(0) }, inverseOf: tag2, }, ]; @@ -913,14 +919,14 @@ describe("SequenceField - Compose", () => { }); it("revive ○ redundant revive", () => { - const reviveA = Change.revive(0, 2, tag1, brand(0)); - const reviveB = Change.redundantRevive(0, 2, tag1, brand(0)); + const reviveA = Change.revive(0, 2, { revision: tag1, localId: brand(0) }); + const reviveB = Change.redundantRevive(0, 2, { revision: tag1, localId: brand(0) }); const expected: SF.Changeset = [ { type: "Revive", content: fakeRepair(tag1, 0, 2), count: 2, - detachEvent: { revision: tag1, localId: brand(0) }, + cellId: { revision: tag1, localId: brand(0) }, inverseOf: tag1, revision: tag2, }, @@ -949,7 +955,7 @@ describe("SequenceField - Compose", () => { revision: tag3, content: fakeRepair(tag1, 0, 1), count: 1, - detachEvent: { revision: tag1, localId: brand(0) }, + cellId: { revision: tag1, localId: brand(0) }, }, { count: 4 }, { @@ -957,7 +963,7 @@ describe("SequenceField - Compose", () => { revision: tag4, content: fakeRepair(tag1, 0, 1), count: 1, - detachEvent: { revision: tag1, localId: brand(0) }, + cellId: { revision: tag1, localId: brand(0) }, }, ]; const actual = shallowCompose([makeAnonChange(insert), makeAnonChange(revive)], revInfos); @@ -967,7 +973,7 @@ describe("SequenceField - Compose", () => { revision: tag3, count: 1, content: fakeRepair(tag1, 0, 1), - detachEvent: { revision: tag1, localId: brand(0) }, + cellId: { revision: tag1, localId: brand(0) }, }, { type: "Insert", revision: tag1, content: [{ type, value: 1 }], id: brand(1) }, { count: 2 }, @@ -977,7 +983,7 @@ describe("SequenceField - Compose", () => { revision: tag4, content: fakeRepair(tag1, 0, 1), count: 1, - detachEvent: { revision: tag1, localId: brand(0) }, + cellId: { revision: tag1, localId: brand(0) }, }, { type: "Insert", revision: tag2, content: [{ type, value: 3 }], id: brand(3) }, ]; @@ -1006,8 +1012,14 @@ describe("SequenceField - Compose", () => { }); it("return ○ return", () => { - const return1 = tagChange(Change.return(0, 1, 3, tag2, brand(0)), tag3); - const return2 = tagChange(Change.return(3, 1, 0, tag3, brand(0)), tag4); + const return1 = tagChange( + Change.return(0, 1, 3, { revision: tag2, localId: brand(0) }), + tag3, + ); + const return2 = tagChange( + Change.return(3, 1, 0, { revision: tag3, localId: brand(0) }), + tag4, + ); const actual = shallowCompose([return1, return2]); assert.deepEqual(actual, []); }); @@ -1058,17 +1070,17 @@ describe("SequenceField - Compose", () => { // Revision 4 modifies B const nodeChange1 = "Change1"; const nodeChange2 = "Change2"; - const detach1: ChangeAtomId = { revision: tag1, localId: brand(0) }; - const detach2: ChangeAtomId = { revision: tag2, localId: brand(0) }; - const lineage: SF.LineageEvent[] = [{ revision: tag2, id: brand(0), count: 1, offset: 0 }]; - const modify1 = Change.modifyDetached(0, nodeChange1, detach1, lineage); + const detach1: SF.CellId = { revision: tag1, localId: brand(0), lineage }; + const detach2: SF.CellId = { revision: tag2, localId: brand(0) }; + + const modify1 = Change.modifyDetached(0, nodeChange1, detach1); const modify2 = Change.modifyDetached(0, nodeChange2, detach2); const actual = shallowCompose([tagChange(modify1, tag3), tagChange(modify2, tag4)]); const expected: SF.Changeset = [ - { type: "Modify", changes: nodeChange1, detachEvent: detach1, lineage }, - { type: "Modify", changes: nodeChange2, detachEvent: detach2 }, + { type: "Modify", changes: nodeChange1, cellId: detach1 }, + { type: "Modify", changes: nodeChange2, cellId: detach2 }, ]; assert.deepEqual(actual, expected); @@ -1082,17 +1094,17 @@ describe("SequenceField - Compose", () => { // Revision 4 modifies A const nodeChange1 = "Change1"; const nodeChange2 = "Change2"; - const detach1: ChangeAtomId = { revision: tag1, localId: brand(1) }; - const detach2: ChangeAtomId = { revision: tag2, localId: brand(0) }; - const lineage: SF.LineageEvent[] = [{ revision: tag2, id: brand(0), count: 1, offset: 1 }]; - const modify1 = Change.modifyDetached(0, nodeChange1, detach1, lineage); + const detach1: SF.CellId = { revision: tag1, localId: brand(1), lineage }; + const detach2: SF.CellId = { revision: tag2, localId: brand(0) }; + + const modify1 = Change.modifyDetached(0, nodeChange1, detach1); const modify2 = Change.modifyDetached(0, nodeChange2, detach2); const actual = shallowCompose([tagChange(modify1, tag3), tagChange(modify2, tag4)]); const expected: SF.Changeset = [ - { type: "Modify", changes: nodeChange2, detachEvent: detach2 }, - { type: "Modify", changes: nodeChange1, detachEvent: detach1, lineage }, + { type: "Modify", changes: nodeChange2, cellId: detach2 }, + { type: "Modify", changes: nodeChange1, cellId: detach1 }, ]; assert.deepEqual(actual, expected); @@ -1106,17 +1118,17 @@ describe("SequenceField - Compose", () => { // Revision 4 modifies A const nodeChange1 = "Change1"; const nodeChange2 = "Change2"; - const detach1: ChangeAtomId = { revision: tag1, localId: brand(0) }; - const detach2: ChangeAtomId = { revision: tag2, localId: brand(0) }; - const lineage: SF.LineageEvent[] = [{ revision: tag2, id: brand(0), count: 1, offset: 0 }]; + const detach1: SF.CellId = { revision: tag1, localId: brand(0), lineage }; + const detach2: SF.CellId = { revision: tag2, localId: brand(0) }; + const modify1 = Change.modifyDetached(0, nodeChange1, detach2); - const modify2 = Change.modifyDetached(0, nodeChange2, detach1, lineage); + const modify2 = Change.modifyDetached(0, nodeChange2, detach1); const actual = shallowCompose([tagChange(modify1, tag3), tagChange(modify2, tag4)]); const expected: SF.Changeset = [ - { type: "Modify", changes: nodeChange2, detachEvent: detach1, lineage }, - { type: "Modify", changes: nodeChange1, detachEvent: detach2 }, + { type: "Modify", changes: nodeChange2, cellId: detach1 }, + { type: "Modify", changes: nodeChange1, cellId: detach2 }, ]; assert.deepEqual(actual, expected); @@ -1130,17 +1142,18 @@ describe("SequenceField - Compose", () => { // Revision 4 modifies B const nodeChange1 = "Change1"; const nodeChange2 = "Change2"; - const detach1: ChangeAtomId = { revision: tag1, localId: brand(1) }; - const detach2: ChangeAtomId = { revision: tag2, localId: brand(0) }; const lineage: SF.LineageEvent[] = [{ revision: tag2, id: brand(0), count: 1, offset: 1 }]; + const detach1: SF.CellId = { revision: tag1, localId: brand(1), lineage }; + const detach2: SF.CellId = { revision: tag2, localId: brand(0) }; + const modify1 = Change.modifyDetached(0, nodeChange1, detach2); - const modify2 = Change.modifyDetached(0, nodeChange2, detach1, lineage); + const modify2 = Change.modifyDetached(0, nodeChange2, detach1); const actual = shallowCompose([tagChange(modify1, tag3), tagChange(modify2, tag4)]); const expected: SF.Changeset = [ - { type: "Modify", changes: nodeChange1, detachEvent: detach2 }, - { type: "Modify", changes: nodeChange2, detachEvent: detach1, lineage }, + { type: "Modify", changes: nodeChange1, cellId: detach2 }, + { type: "Modify", changes: nodeChange2, cellId: detach1 }, ]; assert.deepEqual(actual, expected); @@ -1148,8 +1161,22 @@ describe("SequenceField - Compose", () => { it("adjacent blocked revives", () => { const lineage: SF.LineageEvent[] = [{ revision: tag2, id: brand(0), count: 1, offset: 1 }]; - const revive1 = Change.blockedRevive(0, 5, tag1, tag2, brand(0)); - const revive2 = Change.blockedRevive(0, 4, tag3, tag4, brand(0), undefined, lineage); + const revive1 = Change.blockedRevive( + 0, + 5, + { revision: tag1, localId: brand(0) }, + { revision: tag2, localId: brand(0) }, + ); + const revive2 = Change.blockedRevive( + 0, + 4, + { revision: tag3, localId: brand(0) }, + { + revision: tag4, + localId: brand(0), + lineage, + }, + ); const actual = shallowCompose([tagChange(revive1, tag5), tagChange(revive2, tag6)]); const expected: SF.Changeset = [ @@ -1158,7 +1185,7 @@ describe("SequenceField - Compose", () => { revision: tag5, count: 5, content: fakeRepair(tag1, 0, 5), - detachEvent: { revision: tag2, localId: brand(0) }, + cellId: { revision: tag2, localId: brand(0) }, inverseOf: tag1, }, { @@ -1166,9 +1193,8 @@ describe("SequenceField - Compose", () => { revision: tag6, count: 4, content: fakeRepair(tag3, 0, 4), - detachEvent: { revision: tag4, localId: brand(0) }, + cellId: { revision: tag4, localId: brand(0), lineage }, inverseOf: tag3, - lineage, }, ]; diff --git a/experimental/dds/tree2/src/test/feature-libraries/sequence-field/invert.spec.ts b/experimental/dds/tree2/src/test/feature-libraries/sequence-field/invert.spec.ts index c2a08998f5e3..592e04bde0f6 100644 --- a/experimental/dds/tree2/src/test/feature-libraries/sequence-field/invert.spec.ts +++ b/experimental/dds/tree2/src/test/feature-libraries/sequence-field/invert.spec.ts @@ -73,7 +73,7 @@ describe("SequenceField - Invert", () => { it("delete => revive", () => { const input = composeAnonChanges([Change.modify(0, childChange1), Change.delete(0, 2)]); const expected = composeAnonChanges([ - Change.revive(0, 2, tag1, brand(0)), + Change.revive(0, 2, { revision: tag1, localId: brand(0) }), Change.modify(0, inverseChildChange1), ]); const actual = invert(input); @@ -81,7 +81,7 @@ describe("SequenceField - Invert", () => { }); it("revert-only active revive => delete", () => { - const revive = Change.revive(0, 2, tag1, brand(0)); + const revive = Change.revive(0, 2, { revision: tag1, localId: brand(0) }); const modify = Change.modify(0, childChange1); const input = composeAnonChanges([revive, modify]); const expected = composeAnonChanges([ @@ -93,7 +93,7 @@ describe("SequenceField - Invert", () => { }); it("intentional active revive => delete", () => { - const input = Change.intentionalRevive(0, 2, tag1, brand(0)); + const input = Change.intentionalRevive(0, 2, { revision: tag1, localId: brand(0) }); const expected = Change.delete(0, 2); const actual = invert(input); assert.deepEqual(actual, expected); @@ -103,7 +103,7 @@ describe("SequenceField - Invert", () => { const input = composeAnonChanges([Change.modify(0, childChange1), Change.move(0, 2, 3)]); const expected = composeAnonChanges([ Change.modify(3, inverseChildChange1), - Change.return(3, 2, 0, tag1, brand(0)), + Change.return(3, 2, 0, { revision: tag1, localId: brand(0) }), ]); const actual = invert(input); assert.deepEqual(actual, expected); @@ -113,7 +113,7 @@ describe("SequenceField - Invert", () => { const input = composeAnonChanges([Change.modify(3, childChange1), Change.move(2, 2, 0)]); const expected = composeAnonChanges([ Change.modify(1, inverseChildChange1), - Change.return(0, 2, 2, tag1, brand(0)), + Change.return(0, 2, 2, { revision: tag1, localId: brand(0) }), ]); const actual = invert(input); assert.deepEqual(actual, expected); @@ -122,11 +122,11 @@ describe("SequenceField - Invert", () => { it("return => return", () => { const input = composeAnonChanges([ Change.modify(0, childChange1), - Change.return(0, 2, 3, tag1, brand(0)), + Change.return(0, 2, 3, { revision: tag1, localId: brand(0) }), ]); const expected = composeAnonChanges([ Change.modify(3, inverseChildChange1), - Change.return(3, 2, 0, tag1, brand(0)), + Change.return(3, 2, 0, { revision: tag1, localId: brand(0) }), ]); const actual = invert(input); assert.deepEqual(actual, expected); @@ -141,7 +141,7 @@ describe("SequenceField - Invert", () => { id: brand(0), count: 1, changes: childChange1, - detachEvent, + cellId: detachEvent, }, ]; @@ -159,7 +159,7 @@ describe("SequenceField - Invert", () => { count: 1, id: brand(0), changes: childChange1, - detachEvent, + cellId: detachEvent, }, { type: "MoveIn", @@ -205,7 +205,12 @@ describe("SequenceField - Invert", () => { it("revert-only blocked revive => no-op", () => { const input = composeAnonChanges([ Change.modify(0, childChange1), - Change.blockedRevive(1, 2, tag1, tag2, brand(0)), + Change.blockedRevive( + 1, + 2, + { revision: tag1, localId: brand(0) }, + { revision: tag2, localId: brand(0) }, + ), Change.modify(1, childChange2), ]); const expected = composeAnonChanges([ @@ -219,7 +224,13 @@ describe("SequenceField - Invert", () => { it("intentional redundant revive => skip", () => { const input = composeAnonChanges([ Change.modify(0, childChange1), - Change.redundantRevive(1, 1, tag1, brand(0), undefined, true), + Change.redundantRevive( + 1, + 1, + { revision: tag1, localId: brand(0) }, + undefined, + true, + ), Change.modify(2, childChange2), ]); const expected = composeAnonChanges([ @@ -243,7 +254,7 @@ describe("SequenceField - Invert", () => { type: "ReturnTo", count: 1, id: brand(0), - detachEvent: { revision: tag2, localId: brand(0) }, + cellId: { revision: tag2, localId: brand(0) }, inverseOf: tag1, }, { @@ -265,7 +276,7 @@ describe("SequenceField - Invert", () => { type: "MoveOut", count: 1, id: brand(0), - detachEvent: { revision: tag2, localId: brand(0) }, + cellId: { revision: tag2, localId: brand(0) }, }, { type: "Modify", @@ -296,7 +307,7 @@ describe("SequenceField - Invert", () => { type: "ReturnFrom", count: 1, id: brand(0), - detachEvent: { revision: tag2, localId: brand(0) }, + cellId: { revision: tag2, localId: brand(0) }, }, { type: "Modify", @@ -306,7 +317,7 @@ describe("SequenceField - Invert", () => { type: "ReturnTo", count: 1, id: brand(0), - detachEvent: { revision: tag1, localId: brand(0) }, + cellId: { revision: tag1, localId: brand(0) }, isSrcConflicted: true, }, { @@ -328,7 +339,7 @@ describe("SequenceField - Invert", () => { type: "ReturnFrom", count: 1, id: brand(0), - detachEvent: { revision: tag2, localId: brand(0) }, + cellId: { revision: tag2, localId: brand(0) }, isDstConflicted: true, }, { diff --git a/experimental/dds/tree2/src/test/feature-libraries/sequence-field/markListFactory.spec.ts b/experimental/dds/tree2/src/test/feature-libraries/sequence-field/markListFactory.spec.ts index 0468e9a93a01..4a1b5540b97c 100644 --- a/experimental/dds/tree2/src/test/feature-libraries/sequence-field/markListFactory.spec.ts +++ b/experimental/dds/tree2/src/test/feature-libraries/sequence-field/markListFactory.spec.ts @@ -119,13 +119,13 @@ describe("SequenceField - MarkListFactory", () => { const factory = new SF.MarkListFactory(); const revive1: SF.Reattach = { type: "Revive", - detachEvent: { revision: detachedBy, localId: brand(0) }, + cellId: { revision: detachedBy, localId: brand(0) }, content: fakeRepair(detachedBy, 0, 1), count: 1, }; const revive2: SF.Reattach = { type: "Revive", - detachEvent: { revision: detachedBy, localId: brand(1) }, + cellId: { revision: detachedBy, localId: brand(1) }, content: fakeRepair(detachedBy, 1, 1), count: 1, }; @@ -133,7 +133,7 @@ describe("SequenceField - MarkListFactory", () => { factory.pushContent(revive2); const expected: SF.Reattach = { type: "Revive", - detachEvent: { revision: detachedBy, localId: brand(0) }, + cellId: { revision: detachedBy, localId: brand(0) }, content: fakeRepair(detachedBy, 0, 2), count: 2, }; @@ -144,13 +144,13 @@ describe("SequenceField - MarkListFactory", () => { const factory = new SF.MarkListFactory(); const revive1: SF.Reattach = { type: "Revive", - detachEvent: { revision: detachedBy, localId: brand(0) }, + cellId: { revision: detachedBy, localId: brand(0) }, content: fakeRepair(detachedBy, 0, 1), count: 1, }; const revive2: SF.Reattach = { type: "Revive", - detachEvent: { revision: detachedBy, localId: brand(2) }, + cellId: { revision: detachedBy, localId: brand(2) }, content: fakeRepair(detachedBy, 2, 1), count: 1, }; diff --git a/experimental/dds/tree2/src/test/feature-libraries/sequence-field/rebase.spec.ts b/experimental/dds/tree2/src/test/feature-libraries/sequence-field/rebase.spec.ts index c5d094ac6383..f2caebb4ba03 100644 --- a/experimental/dds/tree2/src/test/feature-libraries/sequence-field/rebase.spec.ts +++ b/experimental/dds/tree2/src/test/feature-libraries/sequence-field/rebase.spec.ts @@ -67,9 +67,9 @@ describe("SequenceField - Rebase", () => { it("revive ↷ modify", () => { const revive = composeAnonChanges([ - Change.revive(0, 2, tag1, brand(0), rebaseRepair), - Change.revive(4, 2, tag1, brand(2), rebaseRepair), - Change.revive(10, 2, tag1, brand(4), rebaseRepair), + Change.revive(0, 2, { revision: tag1, localId: brand(0) }, rebaseRepair), + Change.revive(4, 2, { revision: tag1, localId: brand(2) }, rebaseRepair), + Change.revive(10, 2, { revision: tag1, localId: brand(4) }, rebaseRepair), ]); const mods = composeAnonChanges([ Change.modify(0, TestChange.mint([0], 1)), @@ -119,43 +119,60 @@ describe("SequenceField - Rebase", () => { it("revive ↷ delete", () => { const revive = composeAnonChanges([ - Change.revive(0, 1, tag1, brand(0), rebaseRepair), - Change.revive(3, 1, tag1, brand(1), rebaseRepair), - Change.revive(8, 1, tag1, brand(2), rebaseRepair), + Change.revive(0, 1, { revision: tag1, localId: brand(0) }, rebaseRepair), + Change.revive(3, 1, { revision: tag1, localId: brand(1) }, rebaseRepair), + Change.revive(8, 1, { revision: tag1, localId: brand(2) }, rebaseRepair), ]); const deletion = Change.delete(1, 3); const actual = rebase(revive, deletion, tag2); const expected = composeAnonChanges([ // Rebase does not affect the stored repair data - Change.revive(0, 1, tag1, brand(0), rebaseRepair), - Change.revive(2, 1, tag1, brand(1), rebaseRepair, [ - { revision: tag2, id: brand(0), count: 3, offset: 1 }, - ]), - Change.revive(5, 1, tag1, brand(2), rebaseRepair), + Change.revive(0, 1, { revision: tag1, localId: brand(0) }, rebaseRepair), + Change.revive( + 2, + 1, + { + revision: tag1, + localId: brand(1), + lineage: [{ revision: tag2, id: brand(0), count: 3, offset: 1 }], + }, + rebaseRepair, + ), + Change.revive(5, 1, { revision: tag1, localId: brand(2) }, rebaseRepair), ]); assert.deepEqual(actual, expected); }); it("redundant revive ↷ related delete", () => { - const revive = Change.redundantRevive(0, 3, tag1, brand(1), rebaseRepair); + const revive = Change.redundantRevive( + 0, + 3, + { revision: tag1, localId: brand(1) }, + rebaseRepair, + ); const deletion = Change.delete(1, 1); const actual = rebase(revive, deletion, tag2); const expected = composeAnonChanges([ // Earlier revive is unaffected - Change.redundantRevive(0, 1, tag1, brand(1), rebaseRepair), + Change.redundantRevive(0, 1, { revision: tag1, localId: brand(1) }, rebaseRepair), // Overlapping revive is no longer redundant - Change.revive(1, 1, tag1, brand(1), rebaseRepair, undefined, { + Change.revive(1, 1, { revision: tag1, localId: brand(1) }, rebaseRepair, { revision: tag2, localId: brand(0), }), // Later revive is unaffected - Change.redundantRevive(1, 1, tag1, brand(3), rebaseRepair), + Change.redundantRevive(1, 1, { revision: tag1, localId: brand(3) }, rebaseRepair), ]); assert.deepEqual(actual, expected); }); it("redundant revive ↷ unrelated delete", () => { - const revive = Change.redundantRevive(0, 3, tag1, brand(1), fakeRepair); + const revive = Change.redundantRevive( + 0, + 3, + { revision: tag1, localId: brand(1) }, + fakeRepair, + ); const deletion = Change.delete(1, 1); const actual = rebase(revive, deletion, tag3); const expected: SF.Changeset = [ @@ -170,7 +187,7 @@ describe("SequenceField - Rebase", () => { content: fakeRepair(tag1, 2, 1), count: 1, inverseOf: tag1, - detachEvent: { revision: tag3, localId: brand(0) }, + cellId: { revision: tag3, localId: brand(0) }, }, { type: "Revive", @@ -183,62 +200,80 @@ describe("SequenceField - Rebase", () => { }); it("blocked revive ↷ revive", () => { - const revive1 = Change.blockedRevive(0, 3, tag1, tag2, brand(1), fakeRepair); - const revive2 = Change.revive(0, 1, tag2, brand(2), fakeRepair); + const revive1 = Change.blockedRevive( + 0, + 3, + { revision: tag1, localId: brand(0) }, + { revision: tag2, localId: brand(1) }, + fakeRepair, + ); + const revive2 = Change.revive(0, 1, { revision: tag2, localId: brand(2) }, fakeRepair); const actual = rebase(revive1, revive2, tag2); const expected: SF.Changeset = [ { type: "Revive", - content: fakeRepair(tag1, 1, 1), + content: fakeRepair(tag1, 0, 1), count: 1, inverseOf: tag1, - detachEvent: { revision: tag2, localId: brand(1) }, + cellId: { revision: tag2, localId: brand(1) }, }, { type: "Revive", - content: fakeRepair(tag1, 2, 1), + content: fakeRepair(tag1, 1, 1), count: 1, inverseOf: tag1, }, { type: "Revive", - content: fakeRepair(tag1, 3, 1), + content: fakeRepair(tag1, 2, 1), count: 1, inverseOf: tag1, - detachEvent: { revision: tag2, localId: brand(3) }, + cellId: { revision: tag2, localId: brand(3) }, }, ]; assert.deepEqual(actual, expected); }); it("redundant intentional revive ↷ related delete", () => { - const revive = Change.redundantRevive(0, 3, tag1, brand(1), rebaseRepair, true); + const revive = Change.redundantRevive( + 0, + 3, + { revision: tag1, localId: brand(1) }, + rebaseRepair, + true, + ); const deletion = Change.delete(1, 1); const actual = rebase(revive, deletion, tag2); const expected = composeAnonChanges([ // Earlier revive is unaffected - Change.redundantRevive(0, 1, tag1, brand(1), rebaseRepair, true), + Change.redundantRevive(0, 1, { revision: tag1, localId: brand(1) }, rebaseRepair, true), // Overlapping revive is no longer conflicted. // It now references the target node to revive using the latest delete. - Change.intentionalRevive(1, 1, tag2, brand(0), rebaseRepair), + Change.intentionalRevive(1, 1, { revision: tag2, localId: brand(0) }, rebaseRepair), // Later revive is unaffected - Change.redundantRevive(2, 1, tag1, brand(3), rebaseRepair, true), + Change.redundantRevive(2, 1, { revision: tag1, localId: brand(3) }, rebaseRepair, true), ]); assert.deepEqual(actual, expected); }); it("redundant intentional revive ↷ unrelated delete", () => { - const revive = Change.redundantRevive(0, 3, tag1, brand(1), rebaseRepair, true); + const revive = Change.redundantRevive( + 0, + 3, + { revision: tag1, localId: brand(1) }, + rebaseRepair, + true, + ); const deletion = Change.delete(1, 1); const actual = rebase(revive, deletion, tag3); const expected = composeAnonChanges([ // Earlier revive is unaffected - Change.redundantRevive(0, 1, tag1, brand(1), rebaseRepair, true), + Change.redundantRevive(0, 1, { revision: tag1, localId: brand(1) }, rebaseRepair, true), // Overlapping revive is no longer conflicted. // It now references the target node to revive using the latest delete. - Change.intentionalRevive(1, 1, tag3, brand(0), rebaseRepair), + Change.intentionalRevive(1, 1, { revision: tag3, localId: brand(0) }, rebaseRepair), // Later revive gets linage - Change.redundantRevive(2, 1, tag1, brand(3), rebaseRepair, true), + Change.redundantRevive(2, 1, { revision: tag1, localId: brand(3) }, rebaseRepair, true), ]); assert.deepEqual(actual, expected); }); @@ -260,21 +295,21 @@ describe("SequenceField - Rebase", () => { type: "Delete", id: brand(0), count: 1, - detachEvent: { revision: tag1, localId: brand(1) }, + cellId: { revision: tag1, localId: brand(1) }, }, { type: "Delete", id: brand(1), count: 1 }, { type: "Delete", id: brand(2), count: 1, - detachEvent: { revision: tag1, localId: brand(2) }, + cellId: { revision: tag1, localId: brand(2) }, }, { type: "Delete", id: brand(3), count: 1 }, { type: "Delete", id: brand(4), count: 1, - detachEvent: { revision: tag1, localId: brand(3) }, + cellId: { revision: tag1, localId: brand(3) }, }, ]; checkDeltaEquality(actual, expected); @@ -323,23 +358,29 @@ describe("SequenceField - Rebase", () => { type: "MoveOut", count: 1, id: brand(0), - detachEvent: { revision: tag1, localId: brand(1) }, - lineage: [{ revision: tag1, id: brand(0), count: 1, offset: 1 }], + cellId: { + revision: tag1, + localId: brand(1), + lineage: [{ revision: tag1, id: brand(0), count: 1, offset: 1 }], + }, }, { type: "MoveOut", count: 1, id: brand(1) }, { type: "MoveOut", count: 1, id: brand(2), - detachEvent: { revision: tag1, localId: brand(2) }, + cellId: { revision: tag1, localId: brand(2) }, }, { type: "MoveOut", count: 1, id: brand(3) }, { type: "MoveOut", count: 1, id: brand(4), - detachEvent: { revision: tag1, localId: brand(3) }, - lineage: [{ revision: tag1, id: brand(4), count: 1, offset: 0 }], + cellId: { + revision: tag1, + localId: brand(3), + lineage: [{ revision: tag1, id: brand(4), count: 1, offset: 0 }], + }, }, ]; assert.deepEqual(actual, expected); @@ -393,28 +434,33 @@ describe("SequenceField - Rebase", () => { it("revive ↷ insert", () => { const revive = composeAnonChanges([ - Change.revive(0, 1, tag1, brand(0), rebaseRepair), - Change.revive(3, 2, tag1, brand(1), rebaseRepair), - Change.revive(7, 1, tag1, brand(3), rebaseRepair), + Change.revive(0, 1, { revision: tag1, localId: brand(0) }, rebaseRepair), + Change.revive(3, 2, { revision: tag1, localId: brand(1) }, rebaseRepair), + Change.revive(7, 1, { revision: tag1, localId: brand(3) }, rebaseRepair), ]); // TODO: test both tiebreak policies const insert = Change.insert(2, 1); const actual = rebase(revive, insert); const expected = composeAnonChanges([ - Change.revive(0, 1, tag1, brand(0), rebaseRepair), - Change.revive(4, 2, tag1, brand(1), rebaseRepair), - Change.revive(8, 1, tag1, brand(3), rebaseRepair), + Change.revive(0, 1, { revision: tag1, localId: brand(0) }, rebaseRepair), + Change.revive(4, 2, { revision: tag1, localId: brand(1) }, rebaseRepair), + Change.revive(8, 1, { revision: tag1, localId: brand(3) }, rebaseRepair), ]); assert.deepEqual(actual, expected); }); it("redundant revive ↷ insert", () => { - const revive = Change.redundantRevive(0, 3, tag1, brand(0), rebaseRepair); + const revive = Change.redundantRevive( + 0, + 3, + { revision: tag1, localId: brand(0) }, + rebaseRepair, + ); const insert = Change.insert(1, 1); const actual = rebase(revive, insert); const expected = composeAnonChanges([ - Change.redundantRevive(0, 1, tag1, brand(0), rebaseRepair), - Change.redundantRevive(2, 2, tag1, brand(1), rebaseRepair), + Change.redundantRevive(0, 1, { revision: tag1, localId: brand(0) }, rebaseRepair), + Change.redundantRevive(2, 2, { revision: tag1, localId: brand(1) }, rebaseRepair), ]); assert.deepEqual(actual, expected); }); @@ -424,7 +470,7 @@ describe("SequenceField - Rebase", () => { Change.modify(0, TestChange.mint([0], 1)), Change.modify(3, TestChange.mint([0], 2)), ]); - const revive = Change.revive(2, 1, tag1, brand(0), rebaseRepair); + const revive = Change.revive(2, 1, { revision: tag1, localId: brand(0) }, rebaseRepair); const expected = composeAnonChanges([ // Modify at earlier index is unaffected Change.modify(0, TestChange.mint([0], 1)), @@ -443,7 +489,7 @@ describe("SequenceField - Rebase", () => { Change.delete(2, 1, brand(3)), ]); // Revives content between C and D - const revive = Change.revive(3, 1, tag1, brand(0), rebaseRepair); + const revive = Change.revive(3, 1, { revision: tag1, localId: brand(0) }, rebaseRepair); const expected = composeAnonChanges([ // Delete with earlier index is unaffected Change.delete(0, 1, brand(0)), @@ -459,7 +505,7 @@ describe("SequenceField - Rebase", () => { it("insert ↷ revive", () => { const insert = composeAnonChanges([Change.insert(0, 1, 1), Change.insert(3, 1, 2)]); - const revive = Change.revive(1, 1, tag1, brand(0), rebaseRepair); + const revive = Change.revive(1, 1, { revision: tag1, localId: brand(0) }, rebaseRepair); const actual = rebase(insert, revive); const expected = composeAnonChanges([Change.insert(0, 1, 1), Change.insert(4, 1, 2)]); assert.deepEqual(actual, expected); @@ -467,47 +513,91 @@ describe("SequenceField - Rebase", () => { it("reviveAA ↷ reviveB => BAA", () => { const lineage: SF.LineageEvent[] = [{ revision: tag2, id: brand(0), count: 1, offset: 1 }]; - const reviveAA = Change.revive(0, 2, tag1, brand(0), rebaseRepair, lineage); - const reviveB = Change.revive(0, 1, tag2, brand(0), rebaseRepair); - const expected = Change.revive(1, 2, tag1, brand(0), rebaseRepair, lineage); + const reviveAA = Change.revive( + 0, + 2, + { revision: tag1, localId: brand(0), lineage }, + rebaseRepair, + ); + const reviveB = Change.revive(0, 1, { revision: tag2, localId: brand(0) }, rebaseRepair); + const expected = Change.revive( + 1, + 2, + { revision: tag1, localId: brand(0), lineage }, + rebaseRepair, + ); const actual = rebase(reviveAA, reviveB); assert.deepEqual(actual, expected); }); it("reviveAA ↷ reviveB => AAB", () => { const lineage: SF.LineageEvent[] = [{ revision: tag2, id: brand(0), count: 1, offset: 0 }]; - const reviveAA = Change.revive(0, 2, tag1, brand(0), rebaseRepair, lineage); - const reviveB = Change.revive(0, 1, tag2, brand(0), rebaseRepair); - const expected = Change.revive(0, 2, tag1, brand(0), rebaseRepair, lineage); + const reviveAA = Change.revive( + 0, + 2, + { revision: tag1, localId: brand(0), lineage }, + rebaseRepair, + ); + const reviveB = Change.revive(0, 1, { revision: tag2, localId: brand(0) }, rebaseRepair); + const expected = Change.revive( + 0, + 2, + { revision: tag1, localId: brand(0), lineage }, + rebaseRepair, + ); const actual = rebase(reviveAA, reviveB); assert.deepEqual(actual, expected); }); it("reviveBB ↷ reviveA => BBA", () => { - const reviveBB = Change.revive(0, 2, tag2, brand(0), rebaseRepair); - const reviveA = Change.revive(0, 1, tag1, brand(1), rebaseRepair, [ - { revision: tag2, id: brand(0), count: 2, offset: 2 }, - ]); - const expected = Change.revive(0, 2, tag2, brand(0), rebaseRepair); + const reviveBB = Change.revive(0, 2, { revision: tag2, localId: brand(0) }, rebaseRepair); + const reviveA = Change.revive( + 0, + 1, + { + revision: tag1, + localId: brand(1), + lineage: [{ revision: tag2, id: brand(0), count: 2, offset: 2 }], + }, + rebaseRepair, + ); + const expected = Change.revive(0, 2, { revision: tag2, localId: brand(0) }, rebaseRepair); const actual = rebase(reviveBB, reviveA); assert.deepEqual(actual, expected); }); it("reviveBB ↷ reviveA => ABB", () => { - const reviveBB = Change.revive(5, 2, tag2, brand(0), rebaseRepair); - const reviveA = Change.revive(5, 1, tag1, brand(0), rebaseRepair, [ - { revision: tag2, id: brand(0), count: 2, offset: 0 }, - ]); - const expected = Change.revive(6, 2, tag2, brand(0), rebaseRepair); + const reviveBB = Change.revive(5, 2, { revision: tag2, localId: brand(0) }, rebaseRepair); + const reviveA = Change.revive( + 5, + 1, + { + revision: tag1, + localId: brand(0), + lineage: [{ revision: tag2, id: brand(0), count: 2, offset: 0 }], + }, + rebaseRepair, + ); + const expected = Change.revive(6, 2, { revision: tag2, localId: brand(0) }, rebaseRepair); const actual = rebase(reviveBB, reviveA); assert.deepEqual(actual, expected); }); it("reviveA ↷ reviveBB => BAB", () => { const lineage: SF.LineageEvent[] = [{ revision: tag2, id: brand(5), count: 2, offset: 1 }]; - const reviveA = Change.revive(5, 1, tag1, brand(6), rebaseRepair, lineage); - const reviveBB = Change.revive(5, 2, tag2, brand(5), rebaseRepair); - const expected = Change.revive(6, 1, tag1, brand(6), rebaseRepair, lineage); + const reviveA = Change.revive( + 5, + 1, + { revision: tag1, localId: brand(6), lineage }, + rebaseRepair, + ); + const reviveBB = Change.revive(5, 2, { revision: tag2, localId: brand(5) }, rebaseRepair); + const expected = Change.revive( + 6, + 1, + { revision: tag1, localId: brand(6), lineage }, + rebaseRepair, + ); const actual = rebase(reviveA, reviveBB); assert.deepEqual(actual, expected); }); @@ -517,45 +607,65 @@ describe("SequenceField - Rebase", () => { { revision: tag2, id: brand(0), count: 1, offset: 1 }, { revision: tag3, id: brand(0), count: 1, offset: 1 }, ]; - const reviveAA = Change.revive(0, 2, tag1, brand(0), rebaseRepair, lineage); + const reviveAA = Change.revive( + 0, + 2, + { revision: tag1, localId: brand(0), lineage }, + rebaseRepair, + ); const reviveB = composeAnonChanges([ - Change.revive(0, 1, tag2, brand(0), rebaseRepair), - Change.revive(0, 1, tag3, brand(0), rebaseRepair), + Change.revive(0, 1, { revision: tag2, localId: brand(0) }, rebaseRepair), + Change.revive(0, 1, { revision: tag3, localId: brand(0) }, rebaseRepair), ]); - const expected = Change.revive(2, 2, tag1, brand(0), rebaseRepair, lineage); + const expected = Change.revive( + 2, + 2, + { revision: tag1, localId: brand(0), lineage }, + rebaseRepair, + ); const actual = rebase(reviveAA, reviveB); assert.deepEqual(actual, expected); }); it("intentional revive ↷ same revive", () => { - const reviveA = Change.intentionalRevive(0, 3, tag1, brand(1), rebaseRepair); - const reviveB = Change.revive(0, 1, tag1, brand(2), rebaseRepair); + const reviveA = Change.intentionalRevive( + 0, + 3, + { revision: tag1, localId: brand(1) }, + rebaseRepair, + ); + const reviveB = Change.revive(0, 1, { revision: tag1, localId: brand(2) }, rebaseRepair); const actual = rebase(reviveA, reviveB, tag2); const expected = composeAnonChanges([ - Change.intentionalRevive(0, 1, tag1, brand(1), rebaseRepair), - Change.redundantRevive(1, 1, tag1, brand(2), rebaseRepair, true), - Change.intentionalRevive(2, 1, tag1, brand(3), rebaseRepair), + Change.intentionalRevive(0, 1, { revision: tag1, localId: brand(1) }, rebaseRepair), + Change.redundantRevive(1, 1, { revision: tag1, localId: brand(2) }, rebaseRepair, true), + Change.intentionalRevive(2, 1, { revision: tag1, localId: brand(3) }, rebaseRepair), ]); assert.deepEqual(actual, expected); }); it("revive ↷ same revive (base within curr)", () => { - const reviveA = Change.revive(0, 3, tag1, brand(1), rebaseRepair); - const reviveB = Change.revive(0, 1, tag1, brand(2), rebaseRepair); + const reviveA = Change.revive(0, 3, { revision: tag1, localId: brand(1) }, rebaseRepair); + const reviveB = Change.revive(0, 1, { revision: tag1, localId: brand(2) }, rebaseRepair); const actual = rebase(reviveA, reviveB, tag2); const expected = composeAnonChanges([ - Change.revive(0, 1, tag1, brand(1), rebaseRepair), - Change.redundantRevive(1, 1, tag1, brand(2), rebaseRepair), - Change.revive(2, 1, tag1, brand(3), rebaseRepair), + Change.revive(0, 1, { revision: tag1, localId: brand(1) }, rebaseRepair), + Change.redundantRevive(1, 1, { revision: tag1, localId: brand(2) }, rebaseRepair), + Change.revive(2, 1, { revision: tag1, localId: brand(3) }, rebaseRepair), ]); assert.deepEqual(actual, expected); }); it("revive ↷ same revive (curr within base)", () => { - const reviveA = Change.revive(0, 1, tag1, brand(2), rebaseRepair); - const reviveB = Change.revive(0, 3, tag1, brand(1), rebaseRepair); + const reviveA = Change.revive(0, 1, { revision: tag1, localId: brand(2) }, rebaseRepair); + const reviveB = Change.revive(0, 3, { revision: tag1, localId: brand(1) }, rebaseRepair); const actual = rebase(reviveA, reviveB, tag2); - const expected = Change.redundantRevive(1, 1, tag1, brand(2), rebaseRepair); + const expected = Change.redundantRevive( + 1, + 1, + { revision: tag1, localId: brand(2) }, + rebaseRepair, + ); assert.deepEqual(actual, expected); }); diff --git a/experimental/dds/tree2/src/test/feature-libraries/sequence-field/sequenceChangeRebaser.spec.ts b/experimental/dds/tree2/src/test/feature-libraries/sequence-field/sequenceChangeRebaser.spec.ts index cd3544f8a45e..2e3d9b3fefaf 100644 --- a/experimental/dds/tree2/src/test/feature-libraries/sequence-field/sequenceChangeRebaser.spec.ts +++ b/experimental/dds/tree2/src/test/feature-libraries/sequence-field/sequenceChangeRebaser.spec.ts @@ -89,7 +89,11 @@ const testChanges: [string, (index: number, maxIndex: number) => SF.Changeset - Change.revive(2, 2, tag1, brand(i), undefined, generateLineage(tag1, brand(i), 2, max)), + Change.revive(2, 2, { + revision: tag1, + localId: brand(i), + lineage: generateLineage(tag1, brand(i), 2, max), + }), ], [ "TransientRevive", @@ -98,22 +102,35 @@ const testChanges: [string, (index: number, maxIndex: number) => SF.Changeset Change.redundantRevive(2, 2, tag2, brand(i), undefined)], + [ + "ConflictedRevive", + (i) => Change.redundantRevive(2, 2, { revision: tag2, localId: brand(i) }), + ], ["MoveOut", (i) => Change.move(i, 2, 1)], ["MoveIn", (i) => Change.move(1, 2, i)], [ "ReturnFrom", - (i, max) => Change.return(i, 2, 1, tag4, brand(i), generateLineage(tag4, brand(i), 2, max)), + (i, max) => + Change.return(i, 2, 1, { + revision: tag4, + localId: brand(i), + lineage: generateLineage(tag4, brand(i), 2, max), + }), ], [ "ReturnTo", - (i, max) => Change.return(1, 2, i, tag4, brand(i), generateLineage(tag4, brand(1), 2, max)), + (i, max) => + Change.return(1, 2, i, { + revision: tag4, + localId: brand(i), + lineage: generateLineage(tag4, brand(1), 2, max), + }), ], ]; deepFreeze(testChanges); @@ -340,7 +357,7 @@ describe("SequenceField - Sandwich Rebasing", () => { it("[Delete ABC, Revive ABC] ↷ Delete B", () => { const delB = tagChange(Change.delete(1, 1), tag1); const delABC = tagChange(Change.delete(0, 3), tag2); - const revABC = tagChange(Change.revive(0, 3, tag2, id0), tag4); + const revABC = tagChange(Change.revive(0, 3, { revision: tag2, localId: id0 }), tag4); const delABC2 = rebaseTagged(delABC, delB); const invDelABC = tagRollbackInverse(invert(delABC), tag3, delABC2.revision); const revABC2 = rebaseTagged(revABC, invDelABC); @@ -355,7 +372,7 @@ describe("SequenceField - Sandwich Rebasing", () => { it("[Move ABC, Return ABC] ↷ Delete B", () => { const delB = tagChange(Change.delete(1, 1), tag1); const movABC = tagChange(Change.move(0, 3, 1), tag2); - const retABC = tagChange(Change.return(1, 3, 0, tag2, id0), tag4); + const retABC = tagChange(Change.return(1, 3, 0, { revision: tag2, localId: id0 }), tag4); const movABC2 = rebaseTagged(movABC, delB); const invMovABC = invert(movABC); const retABC2 = rebaseTagged(retABC, tagRollbackInverse(invMovABC, tag3, movABC2.revision)); @@ -370,7 +387,7 @@ describe("SequenceField - Sandwich Rebasing", () => { it("[Delete AC, Revive AC] ↷ Insert B", () => { const addB = tagChange(Change.insert(1, 1), tag1); const delAC = tagChange(Change.delete(0, 2), tag2); - const revAC = tagChange(Change.revive(0, 2, tag2, id0), tag4); + const revAC = tagChange(Change.revive(0, 2, { revision: tag2, localId: id0 }), tag4); const delAC2 = rebaseTagged(delAC, addB); const invDelAC = invert(delAC); const revAC2 = rebaseTagged(revAC, tagRollbackInverse(invDelAC, tag3, delAC2.revision)); diff --git a/experimental/dds/tree2/src/test/feature-libraries/sequence-field/sequenceFieldEncoder.spec.ts b/experimental/dds/tree2/src/test/feature-libraries/sequence-field/sequenceFieldEncoder.spec.ts index 02db20d32062..66783d7a5a9d 100644 --- a/experimental/dds/tree2/src/test/feature-libraries/sequence-field/sequenceFieldEncoder.spec.ts +++ b/experimental/dds/tree2/src/test/feature-libraries/sequence-field/sequenceFieldEncoder.spec.ts @@ -16,7 +16,10 @@ const encodingTestData: EncodingTestData, unknown> = { successes: [ ["with child change", Change.modify(1, TestChange.mint([], 1))], ["without child change", Change.delete(2, 2)], - ["with repair data", Change.revive(0, 1, mintRevisionTag(), brand(10), fakeRepair)], + [ + "with repair data", + Change.revive(0, 1, { revision: mintRevisionTag(), localId: brand(10) }, fakeRepair), + ], // TODO: Include revive case here or in other encode/decode tests in this file. // It's likely we need a different notion of equality, as revive involves a ProtoNode type // and deep equality of that test case fails on comparing two `StackCursor`s. diff --git a/experimental/dds/tree2/src/test/feature-libraries/sequence-field/sequenceFieldToDelta.spec.ts b/experimental/dds/tree2/src/test/feature-libraries/sequence-field/sequenceFieldToDelta.spec.ts index f9b048e4729e..366edc8b1f89 100644 --- a/experimental/dds/tree2/src/test/feature-libraries/sequence-field/sequenceFieldToDelta.spec.ts +++ b/experimental/dds/tree2/src/test/feature-libraries/sequence-field/sequenceFieldToDelta.spec.ts @@ -93,7 +93,7 @@ describe("SequenceField - toDelta", () => { assert.equal(count, 1); return contentCursor; } - const changeset = Change.revive(0, 1, tag, brand(0), reviver); + const changeset = Change.revive(0, 1, { revision: tag, localId: brand(0) }, reviver); const actual = toDelta(changeset); const expected: Delta.MarkList = [ { @@ -117,7 +117,7 @@ describe("SequenceField - toDelta", () => { type: "Revive", content: contentCursor, count: 1, - detachEvent: { revision: tag, localId: brand(0) }, + cellId: { revision: tag, localId: brand(0) }, changes: nodeChange, }, ]; @@ -357,7 +357,7 @@ describe("SequenceField - toDelta", () => { type: "Delete", id: brand(0), count: 2, - detachEvent, + cellId: detachEvent, }, ]; @@ -367,7 +367,9 @@ describe("SequenceField - toDelta", () => { }); it("modify", () => { - const modify: TestChangeset = [{ type: "Modify", changes: childChange1, detachEvent }]; + const modify: TestChangeset = [ + { type: "Modify", changes: childChange1, cellId: detachEvent }, + ]; const actual = toDelta(modify); const expected: Delta.MarkList = []; @@ -378,7 +380,7 @@ describe("SequenceField - toDelta", () => { const move: TestChangeset = [ { type: "MoveIn", id: brand(0), count: 1, isSrcConflicted: true }, { count: 1 }, - { type: "MoveOut", id: brand(0), count: 1, detachEvent }, + { type: "MoveOut", id: brand(0), count: 1, cellId: detachEvent }, ]; const actual = toDelta(move); @@ -408,7 +410,7 @@ describe("SequenceField - toDelta", () => { count: 1, content: fakeRepairData(tag, 0, 1), inverseOf: tag1, - detachEvent: { revision: tag2, localId: brand(0) }, + cellId: { revision: tag2, localId: brand(0) }, }, { type: "Revive", @@ -416,7 +418,7 @@ describe("SequenceField - toDelta", () => { changes: childChange1, content: fakeRepairData(tag, 1, 1), inverseOf: tag1, - detachEvent: { revision: tag2, localId: brand(1) }, + cellId: { revision: tag2, localId: brand(1) }, }, ]; const actual = toDelta(changeset); diff --git a/experimental/dds/tree2/src/test/feature-libraries/sequence-field/testEdits.ts b/experimental/dds/tree2/src/test/feature-libraries/sequence-field/testEdits.ts index bc2af76571ee..6678900e0eae 100644 --- a/experimental/dds/tree2/src/test/feature-libraries/sequence-field/testEdits.ts +++ b/experimental/dds/tree2/src/test/feature-libraries/sequence-field/testEdits.ts @@ -39,9 +39,9 @@ export const cases: { createModifyChangeset(1, TestChange.mint([], 2)), ]), delete: createDeleteChangeset(1, 3), - revive: createReviveChangeset(2, 2, tag, brand(0)), + revive: createReviveChangeset(2, 2, { revision: tag, localId: brand(0) }), move: createMoveChangeset(1, 2, 2), - return: createReturnChangeset(1, 3, 0, tag, brand(0)), + return: createReturnChangeset(1, 3, 0, { revision: tag, localId: brand(0) }), }; function createInsertChangeset( @@ -75,32 +75,21 @@ function createRedundantRemoveChangeset( detachEvent: ChangeAtomId, ): SF.Changeset { const changeset = createDeleteChangeset(index, size); - (changeset[changeset.length - 1] as SF.Delete).detachEvent = detachEvent; + (changeset[changeset.length - 1] as SF.Delete).cellId = detachEvent; return changeset; } function createReviveChangeset( startIndex: number, count: number, - detachedBy: RevisionTag, - detachId?: ChangesetLocalId, + detachEvent: SF.CellId, reviver = fakeRepair, - lineage?: SF.LineageEvent[], - lastDetach?: ChangeAtomId, + lastDetach?: SF.CellId, ): SF.Changeset { - const markList = SF.sequenceFieldEditor.revive( - startIndex, - count, - detachedBy, - detachId ?? brand(0), - reviver, - ); + const markList = SF.sequenceFieldEditor.revive(startIndex, count, detachEvent, reviver); const mark = markList[markList.length - 1] as SF.Reattach; if (lastDetach !== undefined) { - mark.detachEvent = lastDetach; - } - if (lineage !== undefined) { - mark.lineage = lineage; + mark.cellId = lastDetach; } return markList; } @@ -108,74 +97,49 @@ function createReviveChangeset( function createRedundantReviveChangeset( startIndex: number, count: number, - detachedBy: RevisionTag, - detachId: ChangesetLocalId, + detachEvent: SF.CellId, reviver = fakeRepair, isIntention?: boolean, ): SF.Changeset { const markList = SF.sequenceFieldEditor.revive( startIndex, count, - detachedBy, - detachId, + detachEvent, reviver, isIntention, ); const mark = markList[markList.length - 1] as SF.Reattach; - delete mark.detachEvent; + delete mark.cellId; return markList; } function createBlockedReviveChangeset( startIndex: number, count: number, - inverseOf: RevisionTag, - lastDetachedBy: RevisionTag, - lastDetachId: ChangesetLocalId, + detachEvent: SF.CellId, + lastDetach: SF.CellId, reviver = fakeRepair, - lineage?: SF.LineageEvent[], ): SF.Changeset { - const markList = SF.sequenceFieldEditor.revive( - startIndex, - count, - inverseOf, - lastDetachId, - reviver, - ); + const markList = SF.sequenceFieldEditor.revive(startIndex, count, detachEvent, reviver); const mark = markList[markList.length - 1] as SF.Reattach; - mark.detachEvent = { revision: lastDetachedBy, localId: lastDetachId }; - if (lineage !== undefined) { - mark.lineage = lineage; - } + mark.cellId = lastDetach; return markList; } function createIntentionalReviveChangeset( startIndex: number, count: number, - detachedBy: RevisionTag, - detachId: ChangesetLocalId, + detachEvent: SF.CellId, reviver = fakeRepair, - lineage?: SF.LineageEvent[], - lastDetach?: ChangeAtomId, + lastDetach?: SF.CellId, ): SF.Changeset { - const markList = SF.sequenceFieldEditor.revive( - startIndex, - count, - detachedBy, - detachId, - reviver, - true, - ); + const markList = SF.sequenceFieldEditor.revive(startIndex, count, detachEvent, reviver, true); const mark = markList[markList.length - 1] as SF.Reattach; if (lastDetach !== undefined) { - mark.detachEvent = lastDetach; + mark.cellId = lastDetach; } - if (lineage !== undefined) { - mark.lineage = lineage; - } return markList; } @@ -194,18 +158,9 @@ function createReturnChangeset( sourceIndex: number, count: number, destIndex: number, - detachedBy: RevisionTag, - detachId: ChangesetLocalId, - lineage?: SF.LineageEvent[], + detachEvent: SF.CellId, ): SF.Changeset { - return SF.sequenceFieldEditor.return( - sourceIndex, - count, - destIndex, - detachedBy, - detachId, - lineage, - ); + return SF.sequenceFieldEditor.return(sourceIndex, count, destIndex, detachEvent); } function createModifyChangeset( @@ -218,15 +173,11 @@ function createModifyChangeset( function createModifyDetachedChangeset( index: number, change: TNodeChange, - detachEvent: ChangeAtomId, - lineage?: SF.LineageEvent[], + detachEvent: SF.CellId, ): SF.Changeset { const changeset = createModifyChangeset(index, change); const modify = changeset[changeset.length - 1] as SF.Modify; - modify.detachEvent = detachEvent; - if (lineage !== undefined) { - modify.lineage = lineage; - } + modify.cellId = detachEvent; return changeset; }