Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

perf(vt100) increase render performance for tui_term codepath #9123

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
60 changes: 45 additions & 15 deletions crates/turborepo-vt100/src/cell.rs
Original file line number Diff line number Diff line change
@@ -1,34 +1,39 @@
use unicode_width::UnicodeWidthChar as _;

const CODEPOINTS_IN_CELL: usize = 6;
const BYTES_IN_CHAR: usize = 4;

/// Represents a single terminal cell.
#[derive(Clone, Debug, Eq)]
pub struct Cell {
contents: [char; CODEPOINTS_IN_CELL],
contents: [u8; CODEPOINTS_IN_CELL * BYTES_IN_CHAR],
// Number of bytes needed to represent all characters
num_bytes: u8,
// Length of characters
len: u8,
attrs: crate::attrs::Attrs,
selected: bool,
}

impl PartialEq<Self> for Cell {
fn eq(&self, other: &Self) -> bool {
if self.len != other.len {
if self.len != other.len || self.num_bytes != other.num_bytes {
return false;
}
if self.attrs != other.attrs {
return false;
}
let len = self.len();
let num_bytes = self.num_bytes();
// self.len() always returns a valid value
self.contents[..len] == other.contents[..len]
self.contents[..num_bytes] == other.contents[..num_bytes]
}
}

impl Cell {
pub(crate) fn new() -> Self {
Self {
contents: Default::default(),
num_bytes: 0,
len: 0,
attrs: crate::attrs::Attrs::default(),
selected: false,
Expand All @@ -40,9 +45,15 @@ impl Cell {
usize::from(self.len & 0x0f)
}

#[inline]
fn num_bytes(&self) -> usize {
usize::from(self.num_bytes & 0x0f)
}

pub(crate) fn set(&mut self, c: char, a: crate::attrs::Attrs) {
self.contents[0] = c;
self.len = 1;
self.num_bytes = 0;
self.len = 0;
self.append_char(0, c);
// strings in this context should always be an arbitrary character
// followed by zero or more zero-width characters, so we should only
// have to look at the first character
Expand All @@ -52,23 +63,44 @@ impl Cell {

pub(crate) fn append(&mut self, c: char) {
let len = self.len();
// This implies that len is at most 5 meaning num_bytes is at most 20
// with still enough room for a 4 byte char.
if len >= CODEPOINTS_IN_CELL {
return;
}
if len == 0 {
// 0 is always less than 6
self.contents[0] = ' ';
self.contents[0] = b' ';
self.num_bytes += 1;
self.len += 1;
}

let len = self.len();
let num_bytes = self.num_bytes();
// we already checked that len < CODEPOINTS_IN_CELL
self.contents[len] = c;
self.append_char(num_bytes, c);
}

#[allow(clippy::cast_possible_truncation, clippy::as_conversions)]
#[inline]
// Writes bytes representing c at start
// Requires caller to verify start <= CODEPOINTS_IN_CELL * 4
fn append_char(&mut self, start: usize, c: char) {
match c.len_utf8() {
1 => {
self.contents[start] = c as u8;
self.num_bytes += 1;
}
n => {
c.encode_utf8(&mut self.contents[start..]);
self.num_bytes += n as u8;
}
}
self.len += 1;
}

pub(crate) fn clear(&mut self, attrs: crate::attrs::Attrs) {
self.len = 0;
self.num_bytes = 0;
self.attrs = attrs;
self.selected = false;
}
Expand All @@ -87,12 +119,10 @@ impl Cell {
/// used, but will contain at most one character with a non-zero character
/// width.
#[must_use]
pub fn contents(&self) -> String {
let mut s = String::with_capacity(CODEPOINTS_IN_CELL * 4);
for c in self.contents.iter().take(self.len()) {
s.push(*c);
}
s
pub fn contents(&self) -> &str {
let num_bytes = self.num_bytes();
// Since contents has been constructed by appending chars encoded as UTF-8 it will be valid UTF-8
unsafe { std::str::from_utf8_unchecked(&self.contents[..num_bytes]) }
}

/// Returns whether the cell contains any text data.
Expand Down
16 changes: 15 additions & 1 deletion crates/turborepo-vt100/src/grid.rs
Original file line number Diff line number Diff line change
Expand Up @@ -198,7 +198,21 @@ impl Grid {
}

pub fn visible_row(&self, row: u16) -> Option<&crate::row::Row> {
self.visible_rows().nth(usize::from(row))
// The number of rows that are in scrollback before the current screen
let scrollback_rows_before_screen =
self.scrollback.len().saturating_sub(self.scrollback_offset);
// The index for row if it is in the scrollback
let scrollback_idx = scrollback_rows_before_screen + usize::from(row);
// If this is a valid scrollback index, then return that row
if scrollback_idx < self.scrollback.len() {
self.scrollback.get(scrollback_idx)
} else {
// Need to subtract scrollback offset
// e.g. if we are showing 1 row of scrollback, and user requests 3rd visible row
// we should return the second row in self.rows
self.rows
.get(usize::from(row).saturating_sub(self.scrollback_offset))
}
}

pub fn drawing_row(&self, row: u16) -> Option<&crate::row::Row> {
Expand Down
4 changes: 2 additions & 2 deletions crates/turborepo-vt100/src/row.rs
Original file line number Diff line number Diff line change
Expand Up @@ -135,7 +135,7 @@ impl Row {
}
prev_col += col - prev_col;

contents.push_str(&cell.contents());
contents.push_str(cell.contents());
prev_col += if cell.is_wide() { 2 } else { 1 };
}
}
Expand Down Expand Up @@ -322,7 +322,7 @@ impl Row {
}
let mut cell_contents = prev_first_cell.contents();
let need_erase = if cell_contents.is_empty() {
cell_contents = " ".to_string();
cell_contents = " ";
true
} else {
false
Expand Down
2 changes: 1 addition & 1 deletion crates/turborepo-vt100/src/tui_term.rs
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ fn fill_buf_cell(
let fg = screen_cell.fgcolor();
let bg = screen_cell.bgcolor();

buf_cell.set_symbol(&screen_cell.contents());
buf_cell.set_symbol(screen_cell.contents());
let fg: Color = fg.into();
let bg: Color = bg.into();
let mut style = Style::reset();
Expand Down
2 changes: 1 addition & 1 deletion crates/turborepo-vt100/tests/helpers/fixtures.rs
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ impl FixtureCell {
#[allow(dead_code)]
pub fn from_cell(cell: &vt100::Cell) -> Self {
Self {
contents: cell.contents(),
contents: cell.contents().to_string(),
is_wide: cell.is_wide(),
is_wide_continuation: cell.is_wide_continuation(),
fgcolor: cell.fgcolor(),
Expand Down
Loading