Skip to content

Commit

Permalink
Emit an event on IntervalCollection creation (microsoft#9384)
Browse files Browse the repository at this point in the history
Let SharedSegmentSequence emit an event on IntervalCollection creation and let the IntervalCollection labels in a given SharedSegmentSequence be enumerated
  • Loading branch information
pleath authored Mar 11, 2022
1 parent 9287bb2 commit 85b2ff6
Show file tree
Hide file tree
Showing 5 changed files with 80 additions and 5 deletions.
4 changes: 4 additions & 0 deletions api-report/sequence.api.md
Original file line number Diff line number Diff line change
Expand Up @@ -238,6 +238,8 @@ export interface ISharedIntervalCollection<TInterval extends ISerializableInterv

// @public
export interface ISharedSegmentSequenceEvents extends ISharedObjectEvents {
// (undocumented)
(event: "createIntervalCollection", listener: (label: string, local: boolean, target: IEventThisPlaceHolder) => void): any;
// (undocumented)
(event: "sequenceDelta", listener: (event: SequenceDeltaEvent, target: IEventThisPlaceHolder) => void): any;
// (undocumented)
Expand Down Expand Up @@ -537,6 +539,8 @@ export abstract class SharedSegmentSequence<T extends ISegment> extends SharedOb
getCurrentSeq(): number;
// (undocumented)
getIntervalCollection(label: string): IntervalCollection<SequenceInterval>;
// (undocumented)
getIntervalCollectionLabels(): IterableIterator<string>;
getLength(): number;
getPosition(segment: ISegment): number;
// (undocumented)
Expand Down
6 changes: 3 additions & 3 deletions packages/dds/sequence/src/mapKernel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -309,11 +309,11 @@ export class MapKernel implements IValueTypeCreator {
if (key === changed.key) {
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
resolve(this.get<T>(changed.key)!);
this.eventEmitter.removeListener("valueChanged", callback);
this.eventEmitter.removeListener("create", callback);
}
};

this.eventEmitter.on("valueChanged", callback);
this.eventEmitter.on("create", callback);
});
}

Expand Down Expand Up @@ -550,7 +550,7 @@ export class MapKernel implements IValueTypeCreator {
const previousValue = this.get(key);
this.data.set(key, value);
const event: IValueChanged = { key, previousValue };
this.eventEmitter.emit("valueChanged", event, local, op, this.eventEmitter);
this.eventEmitter.emit("create", event, local, op, this.eventEmitter);
}

/**
Expand Down
2 changes: 1 addition & 1 deletion packages/dds/sequence/src/mapKernelInterfaces.ts
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,7 @@ export interface IValueTypeCreator {
}

export interface ISharedMapEvents extends ISharedObjectEvents {
(event: "valueChanged", listener: (
(event: "valueChanged" | "create", listener: (
changed: IValueChanged,
local: boolean,
target: IEventThisPlaceHolder) => void);
Expand Down
18 changes: 17 additions & 1 deletion packages/dds/sequence/src/sequence.ts
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,8 @@ const contentPath = "content";
* - `target` - The sequence itself.
*/
export interface ISharedSegmentSequenceEvents extends ISharedObjectEvents {
(event: "createIntervalCollection",
listener: (label: string, local: boolean, target: IEventThisPlaceHolder) => void);
(event: "sequenceDelta", listener: (event: SequenceDeltaEvent, target: IEventThisPlaceHolder) => void);
(event: "maintenance",
listener: (event: SequenceMaintenanceEvent, target: IEventThisPlaceHolder) => void);
Expand Down Expand Up @@ -427,6 +429,18 @@ export abstract class SharedSegmentSequence<T extends ISegment>
return sharedCollection;
}

/**
* @returns an iterable object that enumerates the IntervalCollection labels
* Usage:
* const iter = this.getIntervalCollectionKeys();
* for (key of iter)
* const collection = this.getIntervalCollection(key);
* ...
*/
public getIntervalCollectionLabels(): IterableIterator<string> {
return this.intervalMapKernel.keys();
}

protected summarizeCore(serializer: IFluidSerializer): ISummaryTreeWithStats {
const builder = new SummaryTreeBuilder();

Expand Down Expand Up @@ -680,11 +694,13 @@ export abstract class SharedSegmentSequence<T extends ISegment>

private initializeIntervalCollections() {
// Listen and initialize new SharedIntervalCollections
this.intervalMapKernel.eventEmitter.on("valueChanged", (ev: IValueChanged) => {
this.intervalMapKernel.eventEmitter.on("create", (ev: IValueChanged, local: boolean) => {
const intervalCollection = this.intervalMapKernel.get<IntervalCollection<SequenceInterval>>(ev.key);
if (!intervalCollection.attached) {
intervalCollection.attachGraph(this.client, ev.key);
}
assert(ev.previousValue === undefined, "Creating an interval that already exists?");
this.emit("createIntervalCollection", ev.key, local, this);
});

// Initialize existing SharedIntervalCollections
Expand Down
55 changes: 55 additions & 0 deletions packages/dds/sequence/src/test/sharedString.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import {
} from "@fluidframework/test-runtime-utils";
import { SharedString } from "../sharedString";
import { SharedStringFactory } from "../sequenceFactory";
import { IntervalCollection, IntervalType, SequenceInterval } from "../intervalCollection";

describe("SharedString", () => {
let sharedString: SharedString;
Expand Down Expand Up @@ -474,6 +475,60 @@ describe("SharedString", () => {
const simpleMarker2 = sharedString.getMarkerFromId("markerId") as Marker;
assert.equal(simpleMarker2.properties.color, "blue", "Could not annotate marker in remote string");
});

it("test IntervalCollection creation events", () => {
let createCalls1 = 0;
const createInfo1 = [];
const createCallback1 = (label: string, local: boolean, target: SharedString) => {
assert.strictEqual(target, sharedString, "Expected event to target sharedString");
createInfo1[createCalls1++] = { local, label };
};
sharedString.on("createIntervalCollection", createCallback1);

let createCalls2 = 0;
const createInfo2 = [];
const createCallback2 = (label: string, local: boolean, target: SharedString) => {
assert.strictEqual(target, sharedString2, "Expected event to target sharedString2");
createInfo2[createCalls2++] = { local, label };
};
sharedString2.on("createIntervalCollection", createCallback2);

const collection1: IntervalCollection<SequenceInterval> = sharedString.getIntervalCollection("test1");
const interval1 = collection1.add(0, 1, IntervalType.SlideOnRemove);
collection1.change(interval1.getIntervalId(), 1, 4);

const collection2: IntervalCollection<SequenceInterval> = sharedString2.getIntervalCollection("test2");
const interval2 = collection2.add(0, 2, IntervalType.SlideOnRemove);
collection2.removeIntervalById(interval2.getIntervalId());

const collection3: IntervalCollection<SequenceInterval> = sharedString2.getIntervalCollection("test3");
collection3.add(0, 3, IntervalType.SlideOnRemove);

containerRuntimeFactory.processAllMessages();

const verifyCreateEvents = (s: SharedString, createInfo, infoArray) => {
let i = 0;
const labels = s.getIntervalCollectionLabels();
for (const label of labels) {
assert.equal(label, infoArray[i].label, `Bad label ${i}: ${label}`);
assert.equal(label, createInfo[i].label, `Bad label ${i}: ${createInfo[i].label}`);
assert.equal(
createInfo[i].local, infoArray[i].local, `Bad local value ${i}: ${createInfo[i].local}`);
i++;
}
assert.equal(infoArray.length, createInfo.length, `Wrong number of create calls: ${i}`);
};
verifyCreateEvents(sharedString, createInfo1, [
{ label: "intervalCollections/test1", local: true },
{ label: "intervalCollections/test2", local: false},
{ label: "intervalCollections/test3", local: false},
]);
verifyCreateEvents(sharedString2, createInfo2, [
{ label: "intervalCollections/test2", local: true },
{ label: "intervalCollections/test3", local: true },
{ label: "intervalCollections/test1", local: false},
]);
});
});

describe("reconnect", () => {
Expand Down

0 comments on commit 85b2ff6

Please sign in to comment.