Using a smallstring implementation inside the screen buffer.

This keeps things contiguous in memory in the common case.
This commit is contained in:
Nathan Vegdahl 2017-12-31 20:05:00 -08:00
parent 0a309fab1a
commit 17beb9c06d
6 changed files with 170 additions and 8 deletions

1
Cargo.lock generated
View File

@ -6,6 +6,7 @@ dependencies = [
"ropey 0.5.5 (registry+https://github.com/rust-lang/crates.io-index)", "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 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)", "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)", "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-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)", "unicode-width 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)",

View File

@ -15,4 +15,5 @@ unicode-width = "0.1"
serde = "1.*" serde = "1.*"
serde_derive = "1.*" serde_derive = "1.*"
docopt = "0.8" docopt = "0.8"
smallvec = "0.6"
termion = "1.5" termion = "1.5"

View File

@ -3,6 +3,7 @@ extern crate ropey;
extern crate serde; extern crate serde;
#[macro_use] #[macro_use]
extern crate serde_derive; extern crate serde_derive;
extern crate smallvec;
extern crate termion; extern crate termion;
extern crate unicode_segmentation; extern crate unicode_segmentation;
extern crate unicode_width; extern crate unicode_width;

View File

@ -15,6 +15,7 @@ use utils::digit_count;
pub mod formatter; pub mod formatter;
mod screen; mod screen;
mod smallstring;
use self::screen::{Color, Screen, Style}; use self::screen::{Color, Screen, Style};

View File

@ -3,6 +3,7 @@ use std::cell::RefCell;
use std::io; use std::io;
use std::io::Write; use std::io::Write;
use super::smallstring::SmallString;
use unicode_width::UnicodeWidthStr; use unicode_width::UnicodeWidthStr;
use unicode_segmentation::UnicodeSegmentation; use unicode_segmentation::UnicodeSegmentation;
use termion; use termion;
@ -12,7 +13,7 @@ use termion::raw::{IntoRawMode, RawTerminal};
pub(crate) struct Screen { pub(crate) struct Screen {
out: RefCell<AlternateScreen<RawTerminal<io::Stdout>>>, out: RefCell<AlternateScreen<RawTerminal<io::Stdout>>>,
buf: RefCell<Vec<Option<(Style, String)>>>, buf: RefCell<Vec<Option<(Style, SmallString)>>>,
w: usize, w: usize,
h: usize, h: usize,
} }
@ -20,7 +21,7 @@ pub(crate) struct Screen {
impl Screen { impl Screen {
pub(crate) fn new() -> Self { pub(crate) fn new() -> Self {
let (w, h) = termion::terminal_size().unwrap(); 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) .take(w as usize * h as usize)
.collect(); .collect();
Screen { Screen {
@ -40,7 +41,7 @@ impl Screen {
text.push_str(" "); 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) { pub(crate) fn resize(&mut self, w: usize, h: usize) {
self.w = w; self.w = w;
self.h = h; self.h = h;
self.buf.borrow_mut().resize( self.buf
w * h, .borrow_mut()
Some((Style(Color::Black, Color::Black), " ".to_string())), .resize(w * h, Some((Style(Color::Black, Color::Black), " ".into())));
);
} }
pub(crate) fn present(&self) { pub(crate) fn present(&self) {
@ -99,7 +99,7 @@ impl Screen {
for g in UnicodeSegmentation::graphemes(text, true) { for g in UnicodeSegmentation::graphemes(text, true) {
let width = UnicodeWidthStr::width(g); let width = UnicodeWidthStr::width(g);
if width > 0 { if width > 0 {
buf[y * self.w + x] = Some((style, g.to_string())); buf[y * self.w + x] = Some((style, g.into()));
x += 1; x += 1;
for _ in 0..(width - 1) { for _ in 0..(width - 1) {
buf[y * self.w + x] = None; buf[y * self.w + x] = None;

158
src/term_ui/smallstring.rs Normal file
View File

@ -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<SmallString> 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<str> 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<str> 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()) }
}
}