Skip to content

Commit

Permalink
store if a state is at the start of a timeline in the RoomState class
Browse files Browse the repository at this point in the history
  • Loading branch information
toger5 committed Jun 21, 2024
1 parent b5d5dd3 commit 8ebf04a
Show file tree
Hide file tree
Showing 5 changed files with 37 additions and 40 deletions.
5 changes: 0 additions & 5 deletions spec/unit/event-timeline.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,6 @@ describe("EventTimeline", function () {
const timelineStartState = timeline.startState!;
expect(mocked(timelineStartState).setStateEvents).toHaveBeenCalledWith(events, {
timelineWasEmpty: undefined,
toStartOfTimeline: true,
});
// @ts-ignore private prop
const timelineEndState = timeline.endState!;
Expand Down Expand Up @@ -314,11 +313,9 @@ describe("EventTimeline", function () {

expect(timeline.getState(EventTimeline.FORWARDS)!.setStateEvents).toHaveBeenCalledWith([events[0]], {
timelineWasEmpty: undefined,
toStartOfTimeline: false,
});
expect(timeline.getState(EventTimeline.FORWARDS)!.setStateEvents).toHaveBeenCalledWith([events[1]], {
timelineWasEmpty: undefined,
toStartOfTimeline: false,
});

expect(events[0].forwardLooking).toBe(true);
Expand Down Expand Up @@ -355,11 +352,9 @@ describe("EventTimeline", function () {

expect(timeline.getState(EventTimeline.BACKWARDS)!.setStateEvents).toHaveBeenCalledWith([events[0]], {
timelineWasEmpty: undefined,
toStartOfTimeline: true,
});
expect(timeline.getState(EventTimeline.BACKWARDS)!.setStateEvents).toHaveBeenCalledWith([events[1]], {
timelineWasEmpty: undefined,
toStartOfTimeline: true,
});

expect(events[0].forwardLooking).toBe(false);
Expand Down
13 changes: 9 additions & 4 deletions spec/unit/room-state.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1125,6 +1125,9 @@ describe("RoomState", function () {
describe("skipWrongOrderRoomStateInserts", () => {
const idNow = "now";
const idBefore = "before";
const endState = new RoomState(roomId);
const startState = new RoomState(roomId, undefined, true);

let onRoomStateEvent: (event: MatrixEvent, state: RoomState, lastStateEvent: MatrixEvent | null) => void;
const evNow = new MatrixEvent({
type: "m.call.member",
Expand Down Expand Up @@ -1154,20 +1157,22 @@ describe("RoomState", function () {
updatedStateWithBefore();
}
};
state.on(RoomStateEvent.Events, onRoomStateEvent);
endState.on(RoomStateEvent.Events, onRoomStateEvent);
startState.on(RoomStateEvent.Events, onRoomStateEvent);
});
afterEach(() => {
state.off(RoomStateEvent.Events, onRoomStateEvent);
endState.off(RoomStateEvent.Events, onRoomStateEvent);
startState.off(RoomStateEvent.Events, onRoomStateEvent);
updatedStateWithNow.mockReset();
updatedStateWithBefore.mockReset();
});
it("should skip inserting state to the end of the timeline if the current endState events replaces_state id is the same as the inserted events id", () => {
state.setStateEvents([evNow, evBefore], { toStartOfTimeline: false });
endState.setStateEvents([evNow, evBefore]);
expect(updatedStateWithBefore).not.toHaveBeenCalled();
expect(updatedStateWithNow).toHaveBeenCalled();
});
it("should skip inserting state at the beginning of the timeline if the inserted events replaces_state is the event id of the current startState", () => {
state.setStateEvents([evBefore, evNow], { toStartOfTimeline: true });
startState.setStateEvents([evBefore, evNow]);
expect(updatedStateWithBefore).toHaveBeenCalled();
expect(updatedStateWithNow).not.toHaveBeenCalled();
});
Expand Down
20 changes: 4 additions & 16 deletions spec/unit/room.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -406,14 +406,8 @@ describe("Room", function () {
}),
];
await room.addLiveEvents(events);
expect(room.currentState.setStateEvents).toHaveBeenCalledWith([events[0]], {
timelineWasEmpty: false,
toStartOfTimeline: false,
});
expect(room.currentState.setStateEvents).toHaveBeenCalledWith([events[1]], {
timelineWasEmpty: false,
toStartOfTimeline: false,
});
expect(room.currentState.setStateEvents).toHaveBeenCalledWith([events[0]], { timelineWasEmpty: false });
expect(room.currentState.setStateEvents).toHaveBeenCalledWith([events[1]], { timelineWasEmpty: false });
expect(events[0].forwardLooking).toBe(true);
expect(events[1].forwardLooking).toBe(true);
expect(room.oldState.setStateEvents).not.toHaveBeenCalled();
Expand Down Expand Up @@ -732,14 +726,8 @@ describe("Room", function () {
];

room.addEventsToTimeline(events, true, room.getLiveTimeline());
expect(room.oldState.setStateEvents).toHaveBeenCalledWith([events[0]], {
timelineWasEmpty: undefined,
toStartOfTimeline: true,
});
expect(room.oldState.setStateEvents).toHaveBeenCalledWith([events[1]], {
timelineWasEmpty: undefined,
toStartOfTimeline: true,
});
expect(room.oldState.setStateEvents).toHaveBeenCalledWith([events[0]], { timelineWasEmpty: undefined });
expect(room.oldState.setStateEvents).toHaveBeenCalledWith([events[1]], { timelineWasEmpty: undefined });
expect(events[0].forwardLooking).toBe(false);
expect(events[1].forwardLooking).toBe(false);
expect(room.currentState.setStateEvents).not.toHaveBeenCalled();
Expand Down
6 changes: 3 additions & 3 deletions src/models/event-timeline.ts
Original file line number Diff line number Diff line change
Expand Up @@ -127,7 +127,7 @@ export class EventTimeline {
public constructor(private readonly eventTimelineSet: EventTimelineSet) {
this.roomId = eventTimelineSet.room?.roomId ?? null;
if (this.roomId) {
this.startState = new RoomState(this.roomId);
this.startState = new RoomState(this.roomId, undefined, true);
this.endState = new RoomState(this.roomId);
}

Expand All @@ -151,7 +151,7 @@ export class EventTimeline {
throw new Error("Cannot initialise state after events are added");
}

this.startState?.setStateEvents(stateEvents, { timelineWasEmpty, toStartOfTimeline: true });
this.startState?.setStateEvents(stateEvents, { timelineWasEmpty });
this.endState?.setStateEvents(stateEvents, { timelineWasEmpty });
}

Expand Down Expand Up @@ -375,7 +375,7 @@ export class EventTimeline {

// modify state but only on unfiltered timelineSets
if (event.isState() && timelineSet.room.getUnfilteredTimelineSet() === timelineSet) {
roomState?.setStateEvents([event], { timelineWasEmpty, toStartOfTimeline });
roomState?.setStateEvents([event], { timelineWasEmpty });
// it is possible that the act of setting the state event means we
// can set more metadata (specifically sender/target props), so try
// it again if the prop wasn't previously set. It may also mean that
Expand Down
33 changes: 21 additions & 12 deletions src/models/room-state.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,16 +43,7 @@ export interface IMarkerFoundOptions {
*/
timelineWasEmpty?: boolean;
}
export interface ISetStateEventsOptions {
/** Whether the event is inserted at the start of the timeline
* or at the end. This is relevant for doing a sanity check:
* "Is the event id of the added event the same as the replaces_state id of the current event"
* We need to do this because sync sometimes feeds previous state events.
* If set to true the sanity check is inverted
* "Is the event id of the current event the same as the replaces_state id of the added event"
*/
toStartOfTimeline?: boolean;
}

// possible statuses for out-of-band member loading
enum OobStatus {
NotStarted,
Expand Down Expand Up @@ -175,6 +166,7 @@ export class RoomState extends TypedEventEmitter<EmittedEvents, EventHandlerMap>

public readonly beacons = new Map<BeaconIdentifier, Beacon>();
private _liveBeaconIds: BeaconIdentifier[] = [];
private _isStartTimelineState = false;

/**
* Construct room state.
Expand Down Expand Up @@ -207,8 +199,10 @@ export class RoomState extends TypedEventEmitter<EmittedEvents, EventHandlerMap>
public constructor(
public readonly roomId: string,
private oobMemberFlags = { status: OobStatus.NotStarted },
isStartTimelineState: boolean = false,
) {
super();
this._isStartTimelineState = isStartTimelineState;
this.updateModifiedTime();
}

Expand Down Expand Up @@ -345,6 +339,21 @@ export class RoomState extends TypedEventEmitter<EmittedEvents, EventHandlerMap>
return this._liveBeaconIds;
}

/**
* This state is marked as a start state. This is used to skip state insertions that are
* in the wrong order. The order is determined by the `replaces_state` id.
*
* Example:
* A current state events `replaces_state` value is `1`.
* Trying to insert a state event with `event_id` `1` in its place would fail if isStartTimelineState = false.
*
* A current state events `event_id` is `2`.
* Trying to insert a state event where its `replaces_state` value is `2` would fail if isStartTimelineState = true.
*/
public get isStartTimelineState(): boolean {
return this._isStartTimelineState;
}

/**
* Creates a copy of this room state so that mutations to either won't affect the other.
* @returns the copy of the room state
Expand Down Expand Up @@ -417,7 +426,7 @@ export class RoomState extends TypedEventEmitter<EmittedEvents, EventHandlerMap>
* Fires {@link RoomStateEvent.Events}
* Fires {@link RoomStateEvent.Marker}
*/
public setStateEvents(stateEvents: MatrixEvent[], options?: IMarkerFoundOptions & ISetStateEventsOptions): void {
public setStateEvents(stateEvents: MatrixEvent[], options?: IMarkerFoundOptions): void {
this.updateModifiedTime();

// update the core event dict
Expand All @@ -437,7 +446,7 @@ export class RoomState extends TypedEventEmitter<EmittedEvents, EventHandlerMap>
const lastId = lastStateEvent?.event.event_id;
const newReplaceId = event.event.unsigned?.replaces_state;
const newId = event.event.event_id;
if (options?.toStartOfTimeline) {
if (this._isStartTimelineState) {
// Add an event to the start of the timeline. Its replace id not be the same as the one of the current/last start state event.
if (newReplaceId && newId && newReplaceId === lastId) return;
} else {
Expand Down

0 comments on commit 8ebf04a

Please sign in to comment.