Track dirtyness per item (#26237)
This reduces the number of multibuffer syncs when starting the editor with 80 files open in the Zed repo from 10,000,000 to 100,000 by avoiding O(n**2) dirtyness checks. Release Notes: - Fixed a beachball when restarting in a large repo with a large number open files
This commit is contained in:
parent
263d9ff755
commit
f373383fc1
3 changed files with 43 additions and 7 deletions
|
@ -802,6 +802,7 @@ impl<T: Item> ItemHandle for Entity<T> {
|
||||||
}
|
}
|
||||||
|
|
||||||
ItemEvent::UpdateTab => {
|
ItemEvent::UpdateTab => {
|
||||||
|
workspace.update_item_dirty_state(item, window, cx);
|
||||||
pane.update(cx, |_, cx| {
|
pane.update(cx, |_, cx| {
|
||||||
cx.emit(pane::Event::ChangeItemTitle);
|
cx.emit(pane::Event::ChangeItemTitle);
|
||||||
cx.notify();
|
cx.notify();
|
||||||
|
|
|
@ -826,6 +826,7 @@ pub struct Workspace {
|
||||||
follower_states: HashMap<PeerId, FollowerState>,
|
follower_states: HashMap<PeerId, FollowerState>,
|
||||||
last_leaders_by_pane: HashMap<WeakEntity<Pane>, PeerId>,
|
last_leaders_by_pane: HashMap<WeakEntity<Pane>, PeerId>,
|
||||||
window_edited: bool,
|
window_edited: bool,
|
||||||
|
dirty_items: HashMap<EntityId, Subscription>,
|
||||||
active_call: Option<(Entity<ActiveCall>, Vec<Subscription>)>,
|
active_call: Option<(Entity<ActiveCall>, Vec<Subscription>)>,
|
||||||
leader_updates_tx: mpsc::UnboundedSender<(PeerId, proto::UpdateFollowers)>,
|
leader_updates_tx: mpsc::UnboundedSender<(PeerId, proto::UpdateFollowers)>,
|
||||||
database_id: Option<WorkspaceId>,
|
database_id: Option<WorkspaceId>,
|
||||||
|
@ -1128,6 +1129,7 @@ impl Workspace {
|
||||||
last_leaders_by_pane: Default::default(),
|
last_leaders_by_pane: Default::default(),
|
||||||
dispatching_keystrokes: Default::default(),
|
dispatching_keystrokes: Default::default(),
|
||||||
window_edited: false,
|
window_edited: false,
|
||||||
|
dirty_items: Default::default(),
|
||||||
active_call,
|
active_call,
|
||||||
database_id: workspace_id,
|
database_id: workspace_id,
|
||||||
app_state,
|
app_state,
|
||||||
|
@ -3395,13 +3397,11 @@ impl Workspace {
|
||||||
if *pane == self.active_pane {
|
if *pane == self.active_pane {
|
||||||
self.active_item_path_changed(window, cx);
|
self.active_item_path_changed(window, cx);
|
||||||
}
|
}
|
||||||
self.update_window_edited(window, cx);
|
|
||||||
serialize_workspace = false;
|
serialize_workspace = false;
|
||||||
}
|
}
|
||||||
pane::Event::RemoveItem { .. } => {}
|
pane::Event::RemoveItem { .. } => {}
|
||||||
pane::Event::RemovedItem { item_id } => {
|
pane::Event::RemovedItem { item_id } => {
|
||||||
cx.emit(Event::ActiveItemChanged);
|
cx.emit(Event::ActiveItemChanged);
|
||||||
self.update_window_edited(window, cx);
|
|
||||||
if let hash_map::Entry::Occupied(entry) = self.panes_by_item.entry(*item_id) {
|
if let hash_map::Entry::Occupied(entry) = self.panes_by_item.entry(*item_id) {
|
||||||
if entry.get().entity_id() == pane.entity_id() {
|
if entry.get().entity_id() == pane.entity_id() {
|
||||||
entry.remove();
|
entry.remove();
|
||||||
|
@ -3858,16 +3858,47 @@ impl Workspace {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn update_window_edited(&mut self, window: &mut Window, cx: &mut App) {
|
fn update_window_edited(&mut self, window: &mut Window, cx: &mut App) {
|
||||||
let is_edited = !self.project.read(cx).is_disconnected(cx)
|
let is_edited = !self.project.read(cx).is_disconnected(cx) && !self.dirty_items.is_empty();
|
||||||
&& self
|
|
||||||
.items(cx)
|
|
||||||
.any(|item| item.has_conflict(cx) || item.is_dirty(cx));
|
|
||||||
if is_edited != self.window_edited {
|
if is_edited != self.window_edited {
|
||||||
self.window_edited = is_edited;
|
self.window_edited = is_edited;
|
||||||
window.set_window_edited(self.window_edited)
|
window.set_window_edited(self.window_edited)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn update_item_dirty_state(
|
||||||
|
&mut self,
|
||||||
|
item: &dyn ItemHandle,
|
||||||
|
window: &mut Window,
|
||||||
|
cx: &mut App,
|
||||||
|
) {
|
||||||
|
let is_dirty = item.is_dirty(cx);
|
||||||
|
let item_id = item.item_id();
|
||||||
|
let was_dirty = self.dirty_items.contains_key(&item_id);
|
||||||
|
if is_dirty == was_dirty {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if was_dirty {
|
||||||
|
self.dirty_items.remove(&item_id);
|
||||||
|
self.update_window_edited(window, cx);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if let Some(window_handle) = window.window_handle().downcast::<Self>() {
|
||||||
|
let s = item.on_release(
|
||||||
|
cx,
|
||||||
|
Box::new(move |cx| {
|
||||||
|
window_handle
|
||||||
|
.update(cx, |this, window, cx| {
|
||||||
|
this.dirty_items.remove(&item_id);
|
||||||
|
this.update_window_edited(window, cx)
|
||||||
|
})
|
||||||
|
.ok();
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
self.dirty_items.insert(item_id, s);
|
||||||
|
self.update_window_edited(window, cx);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn render_notifications(&self, _window: &mut Window, _cx: &mut Context<Self>) -> Option<Div> {
|
fn render_notifications(&self, _window: &mut Window, _cx: &mut Context<Self>) -> Option<Div> {
|
||||||
if self.notifications.is_empty() {
|
if self.notifications.is_empty() {
|
||||||
None
|
None
|
||||||
|
|
|
@ -2112,6 +2112,7 @@ mod tests {
|
||||||
})
|
})
|
||||||
.unwrap();
|
.unwrap();
|
||||||
assert!(window_is_edited(window, cx));
|
assert!(window_is_edited(window, cx));
|
||||||
|
let weak = editor.downgrade();
|
||||||
|
|
||||||
// Closing the item restores the window's edited state.
|
// Closing the item restores the window's edited state.
|
||||||
let close = window
|
let close = window
|
||||||
|
@ -2127,11 +2128,12 @@ mod tests {
|
||||||
|
|
||||||
cx.simulate_prompt_answer("Don't Save");
|
cx.simulate_prompt_answer("Don't Save");
|
||||||
close.await.unwrap();
|
close.await.unwrap();
|
||||||
assert!(!window_is_edited(window, cx));
|
|
||||||
|
|
||||||
// Advance the clock to ensure that the item has been serialized and dropped from the queue
|
// Advance the clock to ensure that the item has been serialized and dropped from the queue
|
||||||
cx.executor().advance_clock(Duration::from_secs(1));
|
cx.executor().advance_clock(Duration::from_secs(1));
|
||||||
|
|
||||||
|
weak.assert_released();
|
||||||
|
assert!(!window_is_edited(window, cx));
|
||||||
// Opening the buffer again doesn't impact the window's edited state.
|
// Opening the buffer again doesn't impact the window's edited state.
|
||||||
cx.update(|cx| {
|
cx.update(|cx| {
|
||||||
open_paths(
|
open_paths(
|
||||||
|
@ -2266,6 +2268,8 @@ mod tests {
|
||||||
assert_eq!(cx.update(|cx| cx.windows().len()), 1);
|
assert_eq!(cx.update(|cx| cx.windows().len()), 1);
|
||||||
assert!(cx.update(|cx| cx.active_window().is_some()));
|
assert!(cx.update(|cx| cx.active_window().is_some()));
|
||||||
|
|
||||||
|
cx.run_until_parked();
|
||||||
|
|
||||||
// When opening the workspace, the window is not in a edited state.
|
// When opening the workspace, the window is not in a edited state.
|
||||||
let window = cx.update(|cx| cx.active_window().unwrap().downcast::<Workspace>().unwrap());
|
let window = cx.update(|cx| cx.active_window().unwrap().downcast::<Workspace>().unwrap());
|
||||||
assert!(window_is_edited(window, cx));
|
assert!(window_is_edited(window, cx));
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue