Skip to content
This repository was archived by the owner on Sep 11, 2024. It is now read-only.

Commit 86f3f5e

Browse files
committed
Handle sticky room to avoid accidental removal
Plus a bunch of logging. This fixes a case where switching rooms would cause the last room you were on to disappear due to an optimization where known NewRoom fires would be translated to tag change fires, which wouldn't re-add the room to the underlying tag algorithm. By tracking the last sticky room, we can identify when we're about to do this and avoid it. This commit also adds a check to ensure that we have the latest reference of a room stored as rooms changing from invite -> join change references. This commit additionally updates the PossibleTagChange handling to be faster and smarter, leading to a more stable generation of the room list. We convert the update cause to a Timeline update in order to indicate it is a change within the same tag rather than having to jump tags. This also means that PossibleTagChange should no longer make it as far as the underlying algorithm. New logging has also been added to aid debugging.
1 parent 4a1b6a2 commit 86f3f5e

File tree

1 file changed

+72
-15
lines changed

1 file changed

+72
-15
lines changed

src/stores/room-list/algorithms/Algorithm.ts

Lines changed: 72 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ import { Room } from "matrix-js-sdk/src/models/room";
1818
import { isNullOrUndefined } from "matrix-js-sdk/src/utils";
1919
import DMRoomMap from "../../../utils/DMRoomMap";
2020
import { EventEmitter } from "events";
21-
import { arrayHasDiff, ArrayUtil } from "../../../utils/arrays";
21+
import { arrayDiff, arrayHasDiff, ArrayUtil } from "../../../utils/arrays";
2222
import { getEnumValues } from "../../../utils/enums";
2323
import { DefaultTagID, RoomUpdateCause, TagID } from "../models";
2424
import {
@@ -58,6 +58,7 @@ export class Algorithm extends EventEmitter {
5858
private _cachedStickyRooms: ITagMap = {}; // a clone of the _cachedRooms, with the sticky room
5959
private filteredRooms: ITagMap = {};
6060
private _stickyRoom: IStickyRoom = null;
61+
private _lastStickyRoom: IStickyRoom = null;
6162
private sortAlgorithms: ITagSortingMap;
6263
private listAlgorithms: IListOrderingMap;
6364
private algorithms: IOrderingAlgorithmMap;
@@ -172,6 +173,7 @@ export class Algorithm extends EventEmitter {
172173
if (this._stickyRoom) {
173174
const stickyRoom = this._stickyRoom.room;
174175
this._stickyRoom = null; // clear before we go to update the algorithm
176+
this._lastStickyRoom = stickyRoom;
175177

176178
// Lie to the algorithm and re-add the room to the algorithm
177179
await this.handleRoomUpdate(stickyRoom, RoomUpdateCause.NewRoom);
@@ -198,7 +200,8 @@ export class Algorithm extends EventEmitter {
198200
// the same thing it no-ops. After we're done calling the algorithm, we'll issue
199201
// a new update for ourselves.
200202
const lastStickyRoom = this._stickyRoom;
201-
this._stickyRoom = null;
203+
this._stickyRoom = null; // clear before we update the algorithm
204+
this._lastStickyRoom = lastStickyRoom;
202205
this.recalculateStickyRoom();
203206

204207
// When we do have the room, re-add the old room (if needed) to the algorithm
@@ -562,7 +565,7 @@ export class Algorithm extends EventEmitter {
562565
/**
563566
* Updates the roomsToTags map
564567
*/
565-
protected updateTagsFromCache() {
568+
private updateTagsFromCache() {
566569
const newMap = {};
567570

568571
const tags = Object.keys(this.cachedRooms);
@@ -609,24 +612,73 @@ export class Algorithm extends EventEmitter {
609612
* processing.
610613
*/
611614
public async handleRoomUpdate(room: Room, cause: RoomUpdateCause): Promise<boolean> {
615+
// TODO: Remove debug: https://github.com/vector-im/riot-web/issues/14035
616+
console.trace(`Handle room update for ${room.roomId} called with cause ${cause}`);
612617
try {
613618
await this.lock.acquireAsync();
614619

615620
if (!this.algorithms) throw new Error("Not ready: no algorithms to determine tags from");
616621

622+
// TODO: Remove debug: https://github.com/vector-im/riot-web/issues/14035
623+
console.log(`[RoomListDebug] @@ ${room.roomId} with ${cause} for tags ${this.roomIdsToTags[room.roomId]} and sticky is currently ${this._stickyRoom && this._stickyRoom.room ? this._stickyRoom.room.roomId : '<none>'} and last is ${this._lastStickyRoom && this._lastStickyRoom.room ? this._lastStickyRoom.room.roomId : '<none>'}`);
624+
617625
if (cause === RoomUpdateCause.NewRoom) {
626+
const isForLastSticky = this._lastStickyRoom && this._lastStickyRoom.room === room;
618627
const roomTags = this.roomIdsToTags[room.roomId];
619-
if (roomTags && roomTags.length > 0) {
628+
const hasTags = roomTags && roomTags.length > 0;
629+
630+
// Don't change the cause if the last sticky room is being re-added. If we fail to
631+
// pass the cause through as NewRoom, we'll fail to lie to the algorithm and thus
632+
// lose the room.
633+
if (hasTags && !isForLastSticky) {
620634
console.warn(`${room.roomId} is reportedly new but is already known - assuming TagChange instead`);
621635
cause = RoomUpdateCause.PossibleTagChange;
622636
}
637+
638+
// If we have tags for a room and don't have the room referenced, the room reference
639+
// probably changed. We need to swap out the problematic reference.
640+
if (hasTags && !this.rooms.includes(room)) {
641+
console.warn(`${room.roomId} is missing from room array but is known - trying to find duplicate`);
642+
this.rooms = this.rooms.map(r => r.roomId === room.roomId ? room : r);
643+
644+
// Sanity check
645+
if (!this.rooms.includes(room)) {
646+
throw new Error(`Failed to replace ${room.roomId} with an updated reference`);
647+
}
648+
}
623649
}
624650

625651
if (cause === RoomUpdateCause.PossibleTagChange) {
626-
// TODO: Be smarter and splice rather than regen the planet. https://github.com/vector-im/riot-web/issues/14035
627-
// TODO: No-op if no change. https://github.com/vector-im/riot-web/issues/14035
628-
await this.setKnownRooms(this.rooms);
629-
return true;
652+
const oldTags = this.roomIdsToTags[room.roomId] || [];
653+
const newTags = this.getTagsForRoom(room);
654+
const diff = arrayDiff(oldTags, newTags);
655+
if (diff.removed.length > 0 || diff.added.length > 0) {
656+
for (const rmTag of diff.removed) {
657+
// TODO: Remove debug: https://github.com/vector-im/riot-web/issues/14035
658+
console.log(`Removing ${room.roomId} from ${rmTag}`);
659+
const algorithm: OrderingAlgorithm = this.algorithms[rmTag];
660+
if (!algorithm) throw new Error(`No algorithm for ${rmTag}`);
661+
await algorithm.handleRoomUpdate(room, RoomUpdateCause.RoomRemoved);
662+
}
663+
for (const addTag of diff.added) {
664+
// TODO: Remove debug: https://github.com/vector-im/riot-web/issues/14035
665+
console.log(`Adding ${room.roomId} to ${addTag}`);
666+
const algorithm: OrderingAlgorithm = this.algorithms[addTag];
667+
if (!algorithm) throw new Error(`No algorithm for ${addTag}`);
668+
await algorithm.handleRoomUpdate(room, RoomUpdateCause.NewRoom);
669+
}
670+
671+
// Update the tag map so we don't regen it in a moment
672+
this.roomIdsToTags[room.roomId] = newTags;
673+
674+
// TODO: Remove debug: https://github.com/vector-im/riot-web/issues/14035
675+
console.log(`Changing update cause for ${room.roomId} to TIMELINE to sort rooms`);
676+
cause = RoomUpdateCause.Timeline;
677+
} else {
678+
// TODO: Remove debug: https://github.com/vector-im/riot-web/issues/14035
679+
console.warn(`Received no-op update for ${room.roomId} - changing to TIMELINE update`);
680+
cause = RoomUpdateCause.Timeline;
681+
}
630682
}
631683

632684
// If the update is for a room change which might be the sticky room, prevent it. We
@@ -640,24 +692,27 @@ export class Algorithm extends EventEmitter {
640692
}
641693
}
642694

643-
if (cause === RoomUpdateCause.NewRoom && !this.roomIdsToTags[room.roomId]) {
695+
if (!this.roomIdsToTags[room.roomId]) {
644696
// TODO: Remove debug: https://github.com/vector-im/riot-web/issues/14035
645-
console.log(`[RoomListDebug] Updating tags for new room ${room.roomId} (${room.name})`);
697+
console.log(`[RoomListDebug] Updating tags for room ${room.roomId} (${room.name})`);
646698

647699
// Get the tags for the room and populate the cache
648700
const roomTags = this.getTagsForRoom(room).filter(t => !isNullOrUndefined(this.cachedRooms[t]));
649701

650-
// TODO: Remove debug: https://github.com/vector-im/riot-web/issues/14035
651-
console.log(`[RoomListDebug] Updated tags for ${room.roomId}:`, roomTags);
652-
653702
// "This should never happen" condition - we specify DefaultTagID.Untagged in getTagsForRoom(),
654703
// which means we should *always* have a tag to go off of.
655704
if (!roomTags.length) throw new Error(`Tags cannot be determined for ${room.roomId}`);
656705

657706
this.roomIdsToTags[room.roomId] = roomTags;
707+
708+
// TODO: Remove debug: https://github.com/vector-im/riot-web/issues/14035
709+
console.log(`[RoomListDebug] Updated tags for ${room.roomId}:`, roomTags);
658710
}
659711

660-
let tags = this.roomIdsToTags[room.roomId];
712+
// TODO: Remove debug: https://github.com/vector-im/riot-web/issues/14035
713+
console.log(`[RoomListDebug] Reached algorithmic handling for ${room.roomId} and cause ${cause}`);
714+
715+
const tags = this.roomIdsToTags[room.roomId];
661716
if (!tags) {
662717
console.warn(`No tags known for "${room.name}" (${room.roomId})`);
663718
return false;
@@ -677,7 +732,9 @@ export class Algorithm extends EventEmitter {
677732
changed = true;
678733
}
679734

680-
return true;
735+
// TODO: Remove debug: https://github.com/vector-im/riot-web/issues/14035
736+
console.log(`[RoomListDebug] Finished handling ${room.roomId} with cause ${cause} (changed=${changed})`);
737+
return changed;
681738
} finally {
682739
this.lock.release();
683740
}

0 commit comments

Comments
 (0)