@fluidframework/sequence 2.43.0-343119 → 2.50.0-345060
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +21 -0
- package/api-report/sequence.legacy.alpha.api.md +2 -2
- package/dist/intervalCollection.d.ts +4 -9
- package/dist/intervalCollection.d.ts.map +1 -1
- package/dist/intervalCollection.js +98 -84
- package/dist/intervalCollection.js.map +1 -1
- package/dist/intervalCollectionMap.d.ts +1 -1
- package/dist/intervalCollectionMap.d.ts.map +1 -1
- package/dist/intervalCollectionMap.js +2 -2
- package/dist/intervalCollectionMap.js.map +1 -1
- package/dist/intervalCollectionMapInterfaces.d.ts +14 -6
- package/dist/intervalCollectionMapInterfaces.d.ts.map +1 -1
- package/dist/intervalCollectionMapInterfaces.js.map +1 -1
- package/dist/intervals/intervalUtils.d.ts +4 -3
- package/dist/intervals/intervalUtils.d.ts.map +1 -1
- package/dist/intervals/intervalUtils.js +16 -3
- package/dist/intervals/intervalUtils.js.map +1 -1
- package/dist/intervals/sequenceInterval.d.ts +21 -7
- package/dist/intervals/sequenceInterval.d.ts.map +1 -1
- package/dist/intervals/sequenceInterval.js +88 -16
- package/dist/intervals/sequenceInterval.js.map +1 -1
- package/dist/packageVersion.d.ts +1 -1
- package/dist/packageVersion.js +1 -1
- package/dist/packageVersion.js.map +1 -1
- package/dist/sequence.js +1 -1
- package/dist/sequence.js.map +1 -1
- package/lib/intervalCollection.d.ts +4 -9
- package/lib/intervalCollection.d.ts.map +1 -1
- package/lib/intervalCollection.js +99 -83
- package/lib/intervalCollection.js.map +1 -1
- package/lib/intervalCollectionMap.d.ts +1 -1
- package/lib/intervalCollectionMap.d.ts.map +1 -1
- package/lib/intervalCollectionMap.js +2 -2
- package/lib/intervalCollectionMap.js.map +1 -1
- package/lib/intervalCollectionMapInterfaces.d.ts +14 -6
- package/lib/intervalCollectionMapInterfaces.d.ts.map +1 -1
- package/lib/intervalCollectionMapInterfaces.js.map +1 -1
- package/lib/intervals/intervalUtils.d.ts +4 -3
- package/lib/intervals/intervalUtils.d.ts.map +1 -1
- package/lib/intervals/intervalUtils.js +15 -3
- package/lib/intervals/intervalUtils.js.map +1 -1
- package/lib/intervals/sequenceInterval.d.ts +21 -7
- package/lib/intervals/sequenceInterval.d.ts.map +1 -1
- package/lib/intervals/sequenceInterval.js +88 -16
- package/lib/intervals/sequenceInterval.js.map +1 -1
- package/lib/packageVersion.d.ts +1 -1
- package/lib/packageVersion.js +1 -1
- package/lib/packageVersion.js.map +1 -1
- package/lib/sequence.js +1 -1
- package/lib/sequence.js.map +1 -1
- package/package.json +19 -19
- package/src/intervalCollection.ts +128 -145
- package/src/intervalCollectionMap.ts +6 -2
- package/src/intervalCollectionMapInterfaces.ts +12 -5
- package/src/intervals/intervalUtils.ts +31 -3
- package/src/intervals/sequenceInterval.ts +135 -72
- package/src/packageVersion.ts +1 -1
- package/src/sequence.ts +1 -1
|
@@ -7,16 +7,19 @@
|
|
|
7
7
|
|
|
8
8
|
import { TypedEventEmitter } from "@fluid-internal/client-utils";
|
|
9
9
|
import { IEvent } from "@fluidframework/core-interfaces";
|
|
10
|
-
import {
|
|
10
|
+
import {
|
|
11
|
+
assert,
|
|
12
|
+
DoublyLinkedList,
|
|
13
|
+
unreachableCase,
|
|
14
|
+
type ListNode,
|
|
15
|
+
} from "@fluidframework/core-utils/internal";
|
|
11
16
|
import { ISequencedDocumentMessage } from "@fluidframework/driver-definitions/internal";
|
|
12
17
|
import {
|
|
13
18
|
Client,
|
|
14
|
-
DetachedReferencePosition,
|
|
15
19
|
ISegment,
|
|
16
20
|
LocalReferencePosition,
|
|
17
21
|
PropertySet,
|
|
18
22
|
ReferenceType,
|
|
19
|
-
SlidingPreference,
|
|
20
23
|
getSlideToSegoff,
|
|
21
24
|
refTypeIncludesFlag,
|
|
22
25
|
reservedRangeLabelsKey,
|
|
@@ -25,8 +28,7 @@ import {
|
|
|
25
28
|
endpointPosAndSide,
|
|
26
29
|
type ISegmentInternal,
|
|
27
30
|
createLocalReconnectingPerspective,
|
|
28
|
-
|
|
29
|
-
type ListNode,
|
|
31
|
+
SlidingPreference,
|
|
30
32
|
} from "@fluidframework/merge-tree/internal";
|
|
31
33
|
import { LoggingError, UsageError } from "@fluidframework/telemetry-utils/internal";
|
|
32
34
|
import { v4 as uuid } from "uuid";
|
|
@@ -57,9 +59,7 @@ import {
|
|
|
57
59
|
SerializedIntervalDelta,
|
|
58
60
|
createPositionReferenceFromSegoff,
|
|
59
61
|
createSequenceInterval,
|
|
60
|
-
endReferenceSlidingPreference,
|
|
61
62
|
getSerializedProperties,
|
|
62
|
-
startReferenceSlidingPreference,
|
|
63
63
|
} from "./intervals/index.js";
|
|
64
64
|
|
|
65
65
|
export type ISerializedIntervalCollectionV1 = ISerializedInterval[];
|
|
@@ -70,7 +70,7 @@ export interface ISerializedIntervalCollectionV2 {
|
|
|
70
70
|
intervals: CompressedSerializedInterval[];
|
|
71
71
|
}
|
|
72
72
|
|
|
73
|
-
|
|
73
|
+
function sidesFromStickiness(stickiness: IntervalStickiness) {
|
|
74
74
|
const startSide = (stickiness & IntervalStickiness.START) !== 0 ? Side.After : Side.Before;
|
|
75
75
|
const endSide = (stickiness & IntervalStickiness.END) !== 0 ? Side.Before : Side.After;
|
|
76
76
|
|
|
@@ -138,25 +138,6 @@ export function toOptionalSequencePlace(
|
|
|
138
138
|
return typeof pos === "number" && side !== undefined ? { pos, side } : pos;
|
|
139
139
|
}
|
|
140
140
|
|
|
141
|
-
export function computeStickinessFromSide(
|
|
142
|
-
startPos: number | "start" | "end" | undefined = -1,
|
|
143
|
-
startSide: Side = Side.Before,
|
|
144
|
-
endPos: number | "start" | "end" | undefined = -1,
|
|
145
|
-
endSide: Side = Side.Before,
|
|
146
|
-
): IntervalStickiness {
|
|
147
|
-
let stickiness: IntervalStickiness = IntervalStickiness.NONE;
|
|
148
|
-
|
|
149
|
-
if (startSide === Side.After || startPos === "start") {
|
|
150
|
-
stickiness |= IntervalStickiness.START;
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
if (endSide === Side.Before || endPos === "end") {
|
|
154
|
-
stickiness |= IntervalStickiness.END;
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
return stickiness as IntervalStickiness;
|
|
158
|
-
}
|
|
159
|
-
|
|
160
141
|
export class LocalIntervalCollection {
|
|
161
142
|
public readonly overlappingIntervalsIndex: ISequenceOverlappingIntervalsIndex;
|
|
162
143
|
public readonly idIntervalIndex: IIdIntervalIndex;
|
|
@@ -721,25 +702,27 @@ function removeMetadataFromPendingChanges(
|
|
|
721
702
|
): IntervalMessageLocalMetadata {
|
|
722
703
|
const acked = (localOpMetadataNode as ListNode<IntervalMessageLocalMetadata>)?.remove()
|
|
723
704
|
?.data;
|
|
724
|
-
assert(acked !== undefined,
|
|
705
|
+
assert(acked !== undefined, 0xbbe /* local change must exist */);
|
|
725
706
|
acked.endpointChangesNode?.remove();
|
|
726
707
|
return acked;
|
|
727
708
|
}
|
|
728
709
|
|
|
729
710
|
function clearEmptyPendingEntry(pendingChanges: PendingChanges, id: string) {
|
|
730
711
|
const pending = pendingChanges[id];
|
|
731
|
-
assert(pending !== undefined,
|
|
712
|
+
assert(pending !== undefined, 0xbbf /* pending must exist for local process */);
|
|
732
713
|
if (pending.local.empty) {
|
|
733
714
|
assert(
|
|
734
715
|
pending.endpointChanges?.empty !== false,
|
|
735
|
-
|
|
716
|
+
0xbc0 /* endpointChanges must be empty if not pending changes */,
|
|
736
717
|
);
|
|
737
718
|
// eslint-disable-next-line @typescript-eslint/no-dynamic-delete
|
|
738
719
|
delete pendingChanges[id];
|
|
739
720
|
}
|
|
740
721
|
}
|
|
741
722
|
|
|
742
|
-
function hasEndpointChanges(
|
|
723
|
+
function hasEndpointChanges(
|
|
724
|
+
serialized: SerializedIntervalDelta,
|
|
725
|
+
): serialized is ISerializedInterval {
|
|
743
726
|
return serialized.start !== undefined && serialized.end !== undefined;
|
|
744
727
|
}
|
|
745
728
|
|
|
@@ -781,6 +764,7 @@ export class IntervalCollection
|
|
|
781
764
|
if (md.type === "add" || (md.type === "change" && hasEndpointChanges(op.value))) {
|
|
782
765
|
const endpointChanges = (pending.endpointChanges ??= new DoublyLinkedList());
|
|
783
766
|
md.endpointChangesNode = endpointChanges.push(md).last;
|
|
767
|
+
md.rebased = undefined;
|
|
784
768
|
}
|
|
785
769
|
submitDelta(op, pending.local.push(md).last);
|
|
786
770
|
};
|
|
@@ -887,7 +871,7 @@ export class IntervalCollection
|
|
|
887
871
|
const { opName, value } = op;
|
|
888
872
|
assert(
|
|
889
873
|
(local === false && localOpMetadata === undefined) || opName === localOpMetadata?.type,
|
|
890
|
-
|
|
874
|
+
0xbc1 /* must be same type */,
|
|
891
875
|
);
|
|
892
876
|
switch (opName) {
|
|
893
877
|
case "add": {
|
|
@@ -924,6 +908,7 @@ export class IntervalCollection
|
|
|
924
908
|
public resubmitMessage(
|
|
925
909
|
op: IIntervalCollectionTypeOperationValue,
|
|
926
910
|
maybeMetadata: unknown,
|
|
911
|
+
squash: boolean,
|
|
927
912
|
): void {
|
|
928
913
|
const { opName, value } = op;
|
|
929
914
|
|
|
@@ -932,7 +917,7 @@ export class IntervalCollection
|
|
|
932
917
|
const rebasedValue =
|
|
933
918
|
localOpMetadata.endpointChangesNode === undefined
|
|
934
919
|
? value
|
|
935
|
-
: this.rebaseLocalInterval(localOpMetadata);
|
|
920
|
+
: this.rebaseLocalInterval(value, localOpMetadata, squash);
|
|
936
921
|
|
|
937
922
|
if (rebasedValue === undefined) {
|
|
938
923
|
const { id } = getSerializedProperties(value);
|
|
@@ -974,69 +959,69 @@ export class IntervalCollection
|
|
|
974
959
|
}
|
|
975
960
|
}
|
|
976
961
|
|
|
977
|
-
private
|
|
978
|
-
|
|
979
|
-
seqNumberFrom: number,
|
|
962
|
+
private rebaseReferenceWithSegmentSlide(
|
|
963
|
+
ref: LocalReferencePosition,
|
|
980
964
|
localSeq: number,
|
|
981
|
-
|
|
965
|
+
squash: boolean,
|
|
966
|
+
): { segment: ISegment; offset: number } | undefined {
|
|
982
967
|
if (!this.client) {
|
|
983
968
|
throw new LoggingError("mergeTree client must exist");
|
|
984
969
|
}
|
|
985
970
|
|
|
986
|
-
if (pos === "start" || pos === "end") {
|
|
987
|
-
return pos;
|
|
988
|
-
}
|
|
989
|
-
|
|
990
971
|
const { clientId } = this.client.getCollabWindow();
|
|
991
|
-
const
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
clientId: this.client.getLongClientId(clientId),
|
|
997
|
-
},
|
|
998
|
-
localSeq,
|
|
999
|
-
) ?? {};
|
|
1000
|
-
|
|
1001
|
-
// if segment is undefined, it slid off the string
|
|
1002
|
-
assert(segment !== undefined && offset !== undefined, 0x54e /* No segment found */);
|
|
972
|
+
const segment: ISegmentInternal | undefined = ref.getSegment();
|
|
973
|
+
if (segment?.endpointType) {
|
|
974
|
+
return { segment, offset: 0 };
|
|
975
|
+
}
|
|
976
|
+
const offset = ref.getOffset();
|
|
1003
977
|
|
|
1004
978
|
const segoff = getSlideToSegoff(
|
|
1005
|
-
{ segment, offset },
|
|
1006
|
-
|
|
1007
|
-
createLocalReconnectingPerspective(
|
|
1008
|
-
|
|
979
|
+
segment === undefined ? undefined : { segment, offset },
|
|
980
|
+
ref.slidingPreference,
|
|
981
|
+
createLocalReconnectingPerspective(
|
|
982
|
+
this.client.getCurrentSeq(),
|
|
983
|
+
clientId,
|
|
984
|
+
localSeq,
|
|
985
|
+
squash,
|
|
986
|
+
),
|
|
987
|
+
ref.canSlideToEndpoint,
|
|
1009
988
|
);
|
|
1010
989
|
|
|
1011
990
|
// case happens when rebasing op, but concurrently entire string has been deleted
|
|
1012
|
-
if (segoff
|
|
1013
|
-
|
|
991
|
+
if (segoff === undefined) {
|
|
992
|
+
if (ref.canSlideToEndpoint !== true) {
|
|
993
|
+
return undefined;
|
|
994
|
+
}
|
|
995
|
+
return {
|
|
996
|
+
segment:
|
|
997
|
+
ref.slidingPreference === SlidingPreference.FORWARD
|
|
998
|
+
? this.client.endOfTree
|
|
999
|
+
: this.client.startOfTree,
|
|
1000
|
+
offset: 0,
|
|
1001
|
+
};
|
|
1014
1002
|
}
|
|
1015
|
-
|
|
1016
|
-
assert(
|
|
1017
|
-
offset !== undefined && 0 <= offset && offset < segment.cachedLength,
|
|
1018
|
-
0x54f /* Invalid offset */,
|
|
1019
|
-
);
|
|
1020
|
-
return this.client.findReconnectionPosition(segoff.segment, localSeq) + segoff.offset;
|
|
1003
|
+
return segoff;
|
|
1021
1004
|
}
|
|
1022
1005
|
|
|
1023
1006
|
private computeRebasedPositions(
|
|
1024
1007
|
localOpMetadata: IntervalAddLocalMetadata | IntervalChangeLocalMetadata,
|
|
1025
|
-
|
|
1008
|
+
squash: boolean,
|
|
1009
|
+
): Record<"start" | "end", { segment: ISegmentInternal; offset: number }> | "detached" {
|
|
1026
1010
|
assert(
|
|
1027
1011
|
this.client !== undefined,
|
|
1028
1012
|
0x550 /* Client should be defined when computing rebased position */,
|
|
1029
1013
|
);
|
|
1030
|
-
|
|
1031
|
-
const
|
|
1032
|
-
const
|
|
1033
|
-
if (start
|
|
1034
|
-
|
|
1014
|
+
|
|
1015
|
+
const { localSeq, interval } = localOpMetadata;
|
|
1016
|
+
const start = this.rebaseReferenceWithSegmentSlide(interval.start, localSeq, squash);
|
|
1017
|
+
if (start === undefined) {
|
|
1018
|
+
return "detached";
|
|
1035
1019
|
}
|
|
1036
|
-
|
|
1037
|
-
|
|
1020
|
+
const end = this.rebaseReferenceWithSegmentSlide(interval.end, localSeq, squash);
|
|
1021
|
+
if (end === undefined) {
|
|
1022
|
+
return "detached";
|
|
1038
1023
|
}
|
|
1039
|
-
return
|
|
1024
|
+
return { start, end };
|
|
1040
1025
|
}
|
|
1041
1026
|
|
|
1042
1027
|
public attachGraph(client: Client, label: string) {
|
|
@@ -1051,11 +1036,11 @@ export class IntervalCollection
|
|
|
1051
1036
|
// Instantiate the local interval collection based on the saved intervals
|
|
1052
1037
|
this.client = client;
|
|
1053
1038
|
if (client) {
|
|
1054
|
-
client.on("normalize", () => {
|
|
1039
|
+
client.on("normalize", (squash) => {
|
|
1055
1040
|
for (const pending of Object.values(this.pending)) {
|
|
1056
1041
|
if (pending?.endpointChanges !== undefined) {
|
|
1057
1042
|
for (const local of pending.endpointChanges) {
|
|
1058
|
-
local.data.rebased = this.computeRebasedPositions(local.data);
|
|
1043
|
+
local.data.rebased = this.computeRebasedPositions(local.data, squash);
|
|
1059
1044
|
}
|
|
1060
1045
|
}
|
|
1061
1046
|
}
|
|
@@ -1216,7 +1201,7 @@ export class IntervalCollection
|
|
|
1216
1201
|
{
|
|
1217
1202
|
type: "add",
|
|
1218
1203
|
localSeq,
|
|
1219
|
-
|
|
1204
|
+
interval,
|
|
1220
1205
|
},
|
|
1221
1206
|
);
|
|
1222
1207
|
}
|
|
@@ -1345,7 +1330,7 @@ export class IntervalCollection
|
|
|
1345
1330
|
type: "change",
|
|
1346
1331
|
localSeq,
|
|
1347
1332
|
previous: interval.serialize(),
|
|
1348
|
-
|
|
1333
|
+
interval: newInterval ?? interval,
|
|
1349
1334
|
};
|
|
1350
1335
|
|
|
1351
1336
|
this.submitDelta(
|
|
@@ -1369,8 +1354,10 @@ export class IntervalCollection
|
|
|
1369
1354
|
}
|
|
1370
1355
|
if (newInterval) {
|
|
1371
1356
|
this.emitChange(newInterval, interval, true, false);
|
|
1372
|
-
|
|
1373
|
-
|
|
1357
|
+
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
1358
|
+
interval.start.properties!.interval = undefined;
|
|
1359
|
+
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
1360
|
+
interval.end.properties!.interval = undefined;
|
|
1374
1361
|
}
|
|
1375
1362
|
return newInterval;
|
|
1376
1363
|
}
|
|
@@ -1477,65 +1464,71 @@ export class IntervalCollection
|
|
|
1477
1464
|
*
|
|
1478
1465
|
*/
|
|
1479
1466
|
public rebaseLocalInterval(
|
|
1467
|
+
original: SerializedIntervalDelta,
|
|
1480
1468
|
localOpMetadata: IntervalAddLocalMetadata | IntervalChangeLocalMetadata,
|
|
1469
|
+
squash: boolean,
|
|
1481
1470
|
): SerializedIntervalDelta | undefined {
|
|
1482
|
-
|
|
1483
|
-
if (!this.client) {
|
|
1471
|
+
if (!this.client || !hasEndpointChanges(original)) {
|
|
1484
1472
|
// If there's no associated mergeTree client, the originally submitted op is still correct.
|
|
1485
1473
|
return original;
|
|
1486
1474
|
}
|
|
1487
|
-
if (!this.attached) {
|
|
1475
|
+
if (!this.attached || this.localCollection === undefined) {
|
|
1488
1476
|
throw new LoggingError("attachSequence must be called");
|
|
1489
1477
|
}
|
|
1490
1478
|
|
|
1491
|
-
const { localSeq } = localOpMetadata;
|
|
1492
|
-
const { intervalType, properties, stickiness, startSide, endSide } = original;
|
|
1479
|
+
const { localSeq, interval } = localOpMetadata;
|
|
1493
1480
|
const { id } = getSerializedProperties(original);
|
|
1494
|
-
const
|
|
1495
|
-
|
|
1496
|
-
|
|
1497
|
-
|
|
1498
|
-
|
|
1499
|
-
const rebased: SerializedIntervalDelta = {
|
|
1500
|
-
start: startRebased,
|
|
1501
|
-
end: endRebased,
|
|
1502
|
-
intervalType,
|
|
1503
|
-
sequenceNumber: this.client?.getCurrentSeq() ?? 0,
|
|
1504
|
-
properties,
|
|
1505
|
-
stickiness,
|
|
1506
|
-
startSide,
|
|
1507
|
-
endSide,
|
|
1508
|
-
};
|
|
1481
|
+
const rebasedEndpoint = (localOpMetadata.rebased ??= this.computeRebasedPositions(
|
|
1482
|
+
localOpMetadata,
|
|
1483
|
+
squash,
|
|
1484
|
+
));
|
|
1485
|
+
const localInterval = this.localCollection.idIntervalIndex.getIntervalById(id);
|
|
1509
1486
|
|
|
1510
1487
|
// if the interval slid off the string, rebase the op to be a noop and delete the interval.
|
|
1511
|
-
if (
|
|
1512
|
-
|
|
1513
|
-
|
|
1514
|
-
|
|
1515
|
-
|
|
1488
|
+
if (rebasedEndpoint === "detached") {
|
|
1489
|
+
if (
|
|
1490
|
+
localInterval !== undefined &&
|
|
1491
|
+
(localInterval === interval || localOpMetadata.type === "add")
|
|
1492
|
+
) {
|
|
1516
1493
|
this.localCollection?.removeExistingInterval(localInterval);
|
|
1517
1494
|
}
|
|
1518
1495
|
return undefined;
|
|
1519
1496
|
}
|
|
1520
1497
|
|
|
1521
|
-
|
|
1522
|
-
|
|
1523
|
-
|
|
1524
|
-
|
|
1525
|
-
|
|
1526
|
-
|
|
1527
|
-
|
|
1528
|
-
|
|
1529
|
-
|
|
1530
|
-
|
|
1498
|
+
const { start, end } = rebasedEndpoint;
|
|
1499
|
+
if (
|
|
1500
|
+
interval.start.getSegment() !== start.segment ||
|
|
1501
|
+
interval.start.getOffset() !== start.offset ||
|
|
1502
|
+
interval.end.getSegment() !== end.segment ||
|
|
1503
|
+
interval.end.getOffset() !== end.offset
|
|
1504
|
+
) {
|
|
1505
|
+
if (localInterval === interval) {
|
|
1506
|
+
this.localCollection.removeExistingInterval(localInterval);
|
|
1507
|
+
}
|
|
1508
|
+
const old = interval.clone();
|
|
1509
|
+
interval.moveEndpointReferences(rebasedEndpoint);
|
|
1510
|
+
if (localInterval === interval) {
|
|
1511
|
+
this.localCollection.add(interval);
|
|
1512
|
+
this.emitChange(interval, old, true, true);
|
|
1513
|
+
}
|
|
1514
|
+
this.client.removeLocalReferencePosition(old.start);
|
|
1515
|
+
this.client.removeLocalReferencePosition(old.end);
|
|
1531
1516
|
}
|
|
1532
1517
|
|
|
1533
|
-
return
|
|
1518
|
+
return {
|
|
1519
|
+
...original,
|
|
1520
|
+
start:
|
|
1521
|
+
start.segment.endpointType ??
|
|
1522
|
+
this.client.findReconnectionPosition(start.segment, localSeq) + start.offset,
|
|
1523
|
+
end:
|
|
1524
|
+
end.segment.endpointType ??
|
|
1525
|
+
this.client.findReconnectionPosition(end.segment, localSeq) + end.offset,
|
|
1526
|
+
sequenceNumber: this.client?.getCurrentSeq() ?? 0,
|
|
1527
|
+
};
|
|
1534
1528
|
}
|
|
1535
1529
|
|
|
1536
1530
|
private getSlideToSegment(
|
|
1537
1531
|
lref: LocalReferencePosition,
|
|
1538
|
-
slidingPreference: SlidingPreference,
|
|
1539
1532
|
): { segment: ISegment; offset: number } | undefined {
|
|
1540
1533
|
if (!this.client) {
|
|
1541
1534
|
throw new LoggingError("client does not exist");
|
|
@@ -1553,9 +1546,9 @@ export class IntervalCollection
|
|
|
1553
1546
|
}
|
|
1554
1547
|
return getSlideToSegoff(
|
|
1555
1548
|
segoff,
|
|
1556
|
-
slidingPreference,
|
|
1549
|
+
lref.slidingPreference,
|
|
1557
1550
|
undefined,
|
|
1558
|
-
|
|
1551
|
+
lref.canSlideToEndpoint,
|
|
1559
1552
|
);
|
|
1560
1553
|
}
|
|
1561
1554
|
|
|
@@ -1567,14 +1560,8 @@ export class IntervalCollection
|
|
|
1567
1560
|
return;
|
|
1568
1561
|
}
|
|
1569
1562
|
|
|
1570
|
-
const newStart = this.getSlideToSegment(
|
|
1571
|
-
|
|
1572
|
-
startReferenceSlidingPreference(interval.stickiness),
|
|
1573
|
-
);
|
|
1574
|
-
const newEnd = this.getSlideToSegment(
|
|
1575
|
-
interval.end,
|
|
1576
|
-
endReferenceSlidingPreference(interval.stickiness),
|
|
1577
|
-
);
|
|
1563
|
+
const newStart = this.getSlideToSegment(interval.start);
|
|
1564
|
+
const newEnd = this.getSlideToSegment(interval.end);
|
|
1578
1565
|
|
|
1579
1566
|
const id = interval.getIntervalId();
|
|
1580
1567
|
const hasPendingChange = this.hasPendingEndpointChanges(id);
|
|
@@ -1606,16 +1593,14 @@ export class IntervalCollection
|
|
|
1606
1593
|
|
|
1607
1594
|
if (needsStartUpdate) {
|
|
1608
1595
|
const props = interval.start.properties;
|
|
1609
|
-
interval.start = createPositionReferenceFromSegoff(
|
|
1610
|
-
this.client,
|
|
1611
|
-
newStart,
|
|
1612
|
-
interval.start.refType,
|
|
1596
|
+
interval.start = createPositionReferenceFromSegoff({
|
|
1597
|
+
client: this.client,
|
|
1598
|
+
segoff: newStart,
|
|
1599
|
+
refType: interval.start.refType,
|
|
1613
1600
|
op,
|
|
1614
|
-
|
|
1615
|
-
|
|
1616
|
-
|
|
1617
|
-
startReferenceSlidingPreference(interval.stickiness) === SlidingPreference.BACKWARD,
|
|
1618
|
-
);
|
|
1601
|
+
slidingPreference: interval.start.slidingPreference,
|
|
1602
|
+
canSlideToEndpoint: interval.start.canSlideToEndpoint,
|
|
1603
|
+
});
|
|
1619
1604
|
if (props) {
|
|
1620
1605
|
interval.start.addProperties(props);
|
|
1621
1606
|
}
|
|
@@ -1627,16 +1612,14 @@ export class IntervalCollection
|
|
|
1627
1612
|
}
|
|
1628
1613
|
if (needsEndUpdate) {
|
|
1629
1614
|
const props = interval.end.properties;
|
|
1630
|
-
interval.end = createPositionReferenceFromSegoff(
|
|
1631
|
-
this.client,
|
|
1632
|
-
newEnd,
|
|
1633
|
-
interval.end.refType,
|
|
1615
|
+
interval.end = createPositionReferenceFromSegoff({
|
|
1616
|
+
client: this.client,
|
|
1617
|
+
segoff: newEnd,
|
|
1618
|
+
refType: interval.end.refType,
|
|
1634
1619
|
op,
|
|
1635
|
-
|
|
1636
|
-
|
|
1637
|
-
|
|
1638
|
-
endReferenceSlidingPreference(interval.stickiness) === SlidingPreference.FORWARD,
|
|
1639
|
-
);
|
|
1620
|
+
slidingPreference: interval.end.slidingPreference,
|
|
1621
|
+
canSlideToEndpoint: interval.end.canSlideToEndpoint,
|
|
1622
|
+
});
|
|
1640
1623
|
if (props) {
|
|
1641
1624
|
interval.end.addProperties(props);
|
|
1642
1625
|
}
|
|
@@ -188,12 +188,16 @@ export class IntervalCollectionMap {
|
|
|
188
188
|
* also sent if we are asked to resubmit the message.
|
|
189
189
|
* @returns True if the operation was submitted, false otherwise.
|
|
190
190
|
*/
|
|
191
|
-
public tryResubmitMessage(
|
|
191
|
+
public tryResubmitMessage(
|
|
192
|
+
content: unknown,
|
|
193
|
+
localOpMetadata: unknown,
|
|
194
|
+
squash: boolean,
|
|
195
|
+
): boolean {
|
|
192
196
|
if (isMapOperation(content)) {
|
|
193
197
|
const { value, key } = content;
|
|
194
198
|
const localValue = this.data.get(key);
|
|
195
199
|
assert(localValue !== undefined, 0x3f8 /* Local value expected on resubmission */);
|
|
196
|
-
localValue.resubmitMessage(value, localOpMetadata);
|
|
200
|
+
localValue.resubmitMessage(value, localOpMetadata, squash);
|
|
197
201
|
return true;
|
|
198
202
|
}
|
|
199
203
|
return false;
|
|
@@ -3,8 +3,9 @@
|
|
|
3
3
|
* Licensed under the MIT License.
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
+
import type { ListNode } from "@fluidframework/core-utils/internal";
|
|
6
7
|
import { ISequencedDocumentMessage } from "@fluidframework/driver-definitions/internal";
|
|
7
|
-
import { IMergeTreeOptions,
|
|
8
|
+
import { IMergeTreeOptions, type ISegmentInternal } from "@fluidframework/merge-tree/internal";
|
|
8
9
|
|
|
9
10
|
import type {
|
|
10
11
|
IntervalCollection,
|
|
@@ -15,28 +16,34 @@ import {
|
|
|
15
16
|
ISerializedInterval,
|
|
16
17
|
IntervalDeltaOpType,
|
|
17
18
|
SerializedIntervalDelta,
|
|
19
|
+
type SequenceIntervalClass,
|
|
18
20
|
} from "./intervals/index.js";
|
|
19
21
|
|
|
20
22
|
export interface IntervalAddLocalMetadata {
|
|
21
23
|
type: typeof IntervalDeltaOpType.ADD;
|
|
22
24
|
localSeq: number;
|
|
23
25
|
endpointChangesNode?: ListNode<IntervalAddLocalMetadata | IntervalChangeLocalMetadata>;
|
|
24
|
-
rebased?:
|
|
25
|
-
|
|
26
|
+
rebased?:
|
|
27
|
+
| Record<"start" | "end", { segment: ISegmentInternal; offset: number }>
|
|
28
|
+
| "detached";
|
|
29
|
+
interval: SequenceIntervalClass;
|
|
26
30
|
}
|
|
27
31
|
export interface IntervalChangeLocalMetadata {
|
|
28
32
|
type: typeof IntervalDeltaOpType.CHANGE;
|
|
29
33
|
localSeq: number;
|
|
30
34
|
previous: ISerializedInterval;
|
|
31
35
|
endpointChangesNode?: ListNode<IntervalChangeLocalMetadata | IntervalChangeLocalMetadata>;
|
|
32
|
-
rebased?:
|
|
33
|
-
|
|
36
|
+
rebased?:
|
|
37
|
+
| Record<"start" | "end", { segment: ISegmentInternal; offset: number }>
|
|
38
|
+
| "detached";
|
|
39
|
+
interval: SequenceIntervalClass;
|
|
34
40
|
}
|
|
35
41
|
export interface IntervalDeleteLocalMetadata {
|
|
36
42
|
type: typeof IntervalDeltaOpType.DELETE;
|
|
37
43
|
localSeq: number;
|
|
38
44
|
previous: ISerializedInterval;
|
|
39
45
|
endpointChangesNode?: undefined;
|
|
46
|
+
interval?: undefined;
|
|
40
47
|
}
|
|
41
48
|
export type IntervalMessageLocalMetadata =
|
|
42
49
|
| IntervalAddLocalMetadata
|
|
@@ -57,7 +57,7 @@ export interface IInterval {
|
|
|
57
57
|
end: SequencePlace | undefined,
|
|
58
58
|
op?: ISequencedDocumentMessage,
|
|
59
59
|
localSeq?: number,
|
|
60
|
-
|
|
60
|
+
canSlideToEndpoint?: boolean,
|
|
61
61
|
): IInterval | undefined;
|
|
62
62
|
/**
|
|
63
63
|
* @returns whether this interval overlaps with `b`.
|
|
@@ -264,8 +264,12 @@ export const IntervalStickiness = {
|
|
|
264
264
|
export type IntervalStickiness = (typeof IntervalStickiness)[keyof typeof IntervalStickiness];
|
|
265
265
|
|
|
266
266
|
export function startReferenceSlidingPreference(
|
|
267
|
-
|
|
267
|
+
startPos: number | "start" | "end" | undefined,
|
|
268
|
+
startSide: Side,
|
|
269
|
+
endPos: number | "start" | "end" | undefined,
|
|
270
|
+
endSide: Side,
|
|
268
271
|
): SlidingPreference {
|
|
272
|
+
const stickiness = computeStickinessFromSide(startPos, startSide, endPos, endSide);
|
|
269
273
|
// if any start stickiness, prefer sliding backwards
|
|
270
274
|
return (stickiness & IntervalStickiness.START) === 0
|
|
271
275
|
? SlidingPreference.FORWARD
|
|
@@ -273,10 +277,34 @@ export function startReferenceSlidingPreference(
|
|
|
273
277
|
}
|
|
274
278
|
|
|
275
279
|
export function endReferenceSlidingPreference(
|
|
276
|
-
|
|
280
|
+
startPos: number | "start" | "end" | undefined,
|
|
281
|
+
startSide: Side,
|
|
282
|
+
endPos: number | "start" | "end" | undefined,
|
|
283
|
+
endSide: Side,
|
|
277
284
|
): SlidingPreference {
|
|
285
|
+
const stickiness = computeStickinessFromSide(startPos, startSide, endPos, endSide);
|
|
286
|
+
|
|
278
287
|
// if any end stickiness, prefer sliding forwards
|
|
279
288
|
return (stickiness & IntervalStickiness.END) === 0
|
|
280
289
|
? SlidingPreference.BACKWARD
|
|
281
290
|
: SlidingPreference.FORWARD;
|
|
282
291
|
}
|
|
292
|
+
|
|
293
|
+
export function computeStickinessFromSide(
|
|
294
|
+
startPos: number | "start" | "end" | undefined,
|
|
295
|
+
startSide: Side,
|
|
296
|
+
endPos: number | "start" | "end" | undefined,
|
|
297
|
+
endSide: Side,
|
|
298
|
+
): IntervalStickiness {
|
|
299
|
+
let stickiness: IntervalStickiness = IntervalStickiness.NONE;
|
|
300
|
+
|
|
301
|
+
if (startSide === Side.After || startPos === "start") {
|
|
302
|
+
stickiness |= IntervalStickiness.START;
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
if (endSide === Side.Before || endPos === "end") {
|
|
306
|
+
stickiness |= IntervalStickiness.END;
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
return stickiness as IntervalStickiness;
|
|
310
|
+
}
|