Skip to content

Commit

Permalink
add new op type for sided obliterates (microsoft#22596)
Browse files Browse the repository at this point in the history
This PR introduces the new `MergeTreeDeltaType.OBLITERATE_SIDED`, which
uses objects for `pos1` and `pos2` with side information. This is
necessary for obliterate endpoint expansion, which will be implemented
in a follow-up PR.

See microsoft#22552 for a functional prototype of endpoint expansion.
  • Loading branch information
titrindl authored Sep 24, 2024
1 parent 802b417 commit 62f5516
Show file tree
Hide file tree
Showing 10 changed files with 175 additions and 25 deletions.
23 changes: 21 additions & 2 deletions packages/dds/merge-tree/api-report/merge-tree.legacy.alpha.api.md
Original file line number Diff line number Diff line change
Expand Up @@ -144,7 +144,7 @@ export class Client extends TypedEventEmitter<IClientEvents> {
readonly logger: ITelemetryLoggerExt;
// (undocumented)
longClientId: string | undefined;
obliterateRangeLocal(start: number, end: number): IMergeTreeObliterateMsg;
obliterateRangeLocal(start: number | InteriorSequencePlace, end: number | InteriorSequencePlace): IMergeTreeObliterateMsg | IMergeTreeObliterateSidedMsg;
peekPendingSegmentGroups(): SegmentGroup | undefined;
// (undocumented)
peekPendingSegmentGroups(count: number): SegmentGroup | SegmentGroup[] | undefined;
Expand Down Expand Up @@ -320,7 +320,7 @@ export interface IMergeTreeDeltaCallbackArgs<TOperationType extends MergeTreeDel
}

// @alpha (undocumented)
export type IMergeTreeDeltaOp = IMergeTreeInsertMsg | IMergeTreeRemoveMsg | IMergeTreeAnnotateMsg | IMergeTreeObliterateMsg;
export type IMergeTreeDeltaOp = IMergeTreeInsertMsg | IMergeTreeRemoveMsg | IMergeTreeAnnotateMsg | IMergeTreeObliterateMsg | IMergeTreeObliterateSidedMsg;

// @alpha (undocumented)
export interface IMergeTreeDeltaOpArgs {
Expand Down Expand Up @@ -369,6 +369,24 @@ export interface IMergeTreeObliterateMsg extends IMergeTreeDelta {
type: typeof MergeTreeDeltaType.OBLITERATE;
}

// @alpha (undocumented)
export interface IMergeTreeObliterateSidedMsg extends IMergeTreeDelta {
// (undocumented)
pos1: {
pos: number;
before: boolean;
};
// (undocumented)
pos2: {
pos: number;
before: boolean;
};
relativePos1?: never;
relativePos2?: never;
// (undocumented)
type: typeof MergeTreeDeltaType.OBLITERATE_SIDED;
}

// @alpha (undocumented)
export type IMergeTreeOp = IMergeTreeDeltaOp | IMergeTreeGroupMsg;

Expand Down Expand Up @@ -615,6 +633,7 @@ export const MergeTreeDeltaType: {
readonly ANNOTATE: 2;
readonly GROUP: 3;
readonly OBLITERATE: 4;
readonly OBLITERATE_SIDED: 5;
};

// @alpha (undocumented)
Expand Down
15 changes: 14 additions & 1 deletion packages/dds/merge-tree/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -176,7 +176,20 @@
"typescript": "~5.4.5"
},
"typeValidation": {
"broken": {},
"broken": {
"Interface_IMergeTreeDeltaOpArgs": {
"backCompat": false
},
"TypeAlias_IMergeTreeDeltaOp": {
"backCompat": false
},
"Interface_IMergeTreeGroupMsg": {
"backCompat": false
},
"TypeAlias_IMergeTreeOp": {
"backCompat": false
}
},
"entrypoint": "internal"
}
}
79 changes: 60 additions & 19 deletions packages/dds/merge-tree/src/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ import {
createGroupOp,
createInsertSegmentOp,
createObliterateRangeOp,
createObliterateRangeOpSided,
createRemoveRangeOp,
} from "./opBuilder.js";
import {
Expand All @@ -72,9 +73,11 @@ import {
IRelativePosition,
MergeTreeDeltaType,
ReferenceType,
type IMergeTreeObliterateSidedMsg,
} from "./ops.js";
import { PropertySet } from "./properties.js";
import { DetachedReferencePosition, ReferencePosition } from "./referencePositions.js";
import { type InteriorSequencePlace } from "./sequencePlace.js";
import { SnapshotLoader } from "./snapshotLoader.js";
import { SnapshotV1 } from "./snapshotV1.js";
import { SnapshotLegacy } from "./snapshotlegacy.js";
Expand Down Expand Up @@ -252,15 +255,26 @@ export class Client extends TypedEventEmitter<IClientEvents> {
* Obliterates the range. This is similar to removing the range, but also
* includes any concurrently inserted content.
*
* @param start - The inclusive start of the range to obliterate
* @param end - The exclusive end of the range to obliterate
* @param start - The start of the range to obliterate. Inclusive is side is Before (default).
* @param end - The end of the range to obliterate. Exclusive is side is After
* (default is to be after the last included character, but number index is exclusive).
*/
public obliterateRangeLocal(
start: number,
end: number,
start: number | InteriorSequencePlace,
end: number | InteriorSequencePlace,
// eslint-disable-next-line import/no-deprecated
): IMergeTreeObliterateMsg {
const obliterateOp = createObliterateRangeOp(start, end);
): IMergeTreeObliterateMsg | IMergeTreeObliterateSidedMsg {
// eslint-disable-next-line import/no-deprecated
let obliterateOp: IMergeTreeObliterateMsg | IMergeTreeObliterateSidedMsg;
if (this._mergeTree.options?.mergeTreeEnableSidedObliterate) {
obliterateOp = createObliterateRangeOpSided(start, end);
} else {
assert(
typeof start === "number" && typeof end === "number",
"Start and end must be numbers if mergeTreeEnableSidedObliterate is not enabled.",
);
obliterateOp = createObliterateRangeOp(start, end);
}
this.applyObliterateRangeOp({ op: obliterateOp });
return obliterateOp;
}
Expand Down Expand Up @@ -472,22 +486,40 @@ export class Client extends TypedEventEmitter<IClientEvents> {

private applyObliterateRangeOp(opArgs: IMergeTreeDeltaOpArgs): void {
assert(
opArgs.op.type === MergeTreeDeltaType.OBLITERATE,
opArgs.op.type === MergeTreeDeltaType.OBLITERATE ||
opArgs.op.type === MergeTreeDeltaType.OBLITERATE_SIDED,
0x866 /* Unexpected op type on range obliterate! */,
);
const op = opArgs.op;
const clientArgs = this.getClientSequenceArgs(opArgs);
const range = this.getValidOpRange(op, clientArgs);

this._mergeTree.obliterateRange(
range.start,
range.end,
clientArgs.referenceSequenceNumber,
clientArgs.clientId,
clientArgs.sequenceNumber,
false,
opArgs,
);
if (this._mergeTree.options?.mergeTreeEnableSidedObliterate) {
/*
const _start: InteriorSequencePlace =
typeof op.pos1 === "object"
? { pos: op.pos1.pos, side: op.pos1.before ? Side.Before : Side.After }
: { pos: op.pos1, side: Side.Before };
const _end: InteriorSequencePlace =
typeof op.pos2 === "object"
? { pos: op.pos2.pos, side: op.pos2.before ? Side.Before : Side.After }
: { pos: op.pos2 - 1, side: Side.After };
*/
assert(false, "TODO: sided obliterate will come in a follow-up PR shortly.");
} else {
assert(
op.type === MergeTreeDeltaType.OBLITERATE,
"Unexpected sided obliterate while mergeTreeEnableSidedObliterate is disabled",
);
const range = this.getValidOpRange(op, clientArgs);
this._mergeTree.obliterateRange(
range.start,
range.end,
clientArgs.referenceSequenceNumber,
clientArgs.clientId,
clientArgs.sequenceNumber,
false,
opArgs,
);
}
}

/**
Expand Down Expand Up @@ -891,7 +923,12 @@ export class Client extends TypedEventEmitter<IClientEvents> {

const first = opList[0];

if (!!first && first.pos2 !== undefined) {
if (
!!first &&
first.pos2 !== undefined &&
first.type !== MergeTreeDeltaType.OBLITERATE_SIDED &&
newOp.type !== MergeTreeDeltaType.OBLITERATE_SIDED
) {
first.pos2 += newOp.pos2! - newOp.pos1!;
} else {
opList.push(newOp);
Expand Down Expand Up @@ -976,6 +1013,10 @@ export class Client extends TypedEventEmitter<IClientEvents> {
this.applyObliterateRangeOp({ op });
break;
}
case MergeTreeDeltaType.OBLITERATE_SIDED: {
assert(false, "TODO: sided obliterate will come in a follow-up PR shortly.");
break;
}
case MergeTreeDeltaType.GROUP: {
op.ops.map((o) => this.applyStashedOp(o));
break;
Expand Down
1 change: 1 addition & 0 deletions packages/dds/merge-tree/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,7 @@ export {
MergeTreeDeltaType,
ReferenceType,
IMergeTreeObliterateMsg,
IMergeTreeObliterateSidedMsg,
} from "./ops.js";
export {
addProperties,
Expand Down
32 changes: 32 additions & 0 deletions packages/dds/merge-tree/src/opBuilder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,10 @@ import {
IMergeTreeObliterateMsg,
IMergeTreeRemoveMsg,
MergeTreeDeltaType,
type IMergeTreeObliterateSidedMsg,
} from "./ops.js";
import { PropertySet } from "./properties.js";
import { normalizePlace, Side, type SequencePlace } from "./sequencePlace.js";

/**
* Creates the op for annotating the markers with the provided properties
Expand Down Expand Up @@ -97,6 +99,36 @@ export function createObliterateRangeOp(start: number, end: number): IMergeTreeO
};
}

/**
* Creates the op to obliterate a range
*
* @param start - The start of the range to obliterate.
* If a number is provided, the range will start before that index.
* @param end - The end of the range to obliterate.
* If a number is provided, the range will end after that index -1.
* This preserves the previous behavior of not expanding obliteration ranges at the endpoints
* for uses which predate the availability of endpoint expansion.
*
* @internal
*/
export function createObliterateRangeOpSided(
start: SequencePlace,
end: SequencePlace,
): IMergeTreeObliterateSidedMsg {
const startPlace = normalizePlace(start);
// If a number is provided, default to after the previous index.
// This preserves the behavior of obliterate prior to the introduction of endpoint expansion.
const endPlace =
typeof end === "number"
? { pos: end - 1, side: Side.After } // default to inclusive bounds
: normalizePlace(end);
return {
type: MergeTreeDeltaType.OBLITERATE_SIDED,
pos1: { pos: startPlace.pos, before: startPlace.side === Side.Before },
pos2: { pos: endPlace.pos, before: endPlace.side === Side.Before },
};
}

/**
* Creates an op for inserting a segment at the specified position.
*
Expand Down
24 changes: 23 additions & 1 deletion packages/dds/merge-tree/src/ops.ts
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ export const MergeTreeDeltaType = {
*/
GROUP: 3,
OBLITERATE: 4,
OBLITERATE_SIDED: 5,
} as const;

/**
Expand Down Expand Up @@ -162,6 +163,26 @@ export interface IMergeTreeObliterateMsg extends IMergeTreeDelta {
relativePos2?: never;
}

/**
* @legacy
* @alpha
*/
export interface IMergeTreeObliterateSidedMsg extends IMergeTreeDelta {
type: typeof MergeTreeDeltaType.OBLITERATE_SIDED;
pos1: { pos: number; before: boolean };
/**
* This field is currently unused, but we keep it around to make the union
* type of all merge-tree messages have the same fields
*/
relativePos1?: never;
pos2: { pos: number; before: boolean };
/**
* This field is currently unused, but we keep it around to make the union
* type of all merge-tree messages have the same fields
*/
relativePos2?: never;
}

/**
* @legacy
* @alpha
Expand Down Expand Up @@ -206,7 +227,8 @@ export type IMergeTreeDeltaOp =
| IMergeTreeInsertMsg
| IMergeTreeRemoveMsg
| IMergeTreeAnnotateMsg
| IMergeTreeObliterateMsg;
| IMergeTreeObliterateMsg
| IMergeTreeObliterateSidedMsg;

/**
* @legacy
Expand Down
3 changes: 2 additions & 1 deletion packages/dds/merge-tree/src/test/reconnectHelper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import {
IMergeTreeDeltaOp,
type IMergeTreeInsertMsg,
type IMergeTreeObliterateMsg,
type IMergeTreeObliterateSidedMsg,
type IMergeTreeRemoveMsg,
} from "../ops.js";

Expand Down Expand Up @@ -121,7 +122,7 @@ export class ReconnectTestHelper {
start: SequencePlace,
end: SequencePlace,
): {
op: IMergeTreeObliterateMsg;
op: IMergeTreeObliterateMsg | IMergeTreeObliterateSidedMsg;
seg: SegmentGroup;
refSeq: number;
} {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -967,6 +967,7 @@ declare type old_as_current_for_Interface_IMergeTreeDeltaOpArgs = requireAssigna
* typeValidation.broken:
* "Interface_IMergeTreeDeltaOpArgs": {"backCompat": false}
*/
// @ts-expect-error compatibility expected to be broken
declare type current_as_old_for_Interface_IMergeTreeDeltaOpArgs = requireAssignableTo<TypeOnly<current.IMergeTreeDeltaOpArgs>, TypeOnly<old.IMergeTreeDeltaOpArgs>>

/*
Expand All @@ -985,6 +986,7 @@ declare type old_as_current_for_Interface_IMergeTreeGroupMsg = requireAssignable
* typeValidation.broken:
* "Interface_IMergeTreeGroupMsg": {"backCompat": false}
*/
// @ts-expect-error compatibility expected to be broken
declare type current_as_old_for_Interface_IMergeTreeGroupMsg = requireAssignableTo<TypeOnly<current.IMergeTreeGroupMsg>, TypeOnly<old.IMergeTreeGroupMsg>>

/*
Expand Down Expand Up @@ -1570,6 +1572,7 @@ declare type old_as_current_for_TypeAlias_IMergeTreeDeltaOp = requireAssignableT
* typeValidation.broken:
* "TypeAlias_IMergeTreeDeltaOp": {"backCompat": false}
*/
// @ts-expect-error compatibility expected to be broken
declare type current_as_old_for_TypeAlias_IMergeTreeDeltaOp = requireAssignableTo<TypeOnly<current.IMergeTreeDeltaOp>, TypeOnly<old.IMergeTreeDeltaOp>>

/*
Expand All @@ -1588,6 +1591,7 @@ declare type old_as_current_for_TypeAlias_IMergeTreeOp = requireAssignableTo<Typ
* typeValidation.broken:
* "TypeAlias_IMergeTreeOp": {"backCompat": false}
*/
// @ts-expect-error compatibility expected to be broken
declare type current_as_old_for_TypeAlias_IMergeTreeOp = requireAssignableTo<TypeOnly<current.IMergeTreeOp>, TypeOnly<old.IMergeTreeOp>>

/*
Expand Down
15 changes: 14 additions & 1 deletion packages/dds/sequence/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -191,7 +191,20 @@
}
},
"typeValidation": {
"broken": {},
"broken": {
"ClassStatics_SequenceMaintenanceEvent": {
"backCompat": false
},
"Class_SequenceMaintenanceEvent": {
"backCompat": false
},
"ClassStatics_SequenceDeltaEvent": {
"backCompat": false
},
"Class_SequenceDeltaEvent": {
"backCompat": false
}
},
"entrypoint": "internal"
}
}
Loading

0 comments on commit 62f5516

Please sign in to comment.