feat(sdk): Implement EventCache
lazy-loading
#4632
Draft
+184
−124
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Apollo e Dafne (Bernini)
This is a follow-up of #4594.
The situation
Lazy-loading is now implemented on the
Timeline
's output. TheTimeline
outputs a certain maximum number of initial items. When a backwards pagination is run, theTimeline
first tries to paginate through in-memory items. When they are all exhausted, theTimeline
asks theEventCache
to provide more events, which will be transformed into items.Prior to this patch, the
EventCache
was reading the entire events of a room. This is wrong and it is the reason of some bugs:EventCache
(it relies on theLinkedChunk
which has been designed to represent gaps). However, because theTimeline
has no way to represent gaps, gaps are removed: only events are kept. TheTimeline
will do a pagination when it reaches one of its end, e.g. “I've reached my top, please feed me with more events!”. TheTimeline
isn't aware that events may be missing inside its set of events, because gaps have been removed. That's why users were seeing missing messages in theirTimeline
! And it was impossible to recover, except by clearing the cache.TimelineEventHandler::deduplicate_local_timeline_item
#4601, fix(ui): Fix performance ofAllRemoteEvents::(in|de)crement_all_timeline_item_index_after
#4608, fix(ui): Fix performance ofReadReceiptTimelineUpdate::apply
#4612, and fix(sdk): Improve performance ofRoomEvents::maybe_apply_new_redaction
#4616, theTimeline
is able to handle 10'000 events 10 times faster. TheTimeline
lazy-loading also dramatically reduces the number of items broadcasted to the apps/callers/consumers/subscribers. But still, theEventCache
outputs too many events, and theTimeline
hasn't been designed for that. TheEventCache
relies on theLinkedChunk
to hold the events. The capacity of a chunk in theLinkedChunk
is 128 in the current implementation, which means a chunk can contain up to 128 events. Much better than thousands.The solution
This patch is going to change the behaviour of the
EventCache
when its storage is enabled. Instead of loading all chunks, only the last one will be loaded. That's it. When theTimeline
will trigger a pagination, theEventCache
will first try to load the previous chunk if it exists, otherwise it means all events have been exhausted and a network pagination must be done. However, if the previous exists, there are 2 cases:Items
—i.e. it contains events—, then it's all good, the chunk is loaded, and updates will be broadcasted to theTimeline
, happy easy-peasy path.Gap
—i.e. it contains a… gap—, then a pagination is triggered over the network to fill this gap. The code will stay unchanged here, we already have this mechanism: theGap
is replaced byItems
, updates will be broadcasted to theTimeline
, happy slow path.This, will fix performance issues, and bugs (c.f. missing messages), but… this isn't the end of the journey.
Apollo e Dafne
Imagine the following scenario:
limited
flag)EventCache
, and so displayed by theTimeline
This behaviour can feel absurd for many reasons:
EventCache
, they are here (!), but we don't want to load them because they are before a gap, otherwise we end up in the current situation where we load all events, no matter the presence of gaps or not, and we end up in the missing message situation.One solution to this problem is to add a way for the
Timeline
to represent gaps. I propose to introduce (later, in another patch) a newVirtualTimelineItem
of kindGap
. It changes the behaviour of theTimeline
greatly: when aVirtualTimelineItem::Gap
enters the viewport of aTimeline
, the app/caller has to trigger a pagination. Such virtual timeline item can be rendered as a loader. TheTimeline
won't trigger a pagination when it reaches one of its end anymore: this newVirtualTimelineItem
of kindGap
will be entirely managed by theTimeline
. A new method will allow to fill/replace gaps, which will trigger a pagination from theEventCache
. Behaviour is still undefined and it raises many unknowns:Gap
in theTimeline
? When we are offline only?Gap
? It can be a bit disturbing for the user to see its messages, then on top of it a loader, then on top of it some events.EventCache
, do we always load this gap and its previous chunk?Well, it raises many many questions. We need to be extremely careful before digging into this.
An alternative exists though: automatic backwards pagination ✨. The SDK can automatically runs backwards pagination to fill all the existing gaps, in parallel of the sync. A correct heuristic must be determined to not bloat the network and to not drain the battery of the user's device (e.g. auto-run for the top most used rooms, up until n events, stuff like that, this is random ideas). It brings several advantages:
Timeline
.A note about Apollo e Dafne. First off, le Bernin is one of my favourite artist. Second, this sculputure is fantastic in many regards. The movements. The unique representation of this myth. The greek inspirations. The details (oh, the sandals…). Third, this patch evokes me this story of Apollo and Dafne. Apollo is in love with Dafne, and Dafne doesn't like him. Apollo is running after Dafne, and Dafne avoids him, escapes him as much as possible. This story is based on Cupido who shot two arrows: one made of gold to create love, another one made of lead to exhaust love. Every time we are going one step closer to perfect offline support, this goal slips away. #RomanticProgramming
EventCache
storage #3280