project/perf: Optimize BufferStore::get_by_path with an additional index (#28670)

Closes #27270

Release Notes:

- Improved performance of git panel with large # of untracked files
This commit is contained in:
Piotr Osiewicz 2025-04-14 17:06:41 +02:00 committed by GitHub
parent fddaa31655
commit 9863b48dd7
No known key found for this signature in database
GPG key ID: B5690EEEBB952194

View file

@ -36,6 +36,7 @@ pub struct BufferStore {
loading_buffers: HashMap<ProjectPath, Shared<Task<Result<Entity<Buffer>, Arc<anyhow::Error>>>>>,
worktree_store: Entity<WorktreeStore>,
opened_buffers: HashMap<BufferId, OpenBuffer>,
path_to_buffer_id: HashMap<ProjectPath, BufferId>,
downstream_client: Option<(AnyProtoClient, u64)>,
shared_buffers: HashMap<proto::PeerId, HashMap<BufferId, SharedBuffer>>,
}
@ -62,7 +63,6 @@ struct RemoteBufferStore {
}
struct LocalBufferStore {
local_buffer_ids_by_path: HashMap<ProjectPath, BufferId>,
local_buffer_ids_by_entry_id: HashMap<ProjectEntryId, BufferId>,
worktree_store: Entity<WorktreeStore>,
_subscription: Subscription,
@ -368,8 +368,9 @@ impl LocalBufferStore {
let line_ending = buffer.line_ending();
let version = buffer.version();
let buffer_id = buffer.remote_id();
if buffer
.file()
let file = buffer.file().cloned();
if file
.as_ref()
.is_some_and(|file| file.disk_state() == DiskState::New)
{
has_changed_file = true;
@ -462,13 +463,11 @@ impl LocalBufferStore {
path: path.clone(),
};
let buffer_id = {
let local = this.as_local_mut()?;
match local.local_buffer_ids_by_entry_id.get(&entry_id) {
Some(&buffer_id) => buffer_id,
None => local.local_buffer_ids_by_path.get(&project_path).copied()?,
}
};
let buffer_id = this
.as_local_mut()
.and_then(|local| local.local_buffer_ids_by_entry_id.get(&entry_id))
.copied()
.or_else(|| this.path_to_buffer_id.get(&project_path).copied())?;
let buffer = if let Some(buffer) = this.get(buffer_id) {
Some(buffer)
@ -480,14 +479,13 @@ impl LocalBufferStore {
let buffer = if let Some(buffer) = buffer {
buffer
} else {
this.path_to_buffer_id.remove(&project_path);
let this = this.as_local_mut()?;
this.local_buffer_ids_by_path.remove(&project_path);
this.local_buffer_ids_by_entry_id.remove(&entry_id);
return None;
};
let events = buffer.update(cx, |buffer, cx| {
let local = this.as_local_mut()?;
let file = buffer.file()?;
let old_file = File::from_dyn(Some(file))?;
if old_file.worktree != *worktree {
@ -528,11 +526,11 @@ impl LocalBufferStore {
let mut events = Vec::new();
if new_file.path != old_file.path {
local.local_buffer_ids_by_path.remove(&ProjectPath {
this.path_to_buffer_id.remove(&ProjectPath {
path: old_file.path.clone(),
worktree_id: old_file.worktree_id(cx),
});
local.local_buffer_ids_by_path.insert(
this.path_to_buffer_id.insert(
ProjectPath {
worktree_id: new_file.worktree_id(cx),
path: new_file.path.clone(),
@ -544,7 +542,7 @@ impl LocalBufferStore {
old_file: buffer.file().cloned(),
});
}
let local = this.as_local_mut()?;
if new_file.entry_id != old_file.entry_id {
if let Some(entry_id) = old_file.entry_id {
local.local_buffer_ids_by_entry_id.remove(&entry_id);
@ -577,32 +575,6 @@ impl LocalBufferStore {
None
}
fn buffer_changed_file(&mut self, buffer: Entity<Buffer>, cx: &mut App) -> Option<()> {
let file = File::from_dyn(buffer.read(cx).file())?;
let remote_id = buffer.read(cx).remote_id();
if let Some(entry_id) = file.entry_id {
match self.local_buffer_ids_by_entry_id.get(&entry_id) {
Some(_) => {
return None;
}
None => {
self.local_buffer_ids_by_entry_id
.insert(entry_id, remote_id);
}
}
};
self.local_buffer_ids_by_path.insert(
ProjectPath {
worktree_id: file.worktree_id(cx),
path: file.path.clone(),
},
remote_id,
);
Some(())
}
fn save_buffer(
&self,
buffer: Entity<Buffer>,
@ -677,15 +649,14 @@ impl LocalBufferStore {
this.add_buffer(buffer.clone(), cx)?;
let buffer_id = buffer.read(cx).remote_id();
if let Some(file) = File::from_dyn(buffer.read(cx).file()) {
let this = this.as_local_mut().unwrap();
this.local_buffer_ids_by_path.insert(
this.path_to_buffer_id.insert(
ProjectPath {
worktree_id: file.worktree_id(cx),
path: file.path.clone(),
},
buffer_id,
);
let this = this.as_local_mut().unwrap();
if let Some(entry_id) = file.entry_id {
this.local_buffer_ids_by_entry_id
.insert(entry_id, buffer_id);
@ -748,7 +719,6 @@ impl BufferStore {
pub fn local(worktree_store: Entity<WorktreeStore>, cx: &mut Context<Self>) -> Self {
Self {
state: BufferStoreState::Local(LocalBufferStore {
local_buffer_ids_by_path: Default::default(),
local_buffer_ids_by_entry_id: Default::default(),
worktree_store: worktree_store.clone(),
_subscription: cx.subscribe(&worktree_store, |this, _, event, cx| {
@ -760,6 +730,7 @@ impl BufferStore {
}),
downstream_client: None,
opened_buffers: Default::default(),
path_to_buffer_id: Default::default(),
shared_buffers: Default::default(),
loading_buffers: Default::default(),
worktree_store,
@ -783,19 +754,13 @@ impl BufferStore {
}),
downstream_client: None,
opened_buffers: Default::default(),
path_to_buffer_id: Default::default(),
loading_buffers: Default::default(),
shared_buffers: Default::default(),
worktree_store,
}
}
fn as_local(&self) -> Option<&LocalBufferStore> {
match &self.state {
BufferStoreState::Local(state) => Some(state),
_ => None,
}
}
fn as_local_mut(&mut self) -> Option<&mut LocalBufferStore> {
match &mut self.state {
BufferStoreState::Local(state) => Some(state),
@ -915,6 +880,10 @@ impl BufferStore {
fn add_buffer(&mut self, buffer_entity: Entity<Buffer>, cx: &mut Context<Self>) -> Result<()> {
let buffer = buffer_entity.read(cx);
let remote_id = buffer.remote_id();
let path = File::from_dyn(buffer.file()).map(|file| ProjectPath {
path: file.path.clone(),
worktree_id: file.worktree_id(cx),
});
let is_remote = buffer.replica_id() != 0;
let open_buffer = OpenBuffer::Complete {
buffer: buffer_entity.downgrade(),
@ -931,10 +900,11 @@ impl BufferStore {
})
.detach()
});
let _expect_path_to_exist;
match self.opened_buffers.entry(remote_id) {
hash_map::Entry::Vacant(entry) => {
entry.insert(open_buffer);
_expect_path_to_exist = false;
}
hash_map::Entry::Occupied(mut entry) => {
if let OpenBuffer::Operations(operations) = entry.get_mut() {
@ -948,9 +918,14 @@ impl BufferStore {
}
}
entry.insert(open_buffer);
_expect_path_to_exist = true;
}
}
if let Some(path) = path {
self.path_to_buffer_id.insert(path, remote_id);
}
cx.subscribe(&buffer_entity, Self::on_buffer_event).detach();
cx.emit(BufferStoreEvent::BufferAdded(buffer_entity));
Ok(())
@ -972,18 +947,13 @@ impl BufferStore {
}
pub fn buffer_id_for_project_path(&self, project_path: &ProjectPath) -> Option<&BufferId> {
self.as_local()
.and_then(|state| state.local_buffer_ids_by_path.get(project_path))
self.path_to_buffer_id.get(project_path)
}
pub fn get_by_path(&self, path: &ProjectPath, cx: &App) -> Option<Entity<Buffer>> {
self.buffers().find_map(|buffer| {
let file = File::from_dyn(buffer.read(cx).file())?;
if file.worktree_id(cx) == path.worktree_id && file.path == path.path {
Some(buffer)
} else {
None
}
pub fn get_by_path(&self, path: &ProjectPath, _cx: &App) -> Option<Entity<Buffer>> {
self.path_to_buffer_id.get(path).and_then(|buffer_id| {
let buffer = self.get(*buffer_id);
buffer
})
}
@ -1055,6 +1025,35 @@ impl BufferStore {
.retain(|_, buffer| !matches!(buffer, OpenBuffer::Operations(_)));
}
fn buffer_changed_file(&mut self, buffer: Entity<Buffer>, cx: &mut App) -> Option<()> {
let file = File::from_dyn(buffer.read(cx).file())?;
let remote_id = buffer.read(cx).remote_id();
if let Some(entry_id) = file.entry_id {
if let Some(local) = self.as_local_mut() {
match local.local_buffer_ids_by_entry_id.get(&entry_id) {
Some(_) => {
return None;
}
None => {
local
.local_buffer_ids_by_entry_id
.insert(entry_id, remote_id);
}
}
}
self.path_to_buffer_id.insert(
ProjectPath {
worktree_id: file.worktree_id(cx),
path: file.path.clone(),
},
remote_id,
);
};
Some(())
}
pub fn find_search_candidates(
&mut self,
query: &SearchQuery,
@ -1118,9 +1117,7 @@ impl BufferStore {
) {
match event {
BufferEvent::FileHandleChanged => {
if let Some(local) = self.as_local_mut() {
local.buffer_changed_file(buffer, cx);
}
self.buffer_changed_file(buffer, cx);
}
BufferEvent::Reloaded => {
let Some((downstream_client, project_id)) = self.downstream_client.as_ref() else {
@ -1316,6 +1313,7 @@ impl BufferStore {
let old_file = buffer.update(cx, |buffer, cx| {
let old_file = buffer.file().cloned();
let new_path = file.path.clone();
buffer.file_updated(Arc::new(file), cx);
if old_file
.as_ref()
@ -1606,18 +1604,17 @@ impl BufferStore {
self.add_buffer(buffer.clone(), cx).log_err();
let buffer_id = buffer.read(cx).remote_id();
let this = self
.as_local_mut()
.expect("local-only method called in a non-local context");
if let Some(file) = File::from_dyn(buffer.read(cx).file()) {
this.local_buffer_ids_by_path.insert(
self.path_to_buffer_id.insert(
ProjectPath {
worktree_id: file.worktree_id(cx),
path: file.path.clone(),
},
buffer_id,
);
let this = self
.as_local_mut()
.expect("local-only method called in a non-local context");
if let Some(entry_id) = file.entry_id {
this.local_buffer_ids_by_entry_id
.insert(entry_id, buffer_id);