client: swap to lists-as-keys

This commit is contained in:
Kegan Dougal 2023-02-13 17:13:42 +00:00
parent 3c964de004
commit 613cbd9a67
3 changed files with 71 additions and 70 deletions

View File

@ -113,7 +113,10 @@ export function svgify(domNode, activeLists, resp) {
}; };
// add insertions, deletions and updates // add insertions, deletions and updates
resp.lists.forEach((list, listIndex) => { let listIndex = 0;
const listKeys = Object.keys(resp.lists).sort();
listKeys.forEach((listKey) => {
const list = resp.lists[listKey];
list.ops.forEach((op) => { list.ops.forEach((op) => {
if (op.op === "DELETE") { if (op.op === "DELETE") {
addLine(listIndex, op.index, colorDelete); addLine(listIndex, op.index, colorDelete);
@ -135,6 +138,7 @@ export function svgify(domNode, activeLists, resp) {
); );
} }
}); });
listIndex++;
}); });
// this is expensive so only do it on smaller accounts // this is expensive so only do it on smaller accounts

View File

@ -241,7 +241,11 @@ const doSyncLoop = async (accessToken) => {
slidingSync.stop(); slidingSync.stop();
} }
console.log("Starting sync loop"); console.log("Starting sync loop");
slidingSync = new SlidingSync(activeLists, syncConnection); const lists = {};
activeLists.forEach((al, i) => {
lists[""+i] = al;
})
slidingSync = new SlidingSync(lists, syncConnection);
slidingSync.addLifecycleListener((state, resp, err) => { slidingSync.addLifecycleListener((state, resp, err) => {
switch (state) { switch (state) {
// The sync has been processed and we can now re-render the UI. // The sync has been processed and we can now re-render the UI.

View File

@ -147,11 +147,11 @@ export class SlidingList {
export class SlidingSync { export class SlidingSync {
/** /**
* Create a new sliding sync instance * Create a new sliding sync instance
* @param {[]SlidingList} lists The lists to use for sliding sync. * @param {Record<string, SlidingList>} lists The lists to use for sliding sync.
* @param {SlidingSyncConnection} conn The connection to use for /sync calls. * @param {SlidingSyncConnection} conn The connection to use for /sync calls.
*/ */
constructor(lists, conn) { constructor(lists, conn) {
this.lists = lists; this.lists = lists || {};
this.conn = conn; this.conn = conn;
this.terminated = false; this.terminated = false;
this.roomSubscription = ""; this.roomSubscription = "";
@ -217,8 +217,9 @@ export class SlidingSync {
let resp; let resp;
try { try {
// these fields are always required // these fields are always required
let reqBody = { const reqLists = {};
lists: this.lists.map((al) => { Object.keys(this.lists).forEach((listKey) => {
const al = this.lists[listKey];
let l = { let l = {
ranges: al.activeRanges, ranges: al.activeRanges,
filters: al.getFilters(), filters: al.getFilters(),
@ -233,8 +234,10 @@ export class SlidingSync {
"by_recency", "by_recency",
]; ];
} }
return l; reqLists[listKey] = l;
}), });
let reqBody = {
lists: reqLists,
}; };
// check if we are (un)subscribing to a room and modify request this one time for it // check if we are (un)subscribing to a room and modify request this one time for it
let subscribingToRoom; let subscribingToRoom;
@ -265,10 +268,14 @@ export class SlidingSync {
currentSub = subscribingToRoom; currentSub = subscribingToRoom;
} }
if (!resp.lists) { if (!resp.lists) {
resp.lists = []; resp.lists = {};
} }
resp.lists.forEach((l, index) => { Object.keys(resp.lists).forEach((key) => {
this.lists[index].joinedCount = l.count; const list = this.lists[key];
if (!list) {
return;
}
list.joinedCount = resp.lists[key].count;
}); });
this._invokeLifecycleListeners( this._invokeLifecycleListeners(
LifecycleSyncRequestFinished, LifecycleSyncRequestFinished,
@ -292,17 +299,18 @@ export class SlidingSync {
this._invokeRoomDataListeners(roomId, resp.rooms[roomId]); this._invokeRoomDataListeners(roomId, resp.rooms[roomId]);
}); });
resp.lists.forEach((list, listIndex) => { Object.keys(resp.lists).forEach((listKey) => {
const list = resp.lists[listKey];
let gapIndex = -1; let gapIndex = -1;
list.ops = list.ops || []; list.ops = list.ops || [];
list.ops.forEach((op) => { list.ops.forEach((op) => {
switch (op.op) { switch (op.op) {
case "DELETE": { case "DELETE": {
console.log("DELETE", listIndex, op.index, ";"); console.log("DELETE", listKey, op.index, ";");
delete this.lists[listIndex].roomIndexToRoomId[op.index]; delete this.lists[listKey].roomIndexToRoomId[op.index];
if (gapIndex !== -1) { if (gapIndex !== -1) {
// we already have a DELETE operation to process, so process it. // we already have a DELETE operation to process, so process it.
this.removeEntry(listIndex, gapIndex); this.removeEntry(listKey, gapIndex);
} }
gapIndex = op.index; gapIndex = op.index;
break; break;
@ -310,16 +318,16 @@ export class SlidingSync {
case "INSERT": { case "INSERT": {
console.log( console.log(
"INSERT", "INSERT",
listIndex, listKey,
op.index, op.index,
op.room_id, op.room_id,
";", ";",
); );
if (this.lists[listIndex].roomIndexToRoomId[op.index]) { if (this.lists[listKey].roomIndexToRoomId[op.index]) {
// something is in this space, shift items out of the way // something is in this space, shift items out of the way
if (gapIndex < 0) { if (gapIndex < 0) {
// we haven't been told where to shift from, so make way for a new room entry. // we haven't been told where to shift from, so make way for a new room entry.
this.addEntry(listIndex, op.index); this.addEntry(listKey, op.index);
} else if (gapIndex > op.index) { } else if (gapIndex > op.index) {
// the gap is further down the list, shift every element to the right // the gap is further down the list, shift every element to the right
// starting at the gap so we can just shift each element in turn: // starting at the gap so we can just shift each element in turn:
@ -328,11 +336,11 @@ export class SlidingSync {
// [A,B,B,C] i=2 // [A,B,B,C] i=2
// [A,A,B,C] i=1 // [A,A,B,C] i=1
// Terminate. We'll assign into op.index next. // Terminate. We'll assign into op.index next.
this.shiftRight(listIndex, gapIndex, op.index); this.shiftRight(listKey, gapIndex, op.index);
} else if (gapIndex < op.index) { } else if (gapIndex < op.index) {
// the gap is further up the list, shift every element to the left // the gap is further up the list, shift every element to the left
// starting at the gap so we can just shift each element in turn // starting at the gap so we can just shift each element in turn
this.shiftLeft(listIndex, op.index, gapIndex); this.shiftLeft(listKey, op.index, gapIndex);
} }
} }
// forget the gap, we don't need it anymore. This is outside the check for // forget the gap, we don't need it anymore. This is outside the check for
@ -340,17 +348,17 @@ export class SlidingSync {
// forget the gap, not conditionally based on the presence of a room in the INSERT // forget the gap, not conditionally based on the presence of a room in the INSERT
// position. Without this, DELETE 0; INSERT 0; would do the wrong thing. // position. Without this, DELETE 0; INSERT 0; would do the wrong thing.
gapIndex = -1; gapIndex = -1;
this.lists[listIndex].roomIndexToRoomId[op.index] = op.room_id; this.lists[listKey].roomIndexToRoomId[op.index] = op.room_id;
break; break;
} }
case "INVALIDATE": { case "INVALIDATE": {
const startIndex = op.range[0]; const startIndex = op.range[0];
for (let i = startIndex; i <= op.range[1]; i++) { for (let i = startIndex; i <= op.range[1]; i++) {
delete this.lists[listIndex].roomIndexToRoomId[i]; delete this.lists[listKey].roomIndexToRoomId[i];
} }
console.log( console.log(
"INVALIDATE", "INVALIDATE",
listIndex, listKey,
op.range[0], op.range[0],
op.range[1], op.range[1],
";", ";",
@ -364,11 +372,11 @@ export class SlidingSync {
if (!roomId) { if (!roomId) {
break; // we are at the end of list break; // we are at the end of list
} }
this.lists[listIndex].roomIndexToRoomId[i] = roomId; this.lists[listKey].roomIndexToRoomId[i] = roomId;
} }
console.log( console.log(
"SYNC", "SYNC",
listIndex, listKey,
op.range[0], op.range[0],
op.range[1], op.range[1],
(op.room_ids || []).join(" "), (op.room_ids || []).join(" "),
@ -381,7 +389,7 @@ export class SlidingSync {
if (gapIndex !== -1) { if (gapIndex !== -1) {
// we already have a DELETE operation to process, so process it // we already have a DELETE operation to process, so process it
// Everything higher than the gap needs to be shifted left. // Everything higher than the gap needs to be shifted left.
this.removeEntry(listIndex, gapIndex); this.removeEntry(listKey, gapIndex);
} }
}); });
@ -389,38 +397,38 @@ export class SlidingSync {
} }
} }
shiftRight(listIndex, hi, low) { shiftRight(listKey, hi, low) {
// l h // l h
// 0,1,2,3,4 <- before // 0,1,2,3,4 <- before
// 0,1,2,2,3 <- after, hi is deleted and low is duplicated // 0,1,2,2,3 <- after, hi is deleted and low is duplicated
for (let i = hi; i > low; i--) { for (let i = hi; i > low; i--) {
if (this.isIndexInRange(this.lists[listIndex], i)) { if (this.isIndexInRange(this.lists[listKey], i)) {
this.lists[listIndex].roomIndexToRoomId[i] = this.lists[listKey].roomIndexToRoomId[i] =
this.lists[listIndex].roomIndexToRoomId[ this.lists[listKey].roomIndexToRoomId[
i - 1 i - 1
]; ];
} }
} }
} }
shiftLeft(listIndex, hi, low) { shiftLeft(listKey, hi, low) {
// l h // l h
// 0,1,2,3,4 <- before // 0,1,2,3,4 <- before
// 0,1,3,4,4 <- after, low is deleted and hi is duplicated // 0,1,3,4,4 <- after, low is deleted and hi is duplicated
for (let i = low; i < hi; i++) { for (let i = low; i < hi; i++) {
if (this.isIndexInRange(this.lists[listIndex], i)) { if (this.isIndexInRange(this.lists[listKey], i)) {
this.lists[listIndex].roomIndexToRoomId[i] = this.lists[listKey].roomIndexToRoomId[i] =
this.lists[listIndex].roomIndexToRoomId[ this.lists[listKey].roomIndexToRoomId[
i + 1 i + 1
]; ];
} }
} }
} }
removeEntry(listIndex, index) { removeEntry(listKey, index) {
// work out the max index // work out the max index
let max = -1; let max = -1;
for (const n in this.lists[listIndex].roomIndexToRoomId) { for (const n in this.lists[listKey].roomIndexToRoomId) {
if (Number(n) > max) { if (Number(n) > max) {
max = Number(n); max = Number(n);
} }
@ -429,14 +437,14 @@ export class SlidingSync {
return; return;
} }
// Everything higher than the gap needs to be shifted left. // Everything higher than the gap needs to be shifted left.
this.shiftLeft(listIndex, max, index); this.shiftLeft(listKey, max, index);
delete this.lists[listIndex].roomIndexToRoomId[max]; delete this.lists[listKey].roomIndexToRoomId[max];
} }
addEntry(listIndex, index) { addEntry(listKey, index) {
// work out the max index // work out the max index
let max = -1; let max = -1;
for (const n in this.lists[listIndex].roomIndexToRoomId) { for (const n in this.lists[listKey].roomIndexToRoomId) {
if (Number(n) > max) { if (Number(n) > max) {
max = Number(n); max = Number(n);
} }
@ -445,7 +453,7 @@ export class SlidingSync {
return; return;
} }
// Everything higher than the gap needs to be shifted right, +1 so we don't delete the highest element // Everything higher than the gap needs to be shifted right, +1 so we don't delete the highest element
this.shiftRight(listIndex, max+1, index); this.shiftRight(listKey, max+1, index);
} }
isIndexInRange(list, i) { isIndexInRange(list, i) {
@ -461,18 +469,3 @@ export class SlidingSync {
const sleep = (ms) => { const sleep = (ms) => {
return new Promise((resolve) => setTimeout(resolve, ms)); return new Promise((resolve) => setTimeout(resolve, ms));
}; };
// SYNC 0 2 a b c; SYNC 6 8 d e f; DELETE 7; INSERT 0 e;
// 0 1 2 3 4 5 6 7 8
// a b c d e f
// a b c d _ f
// e a b c d f <--- c=3 is wrong as we are not tracking it, ergo we need to see if `i` is in range else drop it
const indexInRange = (listIndex, i) => {
let isInRange = false;
activeLists[listIndex].activeRanges.forEach((r) => {
if (r[0] <= i && i <= r[1]) {
isInRange = true;
}
});
return isInRange;
};