From 5dc54863a402a81aa9a3e864adc73964713933b6 Mon Sep 17 00:00:00 2001 From: Piotr Osiewicz <24362066+osiewicz@users.noreply.github.com> Date: Tue, 18 Jun 2024 15:09:52 +0200 Subject: [PATCH] project panel: Improve performance in large projects (#13202) In #12980 I've hoisted out creation of HashSet out of render_entry, which made us not create that hash set for each entry in a worktree on each frame. In current nightly, we do it once per call to render() on the whole worktree, which is better. However, we can still reuse the hashed between the frames, if the worktree has not changed. Once we calculate the hashset for a given worktree state, we keep it around for as long as the state is valid for. We calculate the HashSet lazily, as we may not necessarily need it if the project panel is collapsed. In large worktrees, this helps keep the CPU usage of the main thread low-ish. Release Notes: - Improved performance of project panel in large worktrees. --- crates/project_panel/src/project_panel.rs | 39 +++++++++++++---------- 1 file changed, 22 insertions(+), 17 deletions(-) diff --git a/crates/project_panel/src/project_panel.rs b/crates/project_panel/src/project_panel.rs index 0ca5b6c595..c7e2ee260c 100644 --- a/crates/project_panel/src/project_panel.rs +++ b/crates/project_panel/src/project_panel.rs @@ -22,6 +22,7 @@ use project::{Entry, EntryKind, Fs, Project, ProjectEntryId, ProjectPath, Worktr use project_panel_settings::{ProjectPanelDockPosition, ProjectPanelSettings}; use serde::{Deserialize, Serialize}; use std::{ + cell::OnceCell, collections::HashSet, ffi::OsStr, ops::Range, @@ -46,7 +47,7 @@ pub struct ProjectPanel { fs: Arc, scroll_handle: UniformListScrollHandle, focus_handle: FocusHandle, - visible_entries: Vec<(WorktreeId, Vec)>, + visible_entries: Vec<(WorktreeId, Vec, OnceCell>>)>, last_worktree_root_id: Option, last_external_paths_drag_over_entry: Option, expanded_dir_ids: HashMap>, @@ -675,7 +676,7 @@ impl ProjectPanel { return; } - let (worktree_id, worktree_entries) = &self.visible_entries[worktree_ix]; + let (worktree_id, worktree_entries, _) = &self.visible_entries[worktree_ix]; let selection = SelectedEntry { worktree_id: *worktree_id, entry_id: worktree_entries[entry_ix].id, @@ -1120,7 +1121,7 @@ impl ProjectPanel { if let Some(selection) = self.selection { let (mut worktree_ix, mut entry_ix, _) = self.index_for_selection(selection).unwrap_or_default(); - if let Some((_, worktree_entries)) = self.visible_entries.get(worktree_ix) { + if let Some((_, worktree_entries, _)) = self.visible_entries.get(worktree_ix) { if entry_ix + 1 < worktree_entries.len() { entry_ix += 1; } else { @@ -1129,7 +1130,8 @@ impl ProjectPanel { } } - if let Some((worktree_id, worktree_entries)) = self.visible_entries.get(worktree_ix) { + if let Some((worktree_id, worktree_entries, _)) = self.visible_entries.get(worktree_ix) + { if let Some(entry) = worktree_entries.get(entry_ix) { let selection = SelectedEntry { worktree_id: *worktree_id, @@ -1170,7 +1172,9 @@ impl ProjectPanel { let worktree = self .visible_entries .first() - .and_then(|(worktree_id, _)| self.project.read(cx).worktree_for_id(*worktree_id, cx)); + .and_then(|(worktree_id, _, _)| { + self.project.read(cx).worktree_for_id(*worktree_id, cx) + }); if let Some(worktree) = worktree { let worktree = worktree.read(cx); let worktree_id = worktree.id(); @@ -1190,10 +1194,9 @@ impl ProjectPanel { } fn select_last(&mut self, _: &SelectLast, cx: &mut ViewContext) { - let worktree = self - .visible_entries - .last() - .and_then(|(worktree_id, _)| self.project.read(cx).worktree_for_id(*worktree_id, cx)); + let worktree = self.visible_entries.last().and_then(|(worktree_id, _, _)| { + self.project.read(cx).worktree_for_id(*worktree_id, cx) + }); if let Some(worktree) = worktree { let worktree = worktree.read(cx); let worktree_id = worktree.id(); @@ -1461,7 +1464,7 @@ impl ProjectPanel { fn index_for_selection(&self, selection: SelectedEntry) -> Option<(usize, usize, usize)> { let mut entry_index = 0; let mut visible_entries_index = 0; - for (worktree_index, (worktree_id, worktree_entries)) in + for (worktree_index, (worktree_id, worktree_entries, _)) in self.visible_entries.iter().enumerate() { if *worktree_id == selection.worktree_id { @@ -1623,7 +1626,7 @@ impl ProjectPanel { snapshot.propagate_git_statuses(&mut visible_worktree_entries); project::sort_worktree_entries(&mut visible_worktree_entries); self.visible_entries - .push((worktree_id, visible_worktree_entries)); + .push((worktree_id, visible_worktree_entries, OnceCell::new())); } if let Some((worktree_id, entry_id)) = new_selected_entry { @@ -1794,7 +1797,7 @@ impl ProjectPanel { mut callback: impl FnMut(ProjectEntryId, EntryDetails, &mut ViewContext), ) { let mut ix = 0; - for (worktree_id, visible_worktree_entries) in &self.visible_entries { + for (worktree_id, visible_worktree_entries, entries_paths) in &self.visible_entries { if ix >= range.end { return; } @@ -1823,10 +1826,12 @@ impl ProjectPanel { .unwrap_or(&[]); let entry_range = range.start.saturating_sub(ix)..end_ix - ix; - let entries = visible_worktree_entries - .iter() - .map(|e| (e.path.clone())) - .collect(); + let entries = entries_paths.get_or_init(|| { + visible_worktree_entries + .iter() + .map(|e| (e.path.clone())) + .collect() + }); for entry in visible_worktree_entries[entry_range].iter() { let status = git_status_setting.then(|| entry.git_status).flatten(); let is_expanded = expanded_entry_ids.binary_search(&entry.id).is_ok(); @@ -2298,7 +2303,7 @@ impl Render for ProjectPanel { "entries", self.visible_entries .iter() - .map(|(_, worktree_entries)| worktree_entries.len()) + .map(|(_, worktree_entries, _)| worktree_entries.len()) .sum(), { |this, range, cx| {