From 17beb9c06de476b1f6774ca99506b2a2daffdefb Mon Sep 17 00:00:00 2001 From: Nathan Vegdahl Date: Sun, 31 Dec 2017 20:05:00 -0800 Subject: [PATCH] Using a smallstring implementation inside the screen buffer. This keeps things contiguous in memory in the common case. --- Cargo.lock | 1 + Cargo.toml | 1 + src/main.rs | 1 + src/term_ui/mod.rs | 1 + src/term_ui/screen.rs | 16 ++-- src/term_ui/smallstring.rs | 158 +++++++++++++++++++++++++++++++++++++ 6 files changed, 170 insertions(+), 8 deletions(-) create mode 100644 src/term_ui/smallstring.rs diff --git a/Cargo.lock b/Cargo.lock index d46660c..d34e078 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6,6 +6,7 @@ dependencies = [ "ropey 0.5.5 (registry+https://github.com/rust-lang/crates.io-index)", "serde 1.0.27 (registry+https://github.com/rust-lang/crates.io-index)", "serde_derive 1.0.27 (registry+https://github.com/rust-lang/crates.io-index)", + "smallvec 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)", "termion 1.5.1 (registry+https://github.com/rust-lang/crates.io-index)", "unicode-segmentation 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)", "unicode-width 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)", diff --git a/Cargo.toml b/Cargo.toml index eb060b6..2ae057b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -15,4 +15,5 @@ unicode-width = "0.1" serde = "1.*" serde_derive = "1.*" docopt = "0.8" +smallvec = "0.6" termion = "1.5" \ No newline at end of file diff --git a/src/main.rs b/src/main.rs index c8331a5..f0f8276 100644 --- a/src/main.rs +++ b/src/main.rs @@ -3,6 +3,7 @@ extern crate ropey; extern crate serde; #[macro_use] extern crate serde_derive; +extern crate smallvec; extern crate termion; extern crate unicode_segmentation; extern crate unicode_width; diff --git a/src/term_ui/mod.rs b/src/term_ui/mod.rs index c453313..16ddc9e 100644 --- a/src/term_ui/mod.rs +++ b/src/term_ui/mod.rs @@ -15,6 +15,7 @@ use utils::digit_count; pub mod formatter; mod screen; +mod smallstring; use self::screen::{Color, Screen, Style}; diff --git a/src/term_ui/screen.rs b/src/term_ui/screen.rs index 14f1822..2391852 100644 --- a/src/term_ui/screen.rs +++ b/src/term_ui/screen.rs @@ -3,6 +3,7 @@ use std::cell::RefCell; use std::io; use std::io::Write; +use super::smallstring::SmallString; use unicode_width::UnicodeWidthStr; use unicode_segmentation::UnicodeSegmentation; use termion; @@ -12,7 +13,7 @@ use termion::raw::{IntoRawMode, RawTerminal}; pub(crate) struct Screen { out: RefCell>>, - buf: RefCell>>, + buf: RefCell>>, w: usize, h: usize, } @@ -20,7 +21,7 @@ pub(crate) struct Screen { impl Screen { pub(crate) fn new() -> Self { let (w, h) = termion::terminal_size().unwrap(); - let buf = std::iter::repeat(Some((Style(Color::Black, Color::Black), " ".to_string()))) + let buf = std::iter::repeat(Some((Style(Color::Black, Color::Black), " ".into()))) .take(w as usize * h as usize) .collect(); Screen { @@ -40,7 +41,7 @@ impl Screen { text.push_str(" "); } _ => { - *cell = Some((Style(Color::Black, Color::Black), " ".to_string())); + *cell = Some((Style(Color::Black, Color::Black), " ".into())); } } } @@ -49,10 +50,9 @@ impl Screen { pub(crate) fn resize(&mut self, w: usize, h: usize) { self.w = w; self.h = h; - self.buf.borrow_mut().resize( - w * h, - Some((Style(Color::Black, Color::Black), " ".to_string())), - ); + self.buf + .borrow_mut() + .resize(w * h, Some((Style(Color::Black, Color::Black), " ".into()))); } pub(crate) fn present(&self) { @@ -99,7 +99,7 @@ impl Screen { for g in UnicodeSegmentation::graphemes(text, true) { let width = UnicodeWidthStr::width(g); if width > 0 { - buf[y * self.w + x] = Some((style, g.to_string())); + buf[y * self.w + x] = Some((style, g.into())); x += 1; for _ in 0..(width - 1) { buf[y * self.w + x] = None; diff --git a/src/term_ui/smallstring.rs b/src/term_ui/smallstring.rs new file mode 100644 index 0000000..8665419 --- /dev/null +++ b/src/term_ui/smallstring.rs @@ -0,0 +1,158 @@ +use std; + +use std::borrow::Borrow; +use std::ops::Deref; +use std::ptr; +use std::str; + +use smallvec::SmallVec; + +#[derive(Clone, Default)] +pub(crate) struct SmallString { + buffer: SmallVec<[u8; 8]>, +} + +impl SmallString { + /// Creates a new empty `SmallString` + #[inline(always)] + pub fn new() -> Self { + SmallString { + buffer: SmallVec::new(), + } + } + + /// Creates a new empty `SmallString` with at least `capacity` bytes + /// of capacity. + #[inline(always)] + pub fn with_capacity(capacity: usize) -> Self { + SmallString { + buffer: SmallVec::with_capacity(capacity), + } + } + + /// Creates a new `SmallString` with the same contents as the given `&str`. + pub fn from_str(text: &str) -> Self { + let mut string = SmallString::with_capacity(text.len()); + unsafe { string.insert_bytes(0, text.as_bytes()) }; + string + } + + /// Appends a `&str` to end the of the `SmallString`. + pub fn push_str(&mut self, string: &str) { + let len = self.len(); + unsafe { + self.insert_bytes(len, string.as_bytes()); + } + } + + /// Drops the text after byte index `idx`. + /// + /// Panics if `idx` is not a char boundary, as that would result in an + /// invalid utf8 string. + pub fn truncate(&mut self, idx: usize) { + assert!(self.is_char_boundary(idx)); + debug_assert!(idx <= self.len()); + self.buffer.truncate(idx); + } + + pub fn clear(&mut self) { + self.truncate(0); + } + + #[inline(always)] + unsafe fn insert_bytes(&mut self, idx: usize, bytes: &[u8]) { + assert!(idx <= self.len()); + let len = self.len(); + let amt = bytes.len(); + self.buffer.reserve(amt); + + ptr::copy( + self.buffer.as_ptr().offset(idx as isize), + self.buffer.as_mut_ptr().offset((idx + amt) as isize), + len - idx, + ); + ptr::copy( + bytes.as_ptr(), + self.buffer.as_mut_ptr().offset(idx as isize), + amt, + ); + self.buffer.set_len(len + amt); + } + + #[inline(always)] + unsafe fn remove_bytes(&mut self, start: usize, end: usize) { + assert!(start <= end); + assert!(end <= self.len()); + let len = self.len(); + let amt = end - start; + ptr::copy( + self.buffer.as_ptr().offset(end as isize), + self.buffer.as_mut_ptr().offset(start as isize), + len - end, + ); + self.buffer.set_len(len - amt); + } +} + +impl std::cmp::PartialEq for SmallString { + fn eq(&self, other: &Self) -> bool { + let (s1, s2): (&str, &str) = (self, other); + s1 == s2 + } +} + +impl<'a> PartialEq for &'a str { + fn eq(&self, other: &SmallString) -> bool { + *self == (other as &str) + } +} + +impl<'a> PartialEq<&'a str> for SmallString { + fn eq(&self, other: &&'a str) -> bool { + (self as &str) == *other + } +} + +impl std::fmt::Display for SmallString { + fn fmt(&self, fm: &mut std::fmt::Formatter) -> Result<(), std::fmt::Error> { + SmallString::deref(self).fmt(fm) + } +} + +impl std::fmt::Debug for SmallString { + fn fmt(&self, fm: &mut std::fmt::Formatter) -> std::fmt::Result { + SmallString::deref(self).fmt(fm) + } +} + +impl<'a> From<&'a str> for SmallString { + fn from(s: &str) -> Self { + Self::from_str(s) + } +} + +impl Deref for SmallString { + type Target = str; + + fn deref(&self) -> &str { + // SmallString's methods don't allow `buffer` to become invalid utf8, + // so this is safe. + unsafe { str::from_utf8_unchecked(self.buffer.as_ref()) } + } +} + +impl AsRef for SmallString { + fn as_ref(&self) -> &str { + // SmallString's methods don't allow `buffer` to become invalid utf8, + // so this is safe. + unsafe { str::from_utf8_unchecked(self.buffer.as_ref()) } + } +} + +impl Borrow for SmallString { + fn borrow(&self) -> &str { + // SmallString's methods don't allow `buffer` to become invalid utf8, + // so this is safe. + unsafe { str::from_utf8_unchecked(self.buffer.as_ref()) } + } +}