From 32c62641fd1265e42edda9e120ed6b153871a5b4 Mon Sep 17 00:00:00 2001 From: Kegan Dougal Date: Fri, 3 Dec 2021 13:52:13 +0000 Subject: [PATCH] Add Sync3ObservableList --- package.json | 1 + src/matrix/Sync3ObservableList.ts | 183 ++++++++++++++++++++++++++++++ yarn.lock | 5 + 3 files changed, 189 insertions(+) create mode 100644 src/matrix/Sync3ObservableList.ts diff --git a/package.json b/package.json index 017510d6..749711b8 100644 --- a/package.json +++ b/package.json @@ -58,6 +58,7 @@ "@rollup/plugin-commonjs": "^15.0.0", "@rollup/plugin-json": "^4.1.0", "@rollup/plugin-node-resolve": "^9.0.0", + "@types/assert": "^1.5.6", "aes-js": "^3.1.2", "another-json": "^0.2.0", "base64-arraybuffer": "^0.2.0", diff --git a/src/matrix/Sync3ObservableList.ts b/src/matrix/Sync3ObservableList.ts new file mode 100644 index 00000000..fdad67d9 --- /dev/null +++ b/src/matrix/Sync3ObservableList.ts @@ -0,0 +1,183 @@ +/* +Copyright 2021 The Matrix.org Foundation C.I.C. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +import * as assert from "assert"; +import { BaseObservableList } from "../observable/list/BaseObservableList"; +import { ObservableMap } from "../observable/map/ObservableMap.js"; +import { Room } from "./room/Room.js"; + +// subset of Sync3 functions used in this list; interfaced out for testing +interface ISync { + count(): number; + roomAtIndex(i: number): string +} + +/** + * An observable list that produces a subset of rooms based on Sync3 responses. + */ +export class Sync3ObservableList extends BaseObservableList { + sync: ISync; + rooms: ObservableMap; + + /** + * Construct an observable list that produces Rooms within the sliding window of Sync3 responses + * or `null` to indicate that a placeholder room should be used. + * @param sync3 The Sync3 class, which tracks up-to-date information on the sliding window / room counts. + * @param rooms The entire set of rooms known to the client (e.g from Session.rooms). + */ + constructor(sync3: ISync, rooms: ObservableMap) { + super(); + this.sync = sync3; + this.rooms = rooms; + } + + get length(): number { + return this.sync.count(); + } + + [Symbol.iterator](): Iterator { + let i = 0; + return { + next: (): any => { + // base case + if (i >= this.length) { + return { + done: true, + }; + } + let roomId = this.sync.roomAtIndex(i); + i += 1; + if (!roomId) { + return { + value: null + } + } + return { + value: this.rooms.get(roomId) || null, + } + } + } + } +} + +export function tests() { + const makeRooms = function (len) { + let rooms: any[] = []; + for (let i = 0; i < len; i++) { + const roomId = "!room-" + i; + rooms.push({ + id: roomId, + data: { + some_key: i, + }, + }); + } + return rooms; + } + + const assertList = function (assert, rooms, gotList, wantListRoomIds) { + if (wantListRoomIds.length === 0) { + assert.equal(gotList.length, 0); + for (const room of gotList) { + assert.equal(0, 1); // fail + } + return; + } + let i = 0; + for (const room of gotList) { + const wantRoomId = wantListRoomIds[i]; + const gotRoomId = room ? room.id : null; + assert.equal(wantRoomId, gotRoomId); + if (wantRoomId !== null && gotRoomId !== null) { + assert.deepEqual(room, rooms.get(wantRoomId)); + } + i += 1; + } + } + + return { + "iterator": (assert) => { + const rooms = new ObservableMap(); + let indexToRoomId = {}; + let roomCount = 0; + const sync3 = { + count: (): number => { + return roomCount; + }, + roomAtIndex: (i: number): string => { + return indexToRoomId[i]; + }, + }; + + makeRooms(100).forEach((r) => { + rooms.add(r.id, r); + }); + const list = new Sync3ObservableList(sync3, rooms); + + // iterate over the list (0 items) as sync3 has no indexes for the rooms + assertList(assert, rooms, list, []); + + + // 'load' 5 rooms from sync v3 and set the total count to 5 so we load the entire room list in one go! + // [R,R,R,R,R] + let slidingWindow: any[] = [ + "!room-50", "!room-53", "!room-1", "!room-52", "!room-97" + ] + roomCount = 5; + for (let i = 0; i < slidingWindow.length; i++) { + indexToRoomId[i] = slidingWindow[i]; + } + assertList(assert, rooms, list, slidingWindow); + + // now add 5 more rooms which we don't know about, we should iterate through them with `null` entries + // [R,R,R,R,R,P,P,P,P,P] (R=room, P=placeholder) + roomCount = 10; + assertList(assert, rooms, list, slidingWindow.concat([null, null, null, null, null])); + + + // now track a window in the middle of the list (5-10) + indexToRoomId = {}; + for (let i = 0; i < slidingWindow.length; i++) { + indexToRoomId[i + 5] = slidingWindow[i]; + } + roomCount = 15; + // [P,P,P,P,P,R,R,R,R,R,P,P,P,P,P] + assertList(assert, rooms, list, [null, null, null, null, null].concat(slidingWindow.concat([null, null, null, null, null]))); + + + // now track multiple ranges + const anotherSlidingWindow: any[] = [ + "!room-30", "!room-33", "!room-36", "!room-29", "!room-21" + ] + for (let i = 0; i < anotherSlidingWindow.length; i++) { + indexToRoomId[i + 15] = anotherSlidingWindow[i]; + } + roomCount = 20; + // [P,P,P,P,P,R,R,R,R,R,P,P,P,P,P,R,R,R,R,R] + assertList( + assert, rooms, list, + [null, null, null, null, null].concat( + slidingWindow.concat( + [null, null, null, null, null].concat( + anotherSlidingWindow + ) + ) + ) + ); + + }, + }; +}; \ No newline at end of file diff --git a/yarn.lock b/yarn.lock index 11356c4e..bad286a1 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1164,6 +1164,11 @@ resolved "https://registry.yarnpkg.com/@tootallnate/once/-/once-1.1.2.tgz#ccb91445360179a04e7fe6aff78c00ffc1eeaf82" integrity sha512-RbzJvlNzmRq5c3O09UipeuXno4tA1FE6ikOjxZK0tuxVv3412l64l5t1W5pj4+rJq9vpkm/kwiR07aZXnsKPxw== +"@types/assert@^1.5.6": + version "1.5.6" + resolved "https://registry.yarnpkg.com/@types/assert/-/assert-1.5.6.tgz#a8b5a94ce5fb8f4ba65fdc37fc9507609114189e" + integrity sha512-Y7gDJiIqb9qKUHfBQYOWGngUpLORtirAVPuj/CWJrU2C6ZM4/y3XLwuwfGMF8s7QzW746LQZx23m0+1FSgjfug== + "@types/cacheable-request@^6.0.1": version "6.0.2" resolved "https://registry.yarnpkg.com/@types/cacheable-request/-/cacheable-request-6.0.2.tgz#c324da0197de0a98a2312156536ae262429ff6b9"