fix(performance): keep track of scrollback buffer size (#881)

Fix #877. Avoid calculating scrollback buffer size on each render, since the calculation becomes slower proportionally to the amount of lines buffered, and to the width of each line.
* wip: keep track of scrollback buffer size

* account for lines dropped by bounded_push

* fix: use saturating sub

* fix: account for 0 width lines

* remove log messages
This commit is contained in:
Thomas Linford 2021-11-22 18:34:45 +01:00 committed by GitHub
parent 639566df15
commit 707fedd1df
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23

View file

@ -106,7 +106,7 @@ fn transfer_rows_from_lines_above_to_viewport(
viewport: &mut Vec<Row>, viewport: &mut Vec<Row>,
count: usize, count: usize,
max_viewport_width: usize, max_viewport_width: usize,
) { ) -> usize {
let mut next_lines: Vec<Row> = vec![]; let mut next_lines: Vec<Row> = vec![];
let mut lines_added_to_viewport: isize = 0; let mut lines_added_to_viewport: isize = 0;
loop { loop {
@ -137,6 +137,10 @@ fn transfer_rows_from_lines_above_to_viewport(
let excess_row = Row::from_rows(next_lines, 0); let excess_row = Row::from_rows(next_lines, 0);
bounded_push(lines_above, excess_row); bounded_push(lines_above, excess_row);
} }
match usize::try_from(lines_added_to_viewport) {
Ok(n) => n,
_ => 0,
}
} }
fn transfer_rows_from_viewport_to_lines_above( fn transfer_rows_from_viewport_to_lines_above(
@ -144,12 +148,15 @@ fn transfer_rows_from_viewport_to_lines_above(
lines_above: &mut VecDeque<Row>, lines_above: &mut VecDeque<Row>,
count: usize, count: usize,
max_viewport_width: usize, max_viewport_width: usize,
) { ) -> isize {
let mut next_lines: Vec<Row> = vec![]; let mut next_lines: Vec<Row> = vec![];
let mut transferred_rows_count: isize = 0;
for _ in 0..count { for _ in 0..count {
if next_lines.is_empty() { if next_lines.is_empty() {
if !viewport.is_empty() { if !viewport.is_empty() {
let next_line = viewport.remove(0); let next_line = viewport.remove(0);
transferred_rows_count +=
calculate_row_display_height(next_line.width(), max_viewport_width) as isize;
if !next_line.is_canonical { if !next_line.is_canonical {
let mut bottom_canonical_row_and_wraps_in_dst = let mut bottom_canonical_row_and_wraps_in_dst =
get_lines_above_bottom_canonical_row_and_wraps(lines_above); get_lines_above_bottom_canonical_row_and_wraps(lines_above);
@ -161,7 +168,11 @@ fn transfer_rows_from_viewport_to_lines_above(
break; // no more rows break; // no more rows
} }
} }
bounded_push(lines_above, next_lines.remove(0)); let dropped_line_width = bounded_push(lines_above, next_lines.remove(0));
if let Some(width) = dropped_line_width {
transferred_rows_count -=
calculate_row_display_height(width, max_viewport_width) as isize;
}
} }
if !next_lines.is_empty() { if !next_lines.is_empty() {
let excess_rows = Row::from_rows(next_lines, max_viewport_width) let excess_rows = Row::from_rows(next_lines, max_viewport_width)
@ -170,6 +181,7 @@ fn transfer_rows_from_viewport_to_lines_above(
viewport.insert(0, row); viewport.insert(0, row);
} }
} }
transferred_rows_count
} }
fn transfer_rows_from_lines_below_to_viewport( fn transfer_rows_from_lines_below_to_viewport(
@ -212,11 +224,16 @@ fn transfer_rows_from_lines_below_to_viewport(
} }
} }
fn bounded_push(vec: &mut VecDeque<Row>, value: Row) { fn bounded_push(vec: &mut VecDeque<Row>, value: Row) -> Option<usize> {
let mut dropped_line_width = None;
if vec.len() >= SCROLL_BACK { if vec.len() >= SCROLL_BACK {
vec.pop_front(); let line = vec.pop_front();
if let Some(line) = line {
dropped_line_width = Some(line.width());
}
} }
vec.push_back(value) vec.push_back(value);
dropped_line_width
} }
pub fn create_horizontal_tabstops(columns: usize) -> BTreeSet<usize> { pub fn create_horizontal_tabstops(columns: usize) -> BTreeSet<usize> {
@ -232,6 +249,21 @@ pub fn create_horizontal_tabstops(columns: usize) -> BTreeSet<usize> {
horizontal_tabstops horizontal_tabstops
} }
fn calculate_row_display_height(row_width: usize, viewport_width: usize) -> usize {
if row_width <= viewport_width {
return 1;
}
(row_width as f64 / viewport_width as f64).ceil() as usize
}
fn subtract_isize_from_usize(u: usize, i: isize) -> usize {
if i.is_negative() {
u - i.abs() as usize
} else {
u + i as usize
}
}
#[derive(Debug)] #[derive(Debug)]
pub struct CharacterChunk { pub struct CharacterChunk {
pub terminal_characters: Vec<TerminalCharacter>, pub terminal_characters: Vec<TerminalCharacter>,
@ -362,6 +394,7 @@ pub struct Grid {
pub selection: Selection, pub selection: Selection,
pub title: Option<String>, pub title: Option<String>,
pub is_scrolled: bool, pub is_scrolled: bool,
scrollback_buffer_lines: usize,
} }
impl Debug for Grid { impl Debug for Grid {
@ -407,6 +440,7 @@ impl Grid {
title: None, title: None,
changed_colors: None, changed_colors: None,
is_scrolled: false, is_scrolled: false,
scrollback_buffer_lines: 0,
} }
} }
pub fn render_full_viewport(&mut self) { pub fn render_full_viewport(&mut self) {
@ -453,23 +487,28 @@ impl Grid {
pub fn cursor_shape(&self) -> CursorShape { pub fn cursor_shape(&self) -> CursorShape {
self.cursor.get_shape() self.cursor.get_shape()
} }
pub fn scrollback_position_and_length(&self) -> (usize, usize) { pub fn scrollback_position_and_length(&mut self) -> (usize, usize) {
// (position, length) // (position, length)
(
self.lines_below.len(),
(self.scrollback_buffer_lines + self.lines_below.len()),
)
}
fn recalculate_scrollback_buffer_count(&self) -> usize {
let mut scrollback_buffer_count = 0; let mut scrollback_buffer_count = 0;
for row in self.lines_above.iter() { for row in self.lines_above.iter() {
let row_width = row.width(); let row_width = row.width();
// rows in lines_above are unwrapped, so we need to account for that // rows in lines_above are unwrapped, so we need to account for that
if row_width > self.width { if row_width > self.width {
scrollback_buffer_count += (row_width as f64 / self.width as f64).ceil() as usize; scrollback_buffer_count += calculate_row_display_height(row_width, self.width);
} else { } else {
scrollback_buffer_count += 1; scrollback_buffer_count += 1;
} }
} }
( scrollback_buffer_count
self.lines_below.len(),
(scrollback_buffer_count + self.lines_below.len()),
)
} }
fn set_horizontal_tabstop(&mut self) { fn set_horizontal_tabstop(&mut self) {
self.horizontal_tabstops.insert(self.cursor.x); self.horizontal_tabstops.insert(self.cursor.x);
} }
@ -543,12 +582,15 @@ impl Grid {
let line_to_push_down = self.viewport.pop().unwrap(); let line_to_push_down = self.viewport.pop().unwrap();
self.lines_below.insert(0, line_to_push_down); self.lines_below.insert(0, line_to_push_down);
transfer_rows_from_lines_above_to_viewport( let transferred_rows_height = transfer_rows_from_lines_above_to_viewport(
&mut self.lines_above, &mut self.lines_above,
&mut self.viewport, &mut self.viewport,
1, 1,
self.width, self.width,
); );
self.scrollback_buffer_lines = self
.scrollback_buffer_lines
.saturating_sub(transferred_rows_height);
self.selection.move_down(1); self.selection.move_down(1);
} }
@ -557,12 +599,25 @@ impl Grid {
pub fn scroll_down_one_line(&mut self) { pub fn scroll_down_one_line(&mut self) {
if !self.lines_below.is_empty() && self.viewport.len() == self.height { if !self.lines_below.is_empty() && self.viewport.len() == self.height {
let mut line_to_push_up = self.viewport.remove(0); let mut line_to_push_up = self.viewport.remove(0);
if line_to_push_up.is_canonical {
bounded_push(&mut self.lines_above, line_to_push_up); self.scrollback_buffer_lines +=
calculate_row_display_height(line_to_push_up.width(), self.width);
let line_to_push_up = if line_to_push_up.is_canonical {
line_to_push_up
} else { } else {
let mut last_line_above = self.lines_above.pop_back().unwrap(); let mut last_line_above = self.lines_above.pop_back().unwrap();
last_line_above.append(&mut line_to_push_up.columns); last_line_above.append(&mut line_to_push_up.columns);
bounded_push(&mut self.lines_above, last_line_above); last_line_above
};
let dropped_line_width = bounded_push(&mut self.lines_above, line_to_push_up);
if let Some(width) = dropped_line_width {
let dropped_line_height = calculate_row_display_height(width, self.width);
self.scrollback_buffer_lines = self
.scrollback_buffer_lines
.saturating_sub(dropped_line_height);
} }
transfer_rows_from_lines_below_to_viewport( transfer_rows_from_lines_below_to_viewport(
@ -753,6 +808,7 @@ impl Grid {
if self.scroll_region.is_some() { if self.scroll_region.is_some() {
self.set_scroll_region_to_viewport_size(); self.set_scroll_region_to_viewport_size();
} }
self.scrollback_buffer_lines = self.recalculate_scrollback_buffer_count();
self.output_buffer.update_all_lines(); self.output_buffer.update_all_lines();
} }
pub fn as_character_lines(&self) -> Vec<Vec<TerminalCharacter>> { pub fn as_character_lines(&self) -> Vec<Vec<TerminalCharacter>> {
@ -846,12 +902,16 @@ impl Grid {
} }
pub fn fill_viewport(&mut self, character: TerminalCharacter) { pub fn fill_viewport(&mut self, character: TerminalCharacter) {
let row_count_to_transfer = self.viewport.len(); let row_count_to_transfer = self.viewport.len();
transfer_rows_from_viewport_to_lines_above( let transferred_rows_count = transfer_rows_from_viewport_to_lines_above(
&mut self.viewport, &mut self.viewport,
&mut self.lines_above, &mut self.lines_above,
row_count_to_transfer, row_count_to_transfer,
self.width, self.width,
); );
self.scrollback_buffer_lines =
subtract_isize_from_usize(self.scrollback_buffer_lines, transferred_rows_count);
for _ in 0..self.height { for _ in 0..self.height {
let columns = VecDeque::from(vec![character; self.width]); let columns = VecDeque::from(vec![character; self.width]);
self.viewport.push(Row::from_columns(columns).canonical()); self.viewport.push(Row::from_columns(columns).canonical());
@ -893,12 +953,15 @@ impl Grid {
if self.cursor.y == self.height - 1 { if self.cursor.y == self.height - 1 {
if self.scroll_region.is_none() { if self.scroll_region.is_none() {
let row_count_to_transfer = 1; let row_count_to_transfer = 1;
transfer_rows_from_viewport_to_lines_above( let transferred_rows_count = transfer_rows_from_viewport_to_lines_above(
&mut self.viewport, &mut self.viewport,
&mut self.lines_above, &mut self.lines_above,
row_count_to_transfer, row_count_to_transfer,
self.width, self.width,
); );
self.scrollback_buffer_lines =
subtract_isize_from_usize(self.scrollback_buffer_lines, transferred_rows_count);
self.selection.move_up(1); self.selection.move_up(1);
} }
self.output_buffer.update_all_lines(); self.output_buffer.update_all_lines();
@ -968,12 +1031,14 @@ impl Grid {
self.cursor.x = 0; self.cursor.x = 0;
if self.cursor.y == self.height - 1 { if self.cursor.y == self.height - 1 {
let row_count_to_transfer = 1; let row_count_to_transfer = 1;
transfer_rows_from_viewport_to_lines_above( let transferred_rows_count = transfer_rows_from_viewport_to_lines_above(
&mut self.viewport, &mut self.viewport,
&mut self.lines_above, &mut self.lines_above,
row_count_to_transfer, row_count_to_transfer,
self.width, self.width,
); );
self.scrollback_buffer_lines =
subtract_isize_from_usize(self.scrollback_buffer_lines, transferred_rows_count);
let wrapped_row = Row::new(self.width); let wrapped_row = Row::new(self.width);
self.viewport.push(wrapped_row); self.viewport.push(wrapped_row);
self.selection.move_up(1); self.selection.move_up(1);
@ -1264,6 +1329,7 @@ impl Grid {
self.cursor.change_shape(CursorShape::Initial); self.cursor.change_shape(CursorShape::Initial);
self.output_buffer.update_all_lines(); self.output_buffer.update_all_lines();
self.changed_colors = None; self.changed_colors = None;
self.scrollback_buffer_lines = 0;
} }
fn set_preceding_character(&mut self, terminal_character: TerminalCharacter) { fn set_preceding_character(&mut self, terminal_character: TerminalCharacter) {
self.preceding_char = Some(terminal_character); self.preceding_char = Some(terminal_character);