Skip to content

Commit

Permalink
Tree: Include lineage as part of cell IDs (#16434)
Browse files Browse the repository at this point in the history
## 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>
  • Loading branch information
alex-pardes and yann-achard-MS authored Jul 19, 2023
1 parent aac3ad3 commit a67a0d2
Show file tree
Hide file tree
Showing 18 changed files with 654 additions and 534 deletions.
1 change: 0 additions & 1 deletion api-report/tree2.api.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ import {
Delta,
UpPath,
ITreeCursor,
RevisionTag,
ChangeFamilyEditor,
FieldUpPath,
} from "../../core";
Expand All @@ -21,8 +20,6 @@ import {
ModularEditBuilder,
FieldChangeset,
ModularChangeset,
NodeReviver,
ChangesetLocalId,
} from "../modular-schema";
import { fieldKinds, optional, sequence, value as valueFieldKind } from "./defaultFieldKinds";

Expand Down Expand Up @@ -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);
},
};
}
}
Expand Down Expand Up @@ -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;
}
1 change: 1 addition & 0 deletions experimental/dds/tree2/src/feature-libraries/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,7 @@ export {
NodeExistsConstraint,
NodeExistenceState,
BrandedFieldKind,
ChangeAtomId,
} from "./modular-schema";

export {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import {
Modify,
MoveId,
NoopMarkType,
CellId,
} from "./format";
import { MarkListFactory } from "./markListFactory";
import { MarkQueue } from "./markQueue";
Expand Down Expand Up @@ -192,10 +193,7 @@ function composeMarks<TNodeChange>(
// 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") {
Expand Down Expand Up @@ -363,7 +361,7 @@ function createModifyMark<TNodeChange>(
assert(length === 1, 0x692 /* A mark with a node change must have length one */);
const mark: Modify<TNodeChange> = { type: "Modify", changes: nodeChange };
if (cellId !== undefined) {
mark.detachEvent = cellId;
mark.cellId = cellId;
}
return mark;
}
Expand Down Expand Up @@ -585,7 +583,7 @@ export class ComposeQueue<T> {
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,
Expand Down Expand Up @@ -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;
}

Expand All @@ -826,14 +824,15 @@ function areInverseMovesAtIntermediateLocation(
* are before the first cell of `newMark`.
*/
function compareCellPositions(
baseCellId: ChangeAtomId,
baseCellId: CellId,
baseMark: Mark<unknown>,
newMark: EmptyInputCellMark<unknown>,
newIntention: RevisionTag | undefined,
cancelledInserts: Set<RevisionTag>,
): 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
Expand All @@ -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;
}
Expand Down
Loading

0 comments on commit a67a0d2

Please sign in to comment.