From 49c36009cec8c88d61d796162787990216bfeeab Mon Sep 17 00:00:00 2001 From: Manos Pitsidianakis Date: Sat, 2 Sep 2023 20:40:57 +0300 Subject: [PATCH] mail/view: don't initialize entire thread at once For large threads, this would result in a lot of futures being created. The user just wants to read one entry, not all of them. So prioritize the open entry and some of the latest ones as an optimistic pre-fetching. Signed-off-by: Manos Pitsidianakis --- meli/src/mail/view.rs | 14 +++++++-- meli/src/mail/view/thread.rs | 57 ++++++++++++++++++++++++++++++++---- 2 files changed, 63 insertions(+), 8 deletions(-) diff --git a/meli/src/mail/view.rs b/meli/src/mail/view.rs index eea75aeb..0bd86d37 100644 --- a/meli/src/mail/view.rs +++ b/meli/src/mail/view.rs @@ -61,6 +61,7 @@ pub struct MailView { forward_dialog: Option>>>, theme_default: ThemeAttribute, active_jobs: HashSet, + initialized: bool, state: MailViewState, main_loop_handler: MainLoopHandler, id: ComponentId, @@ -86,6 +87,7 @@ impl Drop for MailView { impl MailView { pub fn new( coordinates: Option<(AccountHash, MailboxHash, EnvelopeHash)>, + initialize_now: bool, context: &mut Context, ) -> Self { let mut ret = MailView { @@ -95,12 +97,15 @@ impl MailView { forward_dialog: None, theme_default: crate::conf::value(context, "mail.view.body"), active_jobs: Default::default(), + initialized: false, state: MailViewState::default(), main_loop_handler: context.main_loop_handler.clone(), id: ComponentId::default(), }; - ret.init_futures(context); + if initialize_now { + ret.init_futures(context); + } ret } @@ -163,6 +168,7 @@ impl MailView { if let Some(p) = pending_action { self.perform_action(p, context); } + self.initialized = true; } fn perform_action(&mut self, action: PendingReplyAction, context: &mut Context) { @@ -284,6 +290,10 @@ impl Component for MailView { return; }; + if !self.initialized { + self.init_futures(context); + return; + } { let account = &context.accounts[&coordinates.0]; if !account.contains_key(coordinates.2) { @@ -782,7 +792,7 @@ impl Component for MailView { }; } UIEvent::Action(Listing(OpenInNewTab)) => { - let mut new_tab = Self::new(self.coordinates, context); + let mut new_tab = Self::new(self.coordinates, true, context); new_tab.set_dirty(true); context .replies diff --git a/meli/src/mail/view/thread.rs b/meli/src/mail/view/thread.rs index 2ae2d492..3227759b 100644 --- a/meli/src/mail/view/thread.rs +++ b/meli/src/mail/view/thread.rs @@ -182,10 +182,9 @@ impl ThreadView { #[inline(always)] fn make_entry( i: (usize, ThreadNodeHash, usize), - account_hash: AccountHash, - mailbox_hash: MailboxHash, - msg_hash: EnvelopeHash, + (account_hash, mailbox_hash, msg_hash): (AccountHash, MailboxHash, EnvelopeHash), seen: bool, + initialize_now: bool, timestamp: UnixTimestamp, context: &mut Context, ) -> ThreadEntry { @@ -195,6 +194,7 @@ impl ThreadView { indentation: ind, mailview: Box::new(MailView::new( Some((account_hash, mailbox_hash, msg_hash)), + initialize_now, context, )), msg_hash, @@ -214,6 +214,34 @@ impl ThreadView { } let (account_hash, mailbox_hash, _) = self.coordinates; + // Find out how many entries there are going to be, and prioritize + // initialization to the open entry and the most recent ones. + // + // This helps skip initializing the whole thread at once, which will make the UI + // loading slower. + // + // This won't help at all if the latest entry is a reply to an older entry but + // oh well. + let mut total_entries = vec![]; + for (_, thread_node_hash) in threads.thread_iter(self.thread_group) { + if let Some(msg_hash) = threads.thread_nodes()[&thread_node_hash].message() { + if Some(msg_hash) == expanded_hash { + continue; + } + let env_ref = collection.get_env(msg_hash); + total_entries.push((msg_hash, env_ref.timestamp)); + }; + } + total_entries.sort_by_key(|e| std::cmp::Reverse(e.1)); + let tokens = f64::from(u32::try_from(total_entries.len()).unwrap_or(0)) * 0.29; + let tokens = tokens.ceil() as usize; + total_entries.truncate(tokens); + + // Now, only the expanded envelope plus the ones that remained in total_entries + // (around 30% of the total messages in the thread) will be scheduled + // for loading immediately. The others will be lazily loaded when the + // user opens them for reading. + let thread_iter = threads.thread_iter(self.thread_group); self.entries.clear(); let mut earliest_unread = 0; @@ -231,12 +259,29 @@ impl ThreadView { } (env_ref.is_seen(), env_ref.timestamp) }; + let initialize_now = if total_entries.is_empty() { + false + } else { + // ExtractIf but it hasn't been stabilized yet. + // https://doc.rust-lang.org/std/vec/struct.Vec.html#method.extract_if + let mut i = 0; + let mut result = false; + while i < total_entries.len() { + if total_entries[i].0 == msg_hash { + total_entries.remove(i); + result = true; + break; + } else { + i += 1; + } + } + result + }; make_entry( (ind, thread_node_hash, line), - account_hash, - mailbox_hash, - msg_hash, + (account_hash, mailbox_hash, msg_hash), is_seen, + initialize_now || expanded_hash == Some(msg_hash), timestamp, context, )