1
0
mirror of https://github.com/matrix-org/matrix-js-sdk.git synced 2025-09-01 21:21:58 +03:00

Restructure timeline batches to have high numbers = newest; it's less confusing that way.

This commit is contained in:
Kegan Dougal
2015-07-01 15:28:41 +01:00
parent 22506513b4
commit 732f9dc592
2 changed files with 119 additions and 80 deletions

View File

@@ -19,7 +19,7 @@
* --------------
* Retrieving a room requires the $ROOMID which then pulls out the current state
* from room_$ROOMID_state. A defined starting batch of timeline events are then
* extracted from the lowest numbered $INDEX for room_$ROOMID_timeline_$INDEX
* extracted from the highest numbered $INDEX for room_$ROOMID_timeline_$INDEX
* (more indices as required). The $INDEX may be negative. These are
* added to the timeline in the same way as /initialSync (old state will diverge).
* If there exists a room_$ROOMID_timeline_live key, then a timeline sync should
@@ -30,9 +30,9 @@
* The earliest event the Room instance knows about is E. Retrieving earlier
* messages requires a Room which has a storageToken defined.
* This token maps to the index I where the Room is at. Events are then retrieved from
* room_$ROOMID_timeline_{I} and events after E are extracted. If the limit
* demands more events, I+1 is retrieved, up until I=max $INDEX where it gives
* less than the limit.
* room_$ROOMID_timeline_{I} and elements before E are extracted. If the limit
* demands more events, I-1 is retrieved, up until I=min $INDEX where it gives
* less than the limit. Index may go negative if you have paginated in the past.
*
* Full Insertion
* --------------
@@ -45,8 +45,7 @@
* Incremental Insertion
* ---------------------
* As events arrive, the store can quickly persist these new events. This
* involves pushing the events to room_$ROOMID_timeline_live. This results in an
* inverted ordering where the highest number is the most recent entry. If the
* involves pushing the events to room_$ROOMID_timeline_live. If the
* current room state has been modified by the new event, then
* room_$ROOMID_state should be updated in addition to the timeline.
*
@@ -56,24 +55,23 @@
* events. This is computationally expensive to perform on every new event, so
* is deferred by inserting live events to room_$ROOMID_timeline_live. A
* timeline sync reconciles timeline_live and timeline_$INDEX. This involves
* retrieving _live and the lowest numbered $INDEX batch. If the batch is < B,
* the earliest entries are inserted into the $INDEX (the earliest entries are
* inverted in _live, so the earliest entry is at index 0, not len-1) until the
* batch == B. Then, the remaining entries in _live are batched to $INDEX-1,
* $INDEX-2, and so on. This will result in negative indices. The easiest way to
* visualise this is that the timeline goes from new to old, left to right:
* retrieving _live and the highest numbered $INDEX batch. If the batch is < B,
* the earliest entries from _live are inserted into the $INDEX until the
* batch == B. Then, the remaining entries in _live are batched to $INDEX+1,
* $INDEX+2, and so on. The easiest way to visualise this is that the timeline
* goes from old to new, left to right:
* -2 -1 0 1
* <--NEW---------------------------------------OLD-->
* <--OLD---------------------------------------NEW-->
* [a,b,c] [d,e,f] [g,h,i] [j,k,l]
*
* Purging
* -------
* Events from the timeline can be purged by removing the highest
* Events from the timeline can be purged by removing the lowest
* timeline_$INDEX in the store.
*
* Example
* -------
* A room with room_id !foo:bar has 9 messages (M1->9 where 1=newest) with a
* A room with room_id !foo:bar has 9 messages (M1->9 where 9=newest) with a
* batch size of 4. The very first time, there is no entry for !foo:bar until
* storeRoom() is called, which results in the keys: [Full Insert]
* room_!foo:bar_timeline_0 : [M1, M2, M3, M4]
@@ -81,31 +79,33 @@
* room_!foo:bar_timeline_2 : [M9]
* room_!foo:bar_state: { ... }
*
* 5 new messages (N1-5, 1=newest) arrive and are then added: [Incremental Insert]
* room_!foo:bar_timeline_live: [N5]
* room_!foo:bar_timeline_live: [N5, N4]
* room_!foo:bar_timeline_live: [N5, N4, N3]
* room_!foo:bar_timeline_live: [N5, N4, N3, N2]
* room_!foo:bar_timeline_live: [N5, N4, N3, N2, N1]
* 5 new messages (N1-5, 5=newest) arrive and are then added: [Incremental Insert]
* room_!foo:bar_timeline_live: [N1]
* room_!foo:bar_timeline_live: [N1, N2]
* room_!foo:bar_timeline_live: [N1, N2, N3]
* room_!foo:bar_timeline_live: [N1, N2, N3, N4]
* room_!foo:bar_timeline_live: [N1, N2, N3, N4, N5]
*
* App is shutdown. Restarts. The timeline is synced [Timeline Sync]
* room_!foo:bar_timeline_-1 : [N2, N3, N4, N5]
* room_!foo:bar_timeline_-2 : [N1]
* room_!foo:bar_timeline_2 : [M9, N1, N2, N3]
* room_!foo:bar_timeline_3 : [N4, N5]
* room_!foo:bar_timeline_live: []
*
* And the room is retrieved with 8 messages: [Room Retrieval]
* Room.timeline: [N1, N2, N3, N4, N5, M1, M2, M3]
* Room.storageToken: => early_index 0
* Room.timeline: [M7, M8, M9, N1, N2, N3, N4, N5]
* Room.storageToken: => early_index = 1 because that's where M7 is.
*
* 3 earlier messages are requested: [Earlier retrieval]
* Use storageToken to find batch index 0. Scan batch for earliest event ID.
* earliest event = M3
* events = room_!foo:bar_timeline[0] where event > M3 = [M4]
* Too few events, use next index and get 2 more:
* events = room_!foo:bar_timeline[1] = [M5, M6, M7, M8] => [M5, M6]
* Use storageToken to find batch index 1. Scan batch for earliest event ID.
* earliest event = M7
* events = room_!foo:bar_timeline_1 where event < M7 = [M5, M6]
* Too few events, use next index (0) and get 1 more:
* events = room_!foo:bar_timeline_0 = [M1, M2, M3, M4] => [M4]
* Return concatentation:
* [M4, M5, M6]
*
* Purge oldest events: [Purge]
* del room_!foo:bar_timeline_2
* del room_!foo:bar_timeline_0
* </pre>
* @module store/webstorage
*/
@@ -178,7 +178,6 @@ WebStorageStore.prototype.getRoom = function(roomId) {
}
var timelineKeys = getTimelineIndices(this.store, roomId);
if (timelineKeys.indexOf("live") !== -1) {
console.log("Syncing live");
this._syncTimeline(roomId, timelineKeys);
}
return loadRoom(this.store, roomId, this.batchSize);
@@ -237,27 +236,25 @@ WebStorageStore.prototype._syncTimeline = function(roomId, timelineIndices) {
timelineIndices = timelineIndices || getTimelineIndices(this.store, roomId);
var liveEvents = this.store.getItem(keyName(roomId, "timeline", "live")) || [];
// get the lowest numbered $INDEX batch
var lowestIndex = getLowestIndex(timelineIndices);
var lowKey = keyName(roomId, "timeline", lowestIndex);
var lowestBatch = this.store.getItem(lowKey) || [];
console.log("Live Events = %s, Low batch = %s (i=%s;b=%s)", liveEvents.length,
lowestBatch.length, lowestIndex, this.batchSize);
// get the highest numbered $INDEX batch
var highestIndex = getHighestIndex(timelineIndices);
var hiKey = keyName(roomId, "timeline", highestIndex);
var hiBatch = this.store.getItem(hiKey) || [];
// fill up the existing batch first.
while (lowestBatch.length < this.batchSize && liveEvents.length > 0) {
lowestBatch.unshift(liveEvents.shift());
while (hiBatch.length < this.batchSize && liveEvents.length > 0) {
hiBatch.push(liveEvents.shift());
}
this.store.setItem(lowKey, lowestBatch);
this.store.setItem(hiKey, hiBatch);
// start adding new batches as required
var batch = [];
while (liveEvents.length > 0) {
batch.unshift(liveEvents.shift());
batch.push(liveEvents.shift());
if (batch.length === this.batchSize || liveEvents.length === 0) {
// persist the full batch and make another
lowestIndex--;
lowKey = keyName(roomId, "timeline", lowestIndex);
this.store.setItem(lowKey, batch);
highestIndex++;
hiKey = keyName(roomId, "timeline", highestIndex);
this.store.setItem(hiKey, batch);
batch = [];
}
}
@@ -284,7 +281,7 @@ function SerialisedRoom(roomId) {
*/
SerialisedRoom.fromRoom = function(room, batchSize) {
var self = new SerialisedRoom(room.roomId);
var ptr;
var index;
self.state.pagination_token = room.oldState.paginationToken;
// [room_$ROOMID_state] downcast to POJO from MatrixEvent
utils.forEach(utils.keys(room.currentState.events), function(eventType) {
@@ -300,16 +297,16 @@ SerialisedRoom.fromRoom = function(room, batchSize) {
// [room_$ROOMID_timeline_$INDEX]
if (batchSize > 0) {
ptr = 0;
while (ptr * batchSize < room.timeline.length) {
self.timeline[ptr] = room.timeline.slice(
ptr * batchSize, (ptr + 1) * batchSize
index = 0;
while (index * batchSize < room.timeline.length) {
self.timeline[index] = room.timeline.slice(
index * batchSize, (index + 1) * batchSize
);
self.timeline[ptr] = utils.map(self.timeline[ptr], function(me) {
self.timeline[index] = utils.map(self.timeline[index], function(me) {
// use POJO not MatrixEvent
return me.event;
});
ptr++;
index++;
}
}
else { // don't batch
@@ -345,7 +342,7 @@ function loadRoom(store, roomId, numEvents) {
// add most recent numEvents
var recentEvents = [];
var index = getLowestIndex(getTimelineIndices(store, roomId));
var index = getHighestIndex(getTimelineIndices(store, roomId));
var i, key, batch;
while (recentEvents.length < numEvents) {
key = keyName(roomId, "timeline", index);
@@ -354,14 +351,15 @@ function loadRoom(store, roomId, numEvents) {
// nothing left in the store.
break;
}
for (i = 0; i < batch.length; i++) {
for (i = batch.length - 1; i >= 0; i--) {
recentEvents.unshift(new MatrixEvent(batch[i]));
if (recentEvents.length === numEvents) {
break;
}
}
index++;
index--;
}
// add events backwards to diverge old state correctly.
room.addEventsToTimeline(recentEvents.reverse(), true);
room.oldState.paginationToken = currentStateMap.pagination_token;
return room;
@@ -390,16 +388,15 @@ function getTimelineIndices(store, roomId) {
return keys;
}
function getLowestIndex(timelineIndices) {
var lowestIndex = 0;
var index;
function getHighestIndex(timelineIndices) {
var highestIndex, index;
for (var i = 0; i < timelineIndices.length; i++) {
index = parseInt(timelineIndices[i]);
if (index && index < lowestIndex) {
lowestIndex = index;
if (!isNaN(index) && (highestIndex === undefined || index > highestIndex)) {
highestIndex = index;
}
}
return lowestIndex;
return highestIndex;
}
function keyName(roomId, key, index) {