matrix_sdk_ui/timeline/controller/
read_receipts.rs

1// Copyright 2023 Kévin Commaille
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15use std::{cmp::Ordering, collections::HashMap};
16
17use futures_core::Stream;
18use indexmap::IndexMap;
19use ruma::{
20    events::receipt::{Receipt, ReceiptEventContent, ReceiptThread, ReceiptType},
21    EventId, OwnedEventId, OwnedUserId, UserId,
22};
23use tokio::sync::watch;
24use tokio_stream::wrappers::WatchStream;
25use tracing::{debug, error, warn};
26
27use super::{
28    rfind_event_by_id, AllRemoteEvents, FullEventMeta, ObservableItemsTransaction,
29    RelativePosition, RoomDataProvider, TimelineMetadata, TimelineState,
30};
31use crate::timeline::{controller::TimelineStateTransaction, TimelineItem};
32
33/// In-memory caches for read receipts.
34#[derive(Clone, Debug, Default)]
35pub(super) struct ReadReceipts {
36    /// Map of public read receipts on events.
37    ///
38    /// Event ID => User ID => Read receipt of the user.
39    by_event: HashMap<OwnedEventId, IndexMap<OwnedUserId, Receipt>>,
40
41    /// In-memory cache of all latest read receipts by user.
42    ///
43    /// User ID => Receipt type => Read receipt of the user of the given
44    /// type.
45    latest_by_user: HashMap<OwnedUserId, HashMap<ReceiptType, (OwnedEventId, Receipt)>>,
46
47    /// A sender to notify of changes to the receipts of our own user.
48    own_user_read_receipts_changed_sender: watch::Sender<()>,
49}
50
51impl ReadReceipts {
52    /// Empty the caches.
53    pub(super) fn clear(&mut self) {
54        self.by_event.clear();
55        self.latest_by_user.clear();
56    }
57
58    /// Subscribe to changes in the read receipts of our own user.
59    pub(super) fn subscribe_own_user_read_receipts_changed(&self) -> impl Stream<Item = ()> {
60        let subscriber = self.own_user_read_receipts_changed_sender.subscribe();
61        WatchStream::from_changes(subscriber)
62    }
63
64    /// Read the latest read receipt of the given type for the given user, from
65    /// the in-memory cache.
66    fn get_latest(
67        &self,
68        user_id: &UserId,
69        receipt_type: &ReceiptType,
70    ) -> Option<&(OwnedEventId, Receipt)> {
71        self.latest_by_user.get(user_id).and_then(|map| map.get(receipt_type))
72    }
73
74    /// Insert or update in the local cache the latest read receipt for the
75    /// given user.
76    fn upsert_latest(
77        &mut self,
78        user_id: OwnedUserId,
79        receipt_type: ReceiptType,
80        read_receipt: (OwnedEventId, Receipt),
81    ) {
82        self.latest_by_user.entry(user_id).or_default().insert(receipt_type, read_receipt);
83    }
84
85    /// Update the timeline items with the given read receipt if it is more
86    /// recent than the current one.
87    ///
88    /// In the process, if applicable, this method updates the inner maps to use
89    /// the new receipt. If `is_own_user_id` is `false`, it also updates the
90    /// receipts on the corresponding timeline items.
91    ///
92    /// Currently this method only works reliably if the timeline was started
93    /// from the end of the timeline.
94    fn maybe_update_read_receipt(
95        &mut self,
96        new_receipt: FullReceipt<'_>,
97        is_own_user_id: bool,
98        timeline_items: &mut ObservableItemsTransaction<'_>,
99    ) {
100        let all_events = timeline_items.all_remote_events();
101
102        // Get old receipt.
103        let old_receipt = self.get_latest(new_receipt.user_id, &new_receipt.receipt_type);
104
105        if old_receipt
106            .as_ref()
107            .is_some_and(|(old_receipt_event_id, _)| old_receipt_event_id == new_receipt.event_id)
108        {
109            // The receipt has not changed so there is nothing to do.
110            return;
111        }
112
113        let old_event_id = old_receipt.map(|(event_id, _)| event_id);
114
115        // Find receipts positions.
116        let mut old_receipt_pos = None;
117        let mut old_item_pos = None;
118        let mut old_item_event_id = None;
119        let mut new_receipt_pos = None;
120        let mut new_item_pos = None;
121        let mut new_item_event_id = None;
122
123        for (pos, event) in all_events.iter().rev().enumerate() {
124            if old_receipt_pos.is_none() && old_event_id == Some(&event.event_id) {
125                old_receipt_pos = Some(pos);
126            }
127
128            // The receipt should appear on the first event that is visible.
129            if old_receipt_pos.is_some() && old_item_event_id.is_none() && event.visible {
130                old_item_pos = event.timeline_item_index;
131                old_item_event_id = Some(event.event_id.clone());
132            }
133
134            if new_receipt_pos.is_none() && new_receipt.event_id == event.event_id {
135                new_receipt_pos = Some(pos);
136            }
137
138            // The receipt should appear on the first event that is visible.
139            if new_receipt_pos.is_some() && new_item_event_id.is_none() && event.visible {
140                new_item_pos = event.timeline_item_index;
141                new_item_event_id = Some(event.event_id.clone());
142            }
143
144            if old_item_event_id.is_some() && new_item_event_id.is_some() {
145                // We have everything we need, stop.
146                break;
147            }
148        }
149
150        // Check if the old receipt is more recent than the new receipt.
151        if let Some(old_receipt_pos) = old_receipt_pos {
152            let Some(new_receipt_pos) = new_receipt_pos else {
153                // The old receipt is more recent since we can't find the new receipt in the
154                // timeline and we supposedly have all events since the end of the timeline.
155                return;
156            };
157
158            if old_receipt_pos < new_receipt_pos {
159                // The old receipt is more recent than the new one.
160                return;
161            }
162        }
163
164        // The new receipt is deemed more recent from now on because:
165        // - If old_receipt_pos is Some, we already checked all the cases where it
166        //   wouldn't be more recent.
167        // - If both old_receipt_pos and new_receipt_pos are None, they are both
168        //   explicit read receipts so the server should only send us a more recent
169        //   receipt.
170        // - If old_receipt_pos is None and new_receipt_pos is Some, the new receipt is
171        //   more recent because it has a place in the timeline.
172
173        if !is_own_user_id {
174            // Remove the old receipt from the old event.
175            if let Some(old_event_id) = old_event_id.cloned() {
176                self.remove_event_receipt_for_user(&old_event_id, new_receipt.user_id);
177            }
178
179            // Add the new receipt to the new event.
180            self.add_event_receipt_for_user(
181                new_receipt.event_id.to_owned(),
182                new_receipt.user_id.to_owned(),
183                new_receipt.receipt.clone(),
184            );
185        }
186
187        // Update the receipt of the user.
188        self.upsert_latest(
189            new_receipt.user_id.to_owned(),
190            new_receipt.receipt_type,
191            (new_receipt.event_id.to_owned(), new_receipt.receipt.clone()),
192        );
193
194        if is_own_user_id {
195            self.own_user_read_receipts_changed_sender.send_replace(());
196            // This receipt cannot change items in the timeline.
197            return;
198        }
199
200        if new_item_event_id == old_item_event_id {
201            // The receipt did not change in the timeline.
202            return;
203        }
204
205        let timeline_update = ReadReceiptTimelineUpdate {
206            old_item_pos,
207            old_event_id: old_item_event_id,
208            new_item_pos,
209            new_event_id: new_item_event_id,
210        };
211
212        timeline_update.apply(
213            timeline_items,
214            new_receipt.user_id.to_owned(),
215            new_receipt.receipt.clone(),
216        );
217    }
218
219    /// Returns the cached receipts by user for a given `event_id`.
220    fn get_event_receipts(&self, event_id: &EventId) -> Option<&IndexMap<OwnedUserId, Receipt>> {
221        self.by_event.get(event_id)
222    }
223
224    /// Mark the given event as seen by the user with the given receipt.
225    fn add_event_receipt_for_user(
226        &mut self,
227        event_id: OwnedEventId,
228        user_id: OwnedUserId,
229        receipt: Receipt,
230    ) {
231        self.by_event.entry(event_id).or_default().insert(user_id, receipt);
232    }
233
234    /// Unmark the given event as seen by the user.
235    fn remove_event_receipt_for_user(&mut self, event_id: &EventId, user_id: &UserId) {
236        if let Some(map) = self.by_event.get_mut(event_id) {
237            map.swap_remove(user_id);
238            // Remove the entire map if this was the last entry.
239            if map.is_empty() {
240                self.by_event.remove(event_id);
241            }
242        }
243    }
244
245    /// Get the read receipts by user for the given event.
246    ///
247    /// This includes all the receipts on the event as well as all the receipts
248    /// on the following events that are filtered out (not visible).
249    pub(super) fn compute_event_receipts(
250        &self,
251        event_id: &EventId,
252        all_events: &AllRemoteEvents,
253        at_end: bool,
254    ) -> IndexMap<OwnedUserId, Receipt> {
255        let mut all_receipts = self.get_event_receipts(event_id).cloned().unwrap_or_default();
256
257        if at_end {
258            // No need to search for extra receipts, there are no events after.
259            return all_receipts;
260        }
261
262        // Find the event.
263        let events_iter = all_events.iter().skip_while(|meta| meta.event_id != event_id);
264
265        // Get past the event
266        let events_iter = events_iter.skip(1);
267
268        // Include receipts from all the following non-visible events.
269        for hidden_event_meta in events_iter.take_while(|meta| !meta.visible) {
270            if let Some(event_receipts) = self.get_event_receipts(&hidden_event_meta.event_id) {
271                all_receipts.extend(event_receipts.clone());
272            }
273        }
274
275        all_receipts
276    }
277}
278
279struct FullReceipt<'a> {
280    event_id: &'a EventId,
281    user_id: &'a UserId,
282    receipt_type: ReceiptType,
283    receipt: &'a Receipt,
284}
285
286/// A read receipt update in the timeline.
287#[derive(Clone, Debug, Default)]
288struct ReadReceiptTimelineUpdate {
289    /// The position of the timeline item that had the old receipt of the user,
290    /// if any.
291    old_item_pos: Option<usize>,
292    /// The old event that had the receipt of the user, if any.
293    old_event_id: Option<OwnedEventId>,
294    /// The position of the timeline item that has the new receipt of the user,
295    /// if any.
296    new_item_pos: Option<usize>,
297    /// The new event that has the receipt of the user, if any.
298    new_event_id: Option<OwnedEventId>,
299}
300
301impl ReadReceiptTimelineUpdate {
302    /// Remove the old receipt from the corresponding timeline item.
303    fn remove_old_receipt(&mut self, items: &mut ObservableItemsTransaction<'_>, user_id: &UserId) {
304        let Some(event_id) = &self.old_event_id else {
305            // Nothing to do.
306            return;
307        };
308
309        let item_pos = self.old_item_pos.or_else(|| {
310            items
311                .iter()
312                .enumerate()
313                .rev()
314                .filter_map(|(nth, item)| Some((nth, item.as_event()?)))
315                .find_map(|(nth, event_item)| {
316                    (event_item.event_id() == Some(event_id)).then_some(nth)
317                })
318        });
319
320        let Some(item_pos) = item_pos else {
321            debug!(%event_id, %user_id, "inconsistent state: old event item for read receipt was not found");
322            return;
323        };
324
325        self.old_item_pos = Some(item_pos);
326
327        let event_item = &items[item_pos];
328        let event_item_id = event_item.unique_id().to_owned();
329
330        let Some(mut event_item) = event_item.as_event().cloned() else {
331            warn!("received a read receipt for a virtual item, this should not be possible");
332            return;
333        };
334
335        if let Some(remote_event_item) = event_item.as_remote_mut() {
336            if remote_event_item.read_receipts.swap_remove(user_id).is_none() {
337                debug!(
338                    %event_id, %user_id,
339                    "inconsistent state: old event item for user's read \
340                     receipt doesn't have a receipt for the user"
341                );
342            }
343            items.replace(item_pos, TimelineItem::new(event_item, event_item_id));
344        } else {
345            warn!("received a read receipt for a local item, this should not be possible");
346        }
347    }
348
349    /// Add the new receipt to the corresponding timeline item.
350    fn add_new_receipt(
351        self,
352        items: &mut ObservableItemsTransaction<'_>,
353        user_id: OwnedUserId,
354        receipt: Receipt,
355    ) {
356        let Some(event_id) = self.new_event_id else {
357            // Nothing to do.
358            return;
359        };
360
361        let item_pos = self.new_item_pos.or_else(|| {
362            items
363                .iter()
364                .enumerate()
365                // Don't iterate over all items if the `old_item_pos` is known: the `item_pos`
366                // for the new item is necessarily _after_ the old item.
367                .skip(self.old_item_pos.unwrap_or(0))
368                .rev()
369                .filter_map(|(nth, item)| Some((nth, item.as_event()?)))
370                .find_map(|(nth, event_item)| {
371                    (event_item.event_id() == Some(&event_id)).then_some(nth)
372                })
373        });
374
375        let Some(item_pos) = item_pos else {
376            debug!(%event_id, %user_id, "inconsistent state: new event item for read receipt was not found");
377            return;
378        };
379
380        debug_assert!(
381            item_pos >= self.old_item_pos.unwrap_or(0),
382            "The new receipt must be added on a timeline item that is _after_ the timeline item that was holding the old receipt");
383
384        let event_item = &items[item_pos];
385        let event_item_id = event_item.unique_id().to_owned();
386
387        let Some(mut event_item) = event_item.as_event().cloned() else {
388            warn!("received a read receipt for a virtual item, this should not be possible");
389            return;
390        };
391
392        if let Some(remote_event_item) = event_item.as_remote_mut() {
393            remote_event_item.read_receipts.insert(user_id, receipt);
394            items.replace(item_pos, TimelineItem::new(event_item, event_item_id));
395        } else {
396            warn!("received a read receipt for a local item, this should not be possible");
397        }
398    }
399
400    /// Apply this update to the timeline.
401    fn apply(
402        mut self,
403        items: &mut ObservableItemsTransaction<'_>,
404        user_id: OwnedUserId,
405        receipt: Receipt,
406    ) {
407        self.remove_old_receipt(items, &user_id);
408        self.add_new_receipt(items, user_id, receipt);
409    }
410}
411
412impl TimelineStateTransaction<'_> {
413    pub(super) fn handle_explicit_read_receipts(
414        &mut self,
415        receipt_event_content: ReceiptEventContent,
416        own_user_id: &UserId,
417    ) {
418        for (event_id, receipt_types) in receipt_event_content.0 {
419            for (receipt_type, receipts) in receipt_types {
420                // We only care about read receipts here.
421                if !matches!(receipt_type, ReceiptType::Read | ReceiptType::ReadPrivate) {
422                    continue;
423                }
424
425                for (user_id, receipt) in receipts {
426                    if !matches!(receipt.thread, ReceiptThread::Unthreaded | ReceiptThread::Main) {
427                        continue;
428                    }
429
430                    let is_own_user_id = user_id == own_user_id;
431                    let full_receipt = FullReceipt {
432                        event_id: &event_id,
433                        user_id: &user_id,
434                        receipt_type: receipt_type.clone(),
435                        receipt: &receipt,
436                    };
437
438                    self.meta.read_receipts.maybe_update_read_receipt(
439                        full_receipt,
440                        is_own_user_id,
441                        &mut self.items,
442                    );
443                }
444            }
445        }
446    }
447
448    /// Load the read receipts from the store for the given event ID.
449    ///
450    /// Populates the read receipts in-memory caches.
451    pub(super) async fn load_read_receipts_for_event<P: RoomDataProvider>(
452        &mut self,
453        event_id: &EventId,
454        room_data_provider: &P,
455    ) {
456        let read_receipts = room_data_provider.load_event_receipts(event_id).await;
457        let own_user_id = room_data_provider.own_user_id();
458
459        // Since they are explicit read receipts, we need to check if they are
460        // superseded by implicit read receipts.
461        for (user_id, receipt) in read_receipts {
462            let full_receipt = FullReceipt {
463                event_id,
464                user_id: &user_id,
465                receipt_type: ReceiptType::Read,
466                receipt: &receipt,
467            };
468
469            self.meta.read_receipts.maybe_update_read_receipt(
470                full_receipt,
471                user_id == own_user_id,
472                &mut self.items,
473            );
474        }
475    }
476
477    /// Add an implicit read receipt to the given event item, if it is more
478    /// recent than the current read receipt for the sender of the event.
479    ///
480    /// According to the spec, read receipts should not point to events sent by
481    /// our own user, but these events are used to reset the notification
482    /// count, so we need to handle them locally too. For that we create an
483    /// "implicit" read receipt, compared to the "explicit" ones sent by the
484    /// client.
485    pub(super) fn maybe_add_implicit_read_receipt(&mut self, event_meta: FullEventMeta<'_>) {
486        let FullEventMeta { event_id, sender, is_own_event, timestamp, .. } = event_meta;
487
488        let (Some(user_id), Some(timestamp)) = (sender, timestamp) else {
489            // We cannot add a read receipt if we do not know the user or the timestamp.
490            return;
491        };
492
493        let receipt = Receipt::new(timestamp);
494        let full_receipt =
495            FullReceipt { event_id, user_id, receipt_type: ReceiptType::Read, receipt: &receipt };
496
497        self.meta.read_receipts.maybe_update_read_receipt(
498            full_receipt,
499            is_own_event,
500            &mut self.items,
501        );
502    }
503
504    /// Update the read receipts on the event with the given event ID and the
505    /// previous visible event because of a visibility change.
506    pub(super) fn maybe_update_read_receipts_of_prev_event(&mut self, event_id: &EventId) {
507        // Find the previous visible event, if there is one.
508        let Some(prev_event_meta) = self
509            .items
510            .all_remote_events()
511            .iter()
512            .rev()
513            // Find the event item.
514            .skip_while(|meta| meta.event_id != event_id)
515            // Go past the event item.
516            .skip(1)
517            // Find the first visible item.
518            .find(|meta| meta.visible)
519        else {
520            return;
521        };
522
523        let Some((prev_item_pos, prev_event_item)) =
524            rfind_event_by_id(&self.items, &prev_event_meta.event_id)
525        else {
526            error!("inconsistent state: timeline item of visible event was not found");
527            return;
528        };
529
530        let prev_event_item_id = prev_event_item.internal_id.to_owned();
531        let mut prev_event_item = prev_event_item.clone();
532
533        let Some(remote_prev_event_item) = prev_event_item.as_remote_mut() else {
534            warn!("loading read receipts for a local item, this should not be possible");
535            return;
536        };
537
538        let read_receipts = self.meta.read_receipts.compute_event_receipts(
539            &remote_prev_event_item.event_id,
540            self.items.all_remote_events(),
541            false,
542        );
543
544        // If the count did not change, the receipts did not change either.
545        if read_receipts.len() == remote_prev_event_item.read_receipts.len() {
546            return;
547        }
548
549        remote_prev_event_item.read_receipts = read_receipts;
550        self.items.replace(prev_item_pos, TimelineItem::new(prev_event_item, prev_event_item_id));
551    }
552}
553
554impl TimelineState {
555    /// Populates our own latest read receipt in the in-memory by-user read
556    /// receipt cache.
557    pub(super) async fn populate_initial_user_receipt<P: RoomDataProvider>(
558        &mut self,
559        room_data_provider: &P,
560        receipt_type: ReceiptType,
561    ) {
562        let own_user_id = room_data_provider.own_user_id().to_owned();
563
564        let mut read_receipt = room_data_provider
565            .load_user_receipt(receipt_type.clone(), ReceiptThread::Unthreaded, &own_user_id)
566            .await;
567
568        // Fallback to the one in the main thread.
569        if read_receipt.is_none() {
570            read_receipt = room_data_provider
571                .load_user_receipt(receipt_type.clone(), ReceiptThread::Main, &own_user_id)
572                .await;
573        }
574
575        if let Some(read_receipt) = read_receipt {
576            self.meta.read_receipts.upsert_latest(own_user_id, receipt_type, read_receipt);
577        }
578    }
579
580    /// Get the latest read receipt for the given user.
581    ///
582    /// Useful to get the latest read receipt, whether it's private or public.
583    pub(super) async fn latest_user_read_receipt<P: RoomDataProvider>(
584        &self,
585        user_id: &UserId,
586        room_data_provider: &P,
587    ) -> Option<(OwnedEventId, Receipt)> {
588        let all_remote_events = self.items.all_remote_events();
589        let public_read_receipt = self
590            .meta
591            .user_receipt(user_id, ReceiptType::Read, room_data_provider, all_remote_events)
592            .await;
593        let private_read_receipt = self
594            .meta
595            .user_receipt(user_id, ReceiptType::ReadPrivate, room_data_provider, all_remote_events)
596            .await;
597
598        // Let's assume that a private read receipt should be more recent than a public
599        // read receipt, otherwise there's no point in the private read receipt,
600        // and use it as default.
601        match self.meta.compare_optional_receipts(
602            public_read_receipt.as_ref(),
603            private_read_receipt.as_ref(),
604            self.items.all_remote_events(),
605        ) {
606            Ordering::Greater => public_read_receipt,
607            Ordering::Less => private_read_receipt,
608            _ => unreachable!(),
609        }
610    }
611
612    /// Get the ID of the visible timeline event with the latest read receipt
613    /// for the given user.
614    pub(super) fn latest_user_read_receipt_timeline_event_id(
615        &self,
616        user_id: &UserId,
617    ) -> Option<OwnedEventId> {
618        // We only need to use the local map, since receipts for known events are
619        // already loaded from the store.
620        let public_read_receipt = self.meta.read_receipts.get_latest(user_id, &ReceiptType::Read);
621        let private_read_receipt =
622            self.meta.read_receipts.get_latest(user_id, &ReceiptType::ReadPrivate);
623
624        // Let's assume that a private read receipt should be more recent than a public
625        // read receipt, otherwise there's no point in the private read receipt,
626        // and use it as default.
627        let (latest_receipt_id, _) = match self.meta.compare_optional_receipts(
628            public_read_receipt,
629            private_read_receipt,
630            self.items.all_remote_events(),
631        ) {
632            Ordering::Greater => public_read_receipt?,
633            Ordering::Less => private_read_receipt?,
634            _ => unreachable!(),
635        };
636
637        // Find the corresponding visible event.
638        self.items
639            .all_remote_events()
640            .iter()
641            .rev()
642            .skip_while(|ev| ev.event_id != *latest_receipt_id)
643            .find(|ev| ev.visible)
644            .map(|ev| ev.event_id.clone())
645    }
646}
647
648impl TimelineMetadata {
649    /// Get the latest receipt of the given type for the given user in the
650    /// timeline.
651    ///
652    /// This will attempt to read the latest user receipt for a user from the
653    /// cache, or load it from the storage if missing from the cache.
654    pub(super) async fn user_receipt<P: RoomDataProvider>(
655        &self,
656        user_id: &UserId,
657        receipt_type: ReceiptType,
658        room_data_provider: &P,
659        all_remote_events: &AllRemoteEvents,
660    ) -> Option<(OwnedEventId, Receipt)> {
661        if let Some(receipt) = self.read_receipts.get_latest(user_id, &receipt_type) {
662            // Since it is in the timeline, it should be the most recent.
663            return Some(receipt.clone());
664        }
665
666        let unthreaded_read_receipt = room_data_provider
667            .load_user_receipt(receipt_type.clone(), ReceiptThread::Unthreaded, user_id)
668            .await;
669
670        let main_thread_read_receipt = room_data_provider
671            .load_user_receipt(receipt_type.clone(), ReceiptThread::Main, user_id)
672            .await;
673
674        // Let's use the unthreaded read receipt as default, since it's the one we
675        // should be using.
676        match self.compare_optional_receipts(
677            main_thread_read_receipt.as_ref(),
678            unthreaded_read_receipt.as_ref(),
679            all_remote_events,
680        ) {
681            Ordering::Greater => main_thread_read_receipt,
682            Ordering::Less => unthreaded_read_receipt,
683            _ => unreachable!(),
684        }
685    }
686
687    /// Compares two optional receipts to know which one is more recent.
688    ///
689    /// Returns `Ordering::Greater` if the left-hand side is more recent than
690    /// the right-hand side, and `Ordering::Less` if it is older. If it's
691    /// not possible to know which one is the more recent, defaults to
692    /// `Ordering::Less`, making the right-hand side the default.
693    fn compare_optional_receipts(
694        &self,
695        lhs: Option<&(OwnedEventId, Receipt)>,
696        rhs_or_default: Option<&(OwnedEventId, Receipt)>,
697        all_remote_events: &AllRemoteEvents,
698    ) -> Ordering {
699        // If we only have one, use it.
700        let Some((lhs_event_id, lhs_receipt)) = lhs else {
701            return Ordering::Less;
702        };
703        let Some((rhs_event_id, rhs_receipt)) = rhs_or_default else {
704            return Ordering::Greater;
705        };
706
707        // Compare by position in the timeline.
708        if let Some(relative_pos) =
709            self.compare_events_positions(lhs_event_id, rhs_event_id, all_remote_events)
710        {
711            if relative_pos == RelativePosition::Before {
712                return Ordering::Greater;
713            }
714
715            return Ordering::Less;
716        }
717
718        // Compare by timestamp.
719        if let Some((lhs_ts, rhs_ts)) = lhs_receipt.ts.zip(rhs_receipt.ts) {
720            if lhs_ts > rhs_ts {
721                return Ordering::Greater;
722            }
723
724            return Ordering::Less;
725        }
726
727        Ordering::Less
728    }
729}