cetirizine

An experimental matrix client written in reactjs utilizing tailwind and storybook
git clone git://archive.git.mtrnord.blog/MTRNord/cetirizine.git
Log | Files | Refs | README | LICENSE

commit d9e74c0346a8c2a1562175cfb1e124b9132c52a1
parent 7eda616a03a9f0d0af321308bd9973b0324465d3
Author: MTRNord <mtrnord1@gmail.com>
Date:   Tue,  2 May 2023 19:27:37 +0200

Switch over to a better memory layout for rooms

Diffstat:
Msrc/app/sdk/client.ts | 251+++++++++++++++++++++++++++----------------------------------------------------
Msrc/app/sdk/room.ts | 8+++++++-
2 files changed, 93 insertions(+), 166 deletions(-)

diff --git a/src/app/sdk/client.ts b/src/app/sdk/client.ts @@ -23,9 +23,11 @@ export declare interface MatrixClient { interface MatrixDB extends DBSchema { rooms: { // Same as roomToRoom map - key: number; + key: string; value: { - windowID: number; + windowPos: { + [list: string]: number; + }; roomID: string; name: string; notification_count: number; @@ -37,10 +39,6 @@ interface MatrixDB extends DBSchema { avatarUrl?: string; isSpace: boolean; }; - indexes: { - // Get room in window order - 'by-windowID': number; - }; }; loginInfo: { // login info @@ -80,7 +78,7 @@ export class MatrixClient extends EventEmitter { private slidingSyncHostname?: string; private syncing = false; private roomsInView: string[] = []; - private roomToRoom: Map<number, Room> = new Map(); + private rooms: Set<Room> = new Set(); private syncPos?: string; private initialSync = true; private database?: IDBPDatabase<MatrixDB>; @@ -133,8 +131,9 @@ export class MatrixClient extends EventEmitter { await roomTx?.done; if (rooms) { - for (const room of rooms) { + instance.rooms = new Set(rooms.map(room => { const roomObj = new Room(room.roomID, instance.hostname!); + roomObj.windowPos = room.windowPos; roomObj.setInvitedCount(room.invited_count); roomObj.setJoinedCount(room.joined_count); roomObj.setNotificationCount(room.notification_count); @@ -146,10 +145,9 @@ export class MatrixClient extends EventEmitter { if (room.stateEvents) { roomObj.addStateEvents(room.stateEvents); } - - instance.roomToRoom.set(room.windowID, roomObj); - } - instance.emit("rooms", new Set(instance.roomToRoom.values())); + return roomObj; + })) + instance.emit("rooms", instance.rooms); } } } @@ -159,20 +157,26 @@ export class MatrixClient extends EventEmitter { } private async createDatabase() { - this.database = await openDB<MatrixDB>("matrix", 2, { - upgrade(db, oldVersion) { - if (oldVersion < 1) { - const roomStore = db.createObjectStore('rooms', { keyPath: 'windowID' }); - roomStore.createIndex('by-windowID', 'windowID'); - db.createObjectStore('loginInfo', { keyPath: 'userId' }); - db.createObjectStore('syncInfo', { keyPath: 'userId' }); + this.database = await openDB<MatrixDB>("matrix", 3, { + upgrade(db) { + if (db.objectStoreNames.contains("rooms")) { + db.deleteObjectStore("rooms"); + } + //if (db.objectStoreNames.contains("loginInfo")) { + // db.deleteObjectStore("loginInfo"); + //} + if (db.objectStoreNames.contains("syncInfo")) { + db.deleteObjectStore("syncInfo"); } + db.createObjectStore('rooms', { keyPath: 'roomID' }); + db.createObjectStore('loginInfo', { keyPath: 'userId' }); + db.createObjectStore('syncInfo', { keyPath: 'userId' }); } }); } - private findNextFreeIndex(): number { - const keys = Array.from(this.roomToRoom.keys()); + private findNextFreeIndex(list: string): number { + const keys = [...this.rooms].map(room => room.windowPos[list]); const max = Math.max(...keys); // Will find highest number const min = 0; // Basically "append" @@ -249,45 +253,48 @@ export class MatrixClient extends EventEmitter { } // This is the initial sync case for each list - let lists_ranges = { + let lists_ranges: { + "overview": number[][]; + [key: string]: number[][]; + } = { "overview": [[0, 10]] }; let timeline_limit = 1; if (!this.initialSync) { - // Set higher timeline limit for subsequent syncs - timeline_limit = 10; - // Calculate overlap between this.roomsInView and this.roomToRoomID and then - // calculate the ranges for each list - const rawRangeInView = [...this.roomToRoom.entries()] - .filter(([_, room]) => this.roomsInView.includes(room.roomID)) - .map(([rangeID]) => rangeID) - .sort(); - - // Increment range by 1 to make sure we always get a little more than we need - // [1,2,3,4,7,8,9,10,11] -> [2,3,4,5,8,9,10,11,12] - rawRangeInView.push(rawRangeInView[rawRangeInView.length - 1] + 1); - - // Turn an input like [1,2,3,4,7,8,9,10,11] to [[1,4], [7,11]] - const rangesInView = rawRangeInView.reduce((acc, cur, i, arr) => { - if (i === 0) { - // [1,2,3,4] -> [[1,1]] + for (const list in lists_ranges) { + // Set higher timeline limit for subsequent syncs + timeline_limit = 10; + // Calculate overlap between this.roomsInView and this.roomToRoomID and then + // calculate the ranges for each list + const rawRangeInView = [...this.rooms] + .filter(room => this.roomsInView.includes(room.roomID)) + .map(room => room.windowPos[list]) + .sort(); + + // Increment range by 1 to make sure we always get a little more than we need + // [1,2,3,4,7,8,9,10,11] -> [2,3,4,5,8,9,10,11,12] + rawRangeInView.push(rawRangeInView[rawRangeInView.length - 1] + 1); + + // Turn an input like [1,2,3,4,7,8,9,10,11] to [[1,4], [7,11]] + const rangesInView = rawRangeInView.reduce((acc, cur, i, arr) => { + if (i === 0) { + // [1,2,3,4] -> [[1,1]] + acc.push([cur, cur]); + return acc; + } + // Cur = 2, arr = [1,2,3,4], arr[i - 1] + 1 = 2 then + if (cur === arr[i - 1] + 1) { + // [1,2,3,4,7] -> [[1,2]] + acc[acc.length - 1][1] = cur; + return acc; + } + // Else [1,2,3,4,7] -> [[1,2], [7,7]] acc.push([cur, cur]); return acc; - } - // Cur = 2, arr = [1,2,3,4], arr[i - 1] + 1 = 2 then - if (cur === arr[i - 1] + 1) { - // [1,2,3,4,7] -> [[1,2]] - acc[acc.length - 1][1] = cur; - return acc; - } - // Else [1,2,3,4,7] -> [[1,2], [7,7]] - acc.push([cur, cur]); - return acc; - }, [] as [number, number][]); + }, [] as [number, number][]); - lists_ranges = { - "overview": rangesInView - }; + lists_ranges[list] = rangesInView; + } } @@ -405,15 +412,18 @@ export class MatrixClient extends EventEmitter { const tx = this.database?.transaction('rooms', 'readwrite'); for (let i = op.range[0]; i <= op.range[1]; i++) { // We shall first forget about these and "startover" - await tx?.store.delete(i); - this.roomToRoom.delete(i); - + await tx?.store.delete(op.room_ids[i]); + const roomObj = [...this.rooms].find(room => room.windowPos[listKey] === i); + if (roomObj) { + this.rooms.delete(roomObj) + } // We start to remember the Room now. const newRoom = new Room(op.room_ids[i], this.hostname!); newRoom.setName("Unknown Room"); - this.roomToRoom.set(i, newRoom); + newRoom.windowPos[listKey] = i; + this.rooms.add(newRoom); await tx?.store.put({ - windowID: i, + windowPos: newRoom.windowPos, roomID: newRoom.roomID, name: newRoom.getName(), notification_count: newRoom.getNotificationCount(), @@ -427,109 +437,25 @@ export class MatrixClient extends EventEmitter { await tx?.done; } else if (isInsertOp(op)) { console.log("Got INSERT OP", op); - console.log("List", this.roomToRoom); - const position = op.index; - // If the position is not empty then clients MUST move entries to the left or the right depending on where the closest empty space is. - const emptySpot = this.findNextFreeIndex(); - console.log("Empty spot", emptySpot); - console.log("Position", position); - // If there is no empty spot at the position we try to insert make sure that there will be one. (aka this.roomToRoom.get(position) === undefined) - if (emptySpot !== position) { - // We shift all rooms to the next free spot either left or right both in memory and database - // First we chech if we need to shift left or right - - // Used to ensure we dont shift past the max or min since the empty spot may be above the max - const max = Math.max(...this.roomToRoom.keys()); - const min = Math.min(...this.roomToRoom.keys()); + console.warn("INSERT not implemented") + } else if (isDeleteOp(op)) { + console.log("Got DELETE OP", op); + const roomObj = [...this.rooms].find(room => room.windowPos[listKey] === op.index); + if (roomObj) { const tx = this.database?.transaction('rooms', 'readwrite'); - // If the empty spot is to the right of the position we shift right - if (emptySpot > position && emptySpot <= max) { - console.log("Shifting right"); - // Make sure we never shift past 0 - for (let i = 0; i < emptySpot && i >= min && i <= max; i++) { - console.log(i); - // TODO: This fails since we write to the new position but that still isn't moved. meaning we end up with overriding instead of shifting - const room = this.roomToRoom.get(i); - if (room !== undefined) { - await tx?.store.put({ - windowID: i + 1, - roomID: room.roomID, - name: room.getName(), - notification_count: room.getNotificationCount(), - highlight_count: room.getNotificationHighlightCount(), - joined_count: room.getJoinedCount(), - invited_count: room.getInvitedCount(), - avatarUrl: room.getAvatarURL(), - isSpace: room.isSpace(), - }); - this.roomToRoom.delete(i); - this.roomToRoom.set(i + 1, room); - } - } - console.log("List", this.roomToRoom); - } - // If the empty spot is to the left of the position we shift left - else if (emptySpot < position && emptySpot <= max) { - console.log("Shifting left"); - // Make sure we never shift past 0 - for (let i = 0; i > emptySpot && i >= min && i <= max; i--) { - const room = this.roomToRoom.get(i); - if (room !== undefined) { - await tx?.store.put({ - windowID: i - 1, - roomID: room.roomID, - name: room.getName(), - notification_count: room.getNotificationCount(), - highlight_count: room.getNotificationHighlightCount(), - joined_count: room.getJoinedCount(), - invited_count: room.getInvitedCount(), - avatarUrl: room.getAvatarURL(), - isSpace: room.isSpace(), - }); - this.roomToRoom.delete(i); - this.roomToRoom.set(i - 1, room); - } - } - console.log("List", this.roomToRoom); - } - console.log("Shifting done"); - console.log("Empty spot", this.findNextFreeIndex()); - console.log("Position", position); - console.log("List", this.roomToRoom); + await tx?.store.delete(roomObj.roomID); await tx?.done; + this.rooms.delete(roomObj) } - if (this.roomToRoom.get(position) !== undefined) { - console.error("Shifting failed"); - } - const newRoom = new Room(op.room_id, this.hostname!); - console.log(newRoom); - newRoom.setName("Unknown Room"); - this.roomToRoom.set(position, newRoom); - const tx = this.database?.transaction('rooms', 'readwrite'); - await tx?.store.put({ - windowID: position, - roomID: op.room_id, - name: newRoom.getName(), - notification_count: newRoom.getNotificationCount(), - highlight_count: newRoom.getNotificationHighlightCount(), - joined_count: newRoom.getJoinedCount(), - invited_count: newRoom.getInvitedCount(), - avatarUrl: newRoom.getAvatarURL(), - isSpace: newRoom.isSpace(), - }); - await tx?.done; - } else if (isDeleteOp(op)) { - console.log("Got DELETE OP", op); - const tx = this.database?.transaction('rooms', 'readwrite'); - await tx?.store.delete(op.index); - await tx?.done; - this.roomToRoom.delete(op.index); } else if (isInvalidateOp(op)) { const tx = this.database?.transaction('rooms', 'readwrite'); for (let i = op.range[0]; i <= op.range[1]; i++) { // We shall first forget about these and "startover" - await tx?.store.delete(i); - this.roomToRoom.delete(i); + const roomObj = [...this.rooms].find(room => room.windowPos[listKey] === i); + if (roomObj) { + await tx?.store.delete(roomObj.roomID); + this.rooms.delete(roomObj) + } } await tx?.done; } @@ -547,13 +473,9 @@ export class MatrixClient extends EventEmitter { const events = room.timeline; const required_state = room.required_state; - // Add the room data to the Room from this.roomToRoomID - // Note that the map key does not mean the roomID but the sync position - // RoomID is part of the value object of the map - // value.roomID is the roomID - const roomObj = [...this.roomToRoom.values()].find(room => room.roomID === roomID); - + const roomObj = [...this.rooms].find(room => room.roomID === roomID); if (!roomObj) { + // TODO: Warn but create missing room instead of failing on it console.error("Could not find roomObj for roomID", roomID); continue; } @@ -574,7 +496,7 @@ export class MatrixClient extends EventEmitter { // Write to database await tx?.store.put({ - windowID: [...this.roomToRoom.keys()].find(key => this.roomToRoom.get(key) === roomObj) as number, + windowPos: roomObj.windowPos, roomID: roomObj.roomID, name: roomObj.getName(), notification_count: roomObj.getNotificationCount(), @@ -589,7 +511,7 @@ export class MatrixClient extends EventEmitter { } await tx?.done if (json.rooms && Object.keys(json.rooms).length > 0) { - this.emit("rooms", new Set(this.roomToRoom.values())); + this.emit("rooms", this.rooms); } } @@ -612,11 +534,11 @@ export class MatrixClient extends EventEmitter { } public getRooms(): Set<Room> { - return new Set(this.roomToRoom.values()); + return this.rooms; } private getSpaces(): Room[] { - return [...this.roomToRoom.values()].filter(room => room.isSpace()); + return [...this.rooms].filter(room => room.isSpace()); } public getSpacesWithRooms(): Set<{ @@ -724,7 +646,6 @@ export class MatrixClient extends EventEmitter { // Set the sliding sync proxy this.slidingSyncHostname = well_known["org.matrix.msc3575.proxy"].url; } else { - // TODO: we might want to check for synapse support somehow throw Error("No sliding sync proxy found"); } } catch (e: any) { diff --git a/src/app/sdk/room.ts b/src/app/sdk/room.ts @@ -10,6 +10,10 @@ export class Room { private joined_count: number = 0; private invited_count: number = 0; + public windowPos: { + [list: string]: number + } = {} + constructor(public roomID: string, private hostname: string) { } @@ -37,7 +41,9 @@ export class Room { public isSpace(): boolean { let isSpace: boolean = false; this.stateEvents.forEach((event) => { - isSpace = isRoomCreateEvent(event) && event.content.type === "m.space"; + if (isRoomCreateEvent(event)) { + isSpace = event.content.type === "m.space"; + } }); return isSpace; }