-
Notifications
You must be signed in to change notification settings - Fork 286
refactor(timeline): finish the aggregations refactoring #5046
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
This allows having a redaction event come before the related event, and still handle it when the redacted event shows up.
…ply_all` It applies *all* the stashed aggregations for a given event timeline item.
…on<&ReactionsByKeyBySender>` instead of owned non-optional
… applying an aggregation onto it
…ng it do more work This avoids another struct definition, and items are going to be needed for edits anyways.
…take care of updating the item
To be fair, this is a regression from a previous commit in this PR.
…ding the edit json ahead of time
The poll events's `related_to` field is a `RelationWithoutReplacement`, while the two others are `Relation<C>`, where `C` is the event content type (in case it was replacement). As a matter of fact, we try converting the `Relation<C>` into a `RelationWithoutReplacement` (which unfortunately requires cloning, which is wasteful if the relation was a replacement indeed), and then we can use a single function to extract the reply information and thread root info, for all three.
We only need the edit_json if we're about to save the edit aggregation. Likewise, if there's no current event id (i.e. the event being handled is a local echo), then we don't need to even try to extract anything from the bundle information.
This can be done by splitting the handling of the msgtype/mentions from the handling of the `relates_to` field, requiring a few API changes here and there.
0a95063
to
73df64e
Compare
Now rebased and ready for review! Even though there are many, the changes ought to be digestible in a commit per commit read-through. |
Codecov ReportAttention: Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #5046 +/- ##
==========================================
+ Coverage 85.85% 85.94% +0.09%
==========================================
Files 325 325
Lines 35937 35941 +4
==========================================
+ Hits 30852 30889 +37
+ Misses 5085 5052 -33 ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's a big PR with many changes. I've spotted a couple of optimisations. Approving ahead of time to avoid another round a review. The main purpose of this patch is reached I think. Good job!
// These items don't hold reactions. | ||
return ApplyAggregationResult::LeftItemIntact; | ||
}; | ||
|
||
let mut reactions = reactions.clone(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I suspect it is possible to rewrite this part without the clone.
Right now, the code does the following:
- clone all reactions
- find a specific reaction or create it if it doesn't exist, insert a new sender for this reaction
- if this reaction for this sender was present,
previous_reaction
equalsSome(…)
- we check the condition to compute
is_same
- if
is_same
is true, we returnLeftItemIntact
, otherwise we get the reactions as a mutable, and update it.
The proposal is the following:
- get the reactions as a mutable directly
- check if the specific reaction exists with
entry
without callinginsert
- if it returns
None
, we can early return withLeftItemIntact
, otherwise computeis_same
and theninsert
if needed
I believe that get
(or entry
) + insert
later is cheaper than a clone here. Something like this:
let Some(mut reactions) = event.content_mut().reactions_mut() else { … }
let reaction_info = ReactionInfo { timestamp: *timestamp, status: reaction_status.clone() };
match reactions.entry(key.clone()) {
Entry::Vacant(entry) => {
entry.insert(sender.clone(), reaction_info);
ApplyAggregationResult::UpdatedItem
}
Entry::Occupied(entry) => {
let mut prev = entry.get_mut();
let is_same = prev.timestmp == *timestamp && matches!(…);
if is_same {
ApplyAggregationResult::LeftItemIntact
} else {
prev.insert(sender.clone(), reaction_info);
ApplyAggregationResult::UpdatedItem
}
}
}
Something like this. Thoughts?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's possible to do this, but there's one change in the code excerpt you posted that would be needed:
let Some(mut reactions) = event.to_mut().content_mut().reactions_mut() else { … }
This to_mut()
eagerly marks the Cow
as written to, so eagerly clones the underlying content if it was borrowed. In that case, we might be cloning even though the final item isn't actually mutated. So we'd need to do another dance:
- on the read-only version of reaction, check first if there's a similar reaction
- if not, do the mutable transition and add it.
I've done that locally; it's a bit more code, but it's OK and should avoid a few useless clones in some edge cases.
@@ -491,6 +493,8 @@ impl TimelineAction { | |||
new_content, | |||
)), | |||
edit_json, | |||
// TODO maybe we could provide it? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Has it been addressed?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It can be done, I'll open a followup PR to keep changes tidy.
crates/matrix-sdk-ui/src/timeline/controller/observable_items.rs
Outdated
Show resolved
Hide resolved
@@ -75,6 +75,8 @@ pub(in crate::timeline) struct TimelineMetadata { | |||
pub aggregations: Aggregations, | |||
|
|||
/// Given an event, what are all the events that are replies to it? | |||
/// | |||
/// Only works for remote events *and* replies which are remote-echoed. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should we rename replies
to remote_replies
? Not convinced, just asking.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's unclear in remote_replies
which side is remote
; in this case, it's both the original and the reply which must have been remote-echoed, and I think this information is more clearly described from the code comments + types used (only OwnedEventId
, not TimelineEventItemId
anywhere). If you don't mind too much, I'll keep this name.
This uses the
Aggregations
manager to take care of redactions and edits, allowing us to get rid of thepending_edits
in theTimelineMetadata
\o/ Aggregations are now handled in a single manner, which is nice for consistency.For redactions, this means we now stash a redaction even if we've seen it before the event it redacts, making sure we won't display something that should've been redacted.
For edits, we now do "the right thing", and will look at the position of each edit event we know about, and use the most "recent" one, if we've found any. This will help handling edits within threads as well (as such, helps with #4869), instead of having us implement some more complicated logic for those.
Part of #4823.