Add Sync3ObservableList

This commit is contained in:
Kegan Dougal 2021-12-03 13:52:13 +00:00
parent 104d98d4a4
commit 32c62641fd
3 changed files with 189 additions and 0 deletions

View file

@ -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",

View file

@ -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<Room | null> {
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<Room | null, any, undefined> {
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
)
)
)
);
},
};
};

View file

@ -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"