Fix performance bottlenecks when multi buffers have huge numbers of buffers (#26308)
This is motivated by trying to make the Project Diff view usable with huge Git change sets. Release Notes: - Improved performance of rendering multibuffers with very large numbers of buffers
This commit is contained in:
parent
cb543f9546
commit
4846e6fb3a
3 changed files with 62 additions and 21 deletions
|
@ -49,7 +49,7 @@ use std::{
|
||||||
num::NonZeroU32,
|
num::NonZeroU32,
|
||||||
ops::{Deref, DerefMut, Range},
|
ops::{Deref, DerefMut, Range},
|
||||||
path::{Path, PathBuf},
|
path::{Path, PathBuf},
|
||||||
str,
|
rc, str,
|
||||||
sync::{Arc, LazyLock},
|
sync::{Arc, LazyLock},
|
||||||
time::{Duration, Instant},
|
time::{Duration, Instant},
|
||||||
vec,
|
vec,
|
||||||
|
@ -125,6 +125,7 @@ pub struct Buffer {
|
||||||
/// Memoize calls to has_changes_since(saved_version).
|
/// Memoize calls to has_changes_since(saved_version).
|
||||||
/// The contents of a cell are (self.version, has_changes) at the time of a last call.
|
/// The contents of a cell are (self.version, has_changes) at the time of a last call.
|
||||||
has_unsaved_edits: Cell<(clock::Global, bool)>,
|
has_unsaved_edits: Cell<(clock::Global, bool)>,
|
||||||
|
change_bits: Vec<rc::Weak<Cell<bool>>>,
|
||||||
_subscriptions: Vec<gpui::Subscription>,
|
_subscriptions: Vec<gpui::Subscription>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -978,6 +979,7 @@ impl Buffer {
|
||||||
completion_triggers_timestamp: Default::default(),
|
completion_triggers_timestamp: Default::default(),
|
||||||
deferred_ops: OperationQueue::new(),
|
deferred_ops: OperationQueue::new(),
|
||||||
has_conflict: false,
|
has_conflict: false,
|
||||||
|
change_bits: Default::default(),
|
||||||
_subscriptions: Vec::new(),
|
_subscriptions: Vec::new(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1252,6 +1254,7 @@ impl Buffer {
|
||||||
self.non_text_state_update_count += 1;
|
self.non_text_state_update_count += 1;
|
||||||
self.syntax_map.lock().clear(&self.text);
|
self.syntax_map.lock().clear(&self.text);
|
||||||
self.language = language;
|
self.language = language;
|
||||||
|
self.was_changed();
|
||||||
self.reparse(cx);
|
self.reparse(cx);
|
||||||
cx.emit(BufferEvent::LanguageChanged);
|
cx.emit(BufferEvent::LanguageChanged);
|
||||||
}
|
}
|
||||||
|
@ -1286,6 +1289,7 @@ impl Buffer {
|
||||||
.set((self.saved_version().clone(), false));
|
.set((self.saved_version().clone(), false));
|
||||||
self.has_conflict = false;
|
self.has_conflict = false;
|
||||||
self.saved_mtime = mtime;
|
self.saved_mtime = mtime;
|
||||||
|
self.was_changed();
|
||||||
cx.emit(BufferEvent::Saved);
|
cx.emit(BufferEvent::Saved);
|
||||||
cx.notify();
|
cx.notify();
|
||||||
}
|
}
|
||||||
|
@ -1381,6 +1385,7 @@ impl Buffer {
|
||||||
|
|
||||||
self.file = Some(new_file);
|
self.file = Some(new_file);
|
||||||
if file_changed {
|
if file_changed {
|
||||||
|
self.was_changed();
|
||||||
self.non_text_state_update_count += 1;
|
self.non_text_state_update_count += 1;
|
||||||
if was_dirty != self.is_dirty() {
|
if was_dirty != self.is_dirty() {
|
||||||
cx.emit(BufferEvent::DirtyChanged);
|
cx.emit(BufferEvent::DirtyChanged);
|
||||||
|
@ -1958,6 +1963,23 @@ impl Buffer {
|
||||||
self.text.subscribe()
|
self.text.subscribe()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Adds a bit to the list of bits that are set when the buffer's text changes.
|
||||||
|
///
|
||||||
|
/// This allows downstream code to check if the buffer's text has changed without
|
||||||
|
/// waiting for an effect cycle, which would be required if using eents.
|
||||||
|
pub fn record_changes(&mut self, bit: rc::Weak<Cell<bool>>) {
|
||||||
|
self.change_bits.push(bit);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn was_changed(&mut self) {
|
||||||
|
self.change_bits.retain(|change_bit| {
|
||||||
|
change_bit.upgrade().map_or(false, |bit| {
|
||||||
|
bit.replace(true);
|
||||||
|
true
|
||||||
|
})
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
/// Starts a transaction, if one is not already in-progress. When undoing or
|
/// Starts a transaction, if one is not already in-progress. When undoing or
|
||||||
/// redoing edits, all of the edits performed within a transaction are undone
|
/// redoing edits, all of the edits performed within a transaction are undone
|
||||||
/// or redone together.
|
/// or redone together.
|
||||||
|
@ -2368,6 +2390,7 @@ impl Buffer {
|
||||||
}
|
}
|
||||||
self.text.apply_ops(buffer_ops);
|
self.text.apply_ops(buffer_ops);
|
||||||
self.deferred_ops.insert(deferred_ops);
|
self.deferred_ops.insert(deferred_ops);
|
||||||
|
self.was_changed();
|
||||||
self.flush_deferred_ops(cx);
|
self.flush_deferred_ops(cx);
|
||||||
self.did_edit(&old_version, was_dirty, cx);
|
self.did_edit(&old_version, was_dirty, cx);
|
||||||
// Notify independently of whether the buffer was edited as the operations could include a
|
// Notify independently of whether the buffer was edited as the operations could include a
|
||||||
|
@ -2502,7 +2525,8 @@ impl Buffer {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn send_operation(&self, operation: Operation, is_local: bool, cx: &mut Context<Self>) {
|
fn send_operation(&mut self, operation: Operation, is_local: bool, cx: &mut Context<Self>) {
|
||||||
|
self.was_changed();
|
||||||
cx.emit(BufferEvent::Operation {
|
cx.emit(BufferEvent::Operation {
|
||||||
operation,
|
operation,
|
||||||
is_local,
|
is_local,
|
||||||
|
|
|
@ -31,7 +31,7 @@ use smol::future::yield_now;
|
||||||
use std::{
|
use std::{
|
||||||
any::type_name,
|
any::type_name,
|
||||||
borrow::Cow,
|
borrow::Cow,
|
||||||
cell::{Ref, RefCell},
|
cell::{Cell, Ref, RefCell},
|
||||||
cmp, fmt,
|
cmp, fmt,
|
||||||
future::Future,
|
future::Future,
|
||||||
io,
|
io,
|
||||||
|
@ -39,6 +39,7 @@ use std::{
|
||||||
mem,
|
mem,
|
||||||
ops::{Range, RangeBounds, Sub},
|
ops::{Range, RangeBounds, Sub},
|
||||||
path::Path,
|
path::Path,
|
||||||
|
rc::Rc,
|
||||||
str,
|
str,
|
||||||
sync::Arc,
|
sync::Arc,
|
||||||
time::{Duration, Instant},
|
time::{Duration, Instant},
|
||||||
|
@ -76,6 +77,7 @@ pub struct MultiBuffer {
|
||||||
history: History,
|
history: History,
|
||||||
title: Option<String>,
|
title: Option<String>,
|
||||||
capability: Capability,
|
capability: Capability,
|
||||||
|
buffer_changed_since_sync: Rc<Cell<bool>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||||
|
@ -568,6 +570,7 @@ impl MultiBuffer {
|
||||||
capability,
|
capability,
|
||||||
title: None,
|
title: None,
|
||||||
buffers_by_path: Default::default(),
|
buffers_by_path: Default::default(),
|
||||||
|
buffer_changed_since_sync: Default::default(),
|
||||||
history: History {
|
history: History {
|
||||||
next_transaction_id: clock::Lamport::default(),
|
next_transaction_id: clock::Lamport::default(),
|
||||||
undo_stack: Vec::new(),
|
undo_stack: Vec::new(),
|
||||||
|
@ -587,6 +590,7 @@ impl MultiBuffer {
|
||||||
subscriptions: Default::default(),
|
subscriptions: Default::default(),
|
||||||
singleton: false,
|
singleton: false,
|
||||||
capability,
|
capability,
|
||||||
|
buffer_changed_since_sync: Default::default(),
|
||||||
history: History {
|
history: History {
|
||||||
next_transaction_id: Default::default(),
|
next_transaction_id: Default::default(),
|
||||||
undo_stack: Default::default(),
|
undo_stack: Default::default(),
|
||||||
|
@ -600,7 +604,11 @@ impl MultiBuffer {
|
||||||
|
|
||||||
pub fn clone(&self, new_cx: &mut Context<Self>) -> Self {
|
pub fn clone(&self, new_cx: &mut Context<Self>) -> Self {
|
||||||
let mut buffers = HashMap::default();
|
let mut buffers = HashMap::default();
|
||||||
|
let buffer_changed_since_sync = Rc::new(Cell::new(false));
|
||||||
for (buffer_id, buffer_state) in self.buffers.borrow().iter() {
|
for (buffer_id, buffer_state) in self.buffers.borrow().iter() {
|
||||||
|
buffer_state.buffer.update(new_cx, |buffer, _| {
|
||||||
|
buffer.record_changes(Rc::downgrade(&buffer_changed_since_sync));
|
||||||
|
});
|
||||||
buffers.insert(
|
buffers.insert(
|
||||||
*buffer_id,
|
*buffer_id,
|
||||||
BufferState {
|
BufferState {
|
||||||
|
@ -629,6 +637,7 @@ impl MultiBuffer {
|
||||||
capability: self.capability,
|
capability: self.capability,
|
||||||
history: self.history.clone(),
|
history: self.history.clone(),
|
||||||
title: self.title.clone(),
|
title: self.title.clone(),
|
||||||
|
buffer_changed_since_sync,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1728,19 +1737,25 @@ impl MultiBuffer {
|
||||||
|
|
||||||
self.sync(cx);
|
self.sync(cx);
|
||||||
|
|
||||||
let buffer_id = buffer.read(cx).remote_id();
|
|
||||||
let buffer_snapshot = buffer.read(cx).snapshot();
|
let buffer_snapshot = buffer.read(cx).snapshot();
|
||||||
|
let buffer_id = buffer_snapshot.remote_id();
|
||||||
|
|
||||||
let mut buffers = self.buffers.borrow_mut();
|
let mut buffers = self.buffers.borrow_mut();
|
||||||
let buffer_state = buffers.entry(buffer_id).or_insert_with(|| BufferState {
|
let buffer_state = buffers.entry(buffer_id).or_insert_with(|| {
|
||||||
last_version: buffer_snapshot.version().clone(),
|
self.buffer_changed_since_sync.replace(true);
|
||||||
last_non_text_state_update_count: buffer_snapshot.non_text_state_update_count(),
|
buffer.update(cx, |buffer, _| {
|
||||||
excerpts: Default::default(),
|
buffer.record_changes(Rc::downgrade(&self.buffer_changed_since_sync));
|
||||||
_subscriptions: [
|
});
|
||||||
cx.observe(&buffer, |_, _, cx| cx.notify()),
|
BufferState {
|
||||||
cx.subscribe(&buffer, Self::on_buffer_event),
|
last_version: buffer_snapshot.version().clone(),
|
||||||
],
|
last_non_text_state_update_count: buffer_snapshot.non_text_state_update_count(),
|
||||||
buffer: buffer.clone(),
|
excerpts: Default::default(),
|
||||||
|
_subscriptions: [
|
||||||
|
cx.observe(&buffer, |_, _, cx| cx.notify()),
|
||||||
|
cx.subscribe(&buffer, Self::on_buffer_event),
|
||||||
|
],
|
||||||
|
buffer: buffer.clone(),
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
let mut snapshot = self.snapshot.borrow_mut();
|
let mut snapshot = self.snapshot.borrow_mut();
|
||||||
|
@ -2236,6 +2251,7 @@ impl MultiBuffer {
|
||||||
cx: &mut Context<Self>,
|
cx: &mut Context<Self>,
|
||||||
) {
|
) {
|
||||||
self.sync(cx);
|
self.sync(cx);
|
||||||
|
self.buffer_changed_since_sync.replace(true);
|
||||||
|
|
||||||
let diff = diff.read(cx);
|
let diff = diff.read(cx);
|
||||||
let buffer_id = diff.buffer_id;
|
let buffer_id = diff.buffer_id;
|
||||||
|
@ -2714,6 +2730,11 @@ impl MultiBuffer {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn sync(&self, cx: &App) {
|
fn sync(&self, cx: &App) {
|
||||||
|
let changed = self.buffer_changed_since_sync.replace(false);
|
||||||
|
if !changed {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
let mut snapshot = self.snapshot.borrow_mut();
|
let mut snapshot = self.snapshot.borrow_mut();
|
||||||
let mut excerpts_to_edit = Vec::new();
|
let mut excerpts_to_edit = Vec::new();
|
||||||
let mut non_text_state_updated = false;
|
let mut non_text_state_updated = false;
|
||||||
|
|
|
@ -2424,14 +2424,10 @@ impl Pane {
|
||||||
.child(label),
|
.child(label),
|
||||||
);
|
);
|
||||||
|
|
||||||
let single_entry_to_resolve = {
|
let single_entry_to_resolve = self.items[ix]
|
||||||
let item_entries = self.items[ix].project_entry_ids(cx);
|
.is_singleton(cx)
|
||||||
if item_entries.len() == 1 {
|
.then(|| self.items[ix].project_entry_ids(cx).get(0).copied())
|
||||||
Some(item_entries[0])
|
.flatten();
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
let total_items = self.items.len();
|
let total_items = self.items.len();
|
||||||
let has_items_to_left = ix > 0;
|
let has_items_to_left = ix > 0;
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue