mirror of
https://github.com/ratatui-org/ratatui
synced 2024-11-25 06:00:34 +00:00
feat: cache the symbol width in the cell
This leads to more than a 50% speedup
This commit is contained in:
parent
2b391ac15d
commit
f08640c53b
3 changed files with 82 additions and 13 deletions
|
@ -6,8 +6,6 @@ use std::{
|
|||
io, iter,
|
||||
};
|
||||
|
||||
use unicode_width::UnicodeWidthStr;
|
||||
|
||||
use crate::{
|
||||
backend::{Backend, ClearType, WindowSize},
|
||||
buffer::{Buffer, Cell},
|
||||
|
@ -52,13 +50,13 @@ fn buffer_view(buffer: &Buffer) -> String {
|
|||
let mut overwritten = vec![];
|
||||
let mut skip: usize = 0;
|
||||
view.push('"');
|
||||
for (x, c) in cells.iter().enumerate() {
|
||||
for (x, cell) in cells.iter().enumerate() {
|
||||
if skip == 0 {
|
||||
view.push_str(c.symbol());
|
||||
view.push_str(cell.symbol());
|
||||
} else {
|
||||
overwritten.push((x, c.symbol()));
|
||||
overwritten.push((x, cell.symbol()));
|
||||
}
|
||||
skip = std::cmp::max(skip, c.symbol().width()).saturating_sub(1);
|
||||
skip = std::cmp::max(skip, cell.width()).saturating_sub(1);
|
||||
}
|
||||
view.push('"');
|
||||
if !overwritten.is_empty() {
|
||||
|
|
|
@ -499,10 +499,8 @@ impl Buffer {
|
|||
updates.push((x, y, &next_buffer[i]));
|
||||
}
|
||||
|
||||
let current_symbol_width = current.symbol().width();
|
||||
let previous_symbol_width = previous.symbol().width();
|
||||
to_skip = current_symbol_width.saturating_sub(1);
|
||||
let affected_width = std::cmp::max(current_symbol_width, previous_symbol_width);
|
||||
to_skip = current.width().saturating_sub(1);
|
||||
let affected_width = std::cmp::max(current.width(), previous.width());
|
||||
invalidated = std::cmp::max(affected_width, invalidated).saturating_sub(1);
|
||||
}
|
||||
updates
|
||||
|
@ -599,7 +597,7 @@ impl fmt::Debug for Buffer {
|
|||
} else {
|
||||
overwritten.push((x, c.symbol()));
|
||||
}
|
||||
skip = std::cmp::max(skip, c.symbol().width()).saturating_sub(1);
|
||||
skip = std::cmp::max(skip, c.width()).saturating_sub(1);
|
||||
#[cfg(feature = "underline-color")]
|
||||
{
|
||||
let style = (c.fg, c.bg, c.underline_color, c.modifier);
|
||||
|
|
|
@ -1,9 +1,12 @@
|
|||
use std::hash::{Hash, Hasher};
|
||||
|
||||
use compact_str::CompactString;
|
||||
use unicode_width::UnicodeWidthStr;
|
||||
|
||||
use crate::style::{Color, Modifier, Style};
|
||||
|
||||
/// A buffer cell
|
||||
#[derive(Debug, Clone, Eq, PartialEq, Hash)]
|
||||
#[derive(Debug, Clone, Eq)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||
pub struct Cell {
|
||||
/// The string to be drawn in the cell.
|
||||
|
@ -31,11 +34,53 @@ pub struct Cell {
|
|||
|
||||
/// Whether the cell should be skipped when copying (diffing) the buffer to the screen.
|
||||
pub skip: bool,
|
||||
|
||||
/// Cache the width of the cell.
|
||||
width: std::cell::Cell<Option<usize>>,
|
||||
}
|
||||
|
||||
impl PartialEq for Cell {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
self.symbol == other.symbol
|
||||
&& self.fg == other.fg
|
||||
&& self.bg == other.bg
|
||||
&& self.underline_color == other.underline_color
|
||||
&& self.modifier == other.modifier
|
||||
&& self.skip == other.skip
|
||||
|
||||
// explicitly not comparing width, as it is a cache and may be not set
|
||||
// && self.width == other.width
|
||||
}
|
||||
}
|
||||
|
||||
impl Hash for Cell {
|
||||
fn hash<H: Hasher>(&self, state: &mut H) {
|
||||
self.symbol.hash(state);
|
||||
self.fg.hash(state);
|
||||
self.bg.hash(state);
|
||||
self.underline_color.hash(state);
|
||||
self.modifier.hash(state);
|
||||
self.skip.hash(state);
|
||||
|
||||
// explicitly not hashing width, as it is a cache and not part of the cell's identity
|
||||
// self.width.hash(state);
|
||||
}
|
||||
}
|
||||
|
||||
impl Cell {
|
||||
/// An empty `Cell`
|
||||
pub const EMPTY: Self = Self::new(" ");
|
||||
pub const EMPTY: Self = {
|
||||
Self {
|
||||
symbol: CompactString::const_new(" "),
|
||||
fg: Color::Reset,
|
||||
bg: Color::Reset,
|
||||
#[cfg(feature = "underline-color")]
|
||||
underline_color: Color::Reset,
|
||||
modifier: Modifier::empty(),
|
||||
skip: false,
|
||||
width: std::cell::Cell::new(Some(1)),
|
||||
}
|
||||
};
|
||||
|
||||
/// Creates a new `Cell` with the given symbol.
|
||||
///
|
||||
|
@ -52,6 +97,7 @@ impl Cell {
|
|||
underline_color: Color::Reset,
|
||||
modifier: Modifier::empty(),
|
||||
skip: false,
|
||||
width: std::cell::Cell::new(None),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -64,6 +110,7 @@ impl Cell {
|
|||
/// Sets the symbol of the cell.
|
||||
pub fn set_symbol(&mut self, symbol: &str) -> &mut Self {
|
||||
self.symbol = CompactString::new(symbol);
|
||||
self.width.set(None);
|
||||
self
|
||||
}
|
||||
|
||||
|
@ -72,6 +119,7 @@ impl Cell {
|
|||
/// This is particularly useful for adding zero-width characters to the cell.
|
||||
pub(crate) fn append_symbol(&mut self, symbol: &str) -> &mut Self {
|
||||
self.symbol.push_str(symbol);
|
||||
self.width.set(None);
|
||||
self
|
||||
}
|
||||
|
||||
|
@ -79,6 +127,7 @@ impl Cell {
|
|||
pub fn set_char(&mut self, ch: char) -> &mut Self {
|
||||
let mut buf = [0; 4];
|
||||
self.symbol = CompactString::new(ch.encode_utf8(&mut buf));
|
||||
self.width.set(None);
|
||||
self
|
||||
}
|
||||
|
||||
|
@ -148,6 +197,21 @@ impl Cell {
|
|||
}
|
||||
self.modifier = Modifier::empty();
|
||||
self.skip = false;
|
||||
self.width.set(Some(1));
|
||||
}
|
||||
|
||||
/// Returns the width of the cell.
|
||||
///
|
||||
/// This value is cached and will only be recomputed when the cell is modified.
|
||||
#[must_use]
|
||||
pub fn width(&self) -> usize {
|
||||
if let Some(width) = self.width.get() {
|
||||
width
|
||||
} else {
|
||||
let width = self.symbol().width();
|
||||
self.width.set(Some(width));
|
||||
width
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -182,6 +246,7 @@ mod tests {
|
|||
underline_color: Color::Reset,
|
||||
modifier: Modifier::empty(),
|
||||
skip: false,
|
||||
width: std::cell::Cell::new(None),
|
||||
}
|
||||
);
|
||||
}
|
||||
|
@ -294,4 +359,12 @@ mod tests {
|
|||
let cell2 = Cell::new("い");
|
||||
assert_ne!(cell1, cell2);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn width() {
|
||||
let cell = Cell::new("あ");
|
||||
assert_eq!(cell.width, std::cell::Cell::new(None)); // not yet cached
|
||||
assert_eq!(cell.width(), 2);
|
||||
assert_eq!(cell.width, std::cell::Cell::new(Some(2))); // cached
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue