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 <manos@pitsidianak.is>
pull/295/head
Manos Pitsidianakis 2023-09-02 20:40:57 +03:00
parent c7825c76c3
commit 49c36009ce
Signed by: Manos Pitsidianakis
GPG Key ID: 7729C7707F7E09D0
2 changed files with 63 additions and 8 deletions

View File

@ -61,6 +61,7 @@ pub struct MailView {
forward_dialog: Option<Box<UIDialog<Option<PendingReplyAction>>>>,
theme_default: ThemeAttribute,
active_jobs: HashSet<JobId>,
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

View File

@ -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,
)