Skip to content

Commit afd2f65

Browse files
committed
perf: Rc styles to reduce TerminalCharacter from 60 to 24 bytes
With a 10MB single line catted into a fresh terminal, VmRSS goes from 845156 kB to 478396 kB (as reported by /proc/<pid>/status) before/after this patch Given a 10MB line catted into the terminal, a toggle-fullscreen + toggle-fullscreen + close-pane + `run true` goes from ~12.5s to ~7s
1 parent 846fda7 commit afd2f65

File tree

6 files changed

+123
-38
lines changed

6 files changed

+123
-38
lines changed

zellij-server/src/output/mod.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -98,7 +98,7 @@ fn serialize_chunks_with_newlines(
9898
for t_character in character_chunk.terminal_characters.iter() {
9999
let current_character_styles = adjust_styles_for_possible_selection(
100100
character_chunk.selection_and_colors(),
101-
t_character.styles,
101+
*t_character.styles,
102102
character_chunk.y,
103103
chunk_width,
104104
);
@@ -139,7 +139,7 @@ fn serialize_chunks(
139139
for t_character in character_chunk.terminal_characters.iter() {
140140
let current_character_styles = adjust_styles_for_possible_selection(
141141
character_chunk.selection_and_colors(),
142-
t_character.styles,
142+
*t_character.styles,
143143
character_chunk.y,
144144
chunk_width,
145145
);

zellij-server/src/panes/grid.rs

Lines changed: 29 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ use crate::panes::link_handler::LinkHandler;
3434
use crate::panes::search::SearchResult;
3535
use crate::panes::selection::Selection;
3636
use crate::panes::terminal_character::{
37-
AnsiCode, CharacterStyles, CharsetIndex, Cursor, CursorShape, StandardCharset,
37+
AnsiCode, CharsetIndex, Cursor, CursorShape, RcCharacterStyles, StandardCharset,
3838
TerminalCharacter, EMPTY_TERMINAL_CHARACTER,
3939
};
4040
use crate::ui::components::UiComponentParser;
@@ -513,7 +513,7 @@ impl Grid {
513513
pub fn update_line_for_rendering(&mut self, line_index: usize) {
514514
self.output_buffer.update_line(line_index);
515515
}
516-
pub fn advance_to_next_tabstop(&mut self, styles: CharacterStyles) {
516+
pub fn advance_to_next_tabstop(&mut self, styles: RcCharacterStyles) {
517517
let next_tabstop = self
518518
.horizontal_tabstops
519519
.iter()
@@ -1186,7 +1186,7 @@ impl Grid {
11861186
self.viewport.remove(scroll_region_bottom);
11871187
}
11881188
let mut pad_character = EMPTY_TERMINAL_CHARACTER;
1189-
pad_character.styles = self.cursor.pending_styles;
1189+
pad_character.styles = self.cursor.pending_styles.clone();
11901190
let columns = VecDeque::from(vec![pad_character; self.width]);
11911191
self.viewport
11921192
.insert(scroll_region_top, Row::from_columns(columns).canonical());
@@ -1202,7 +1202,7 @@ impl Grid {
12021202
{
12031203
self.pad_lines_until(scroll_region_bottom, EMPTY_TERMINAL_CHARACTER);
12041204
let mut pad_character = EMPTY_TERMINAL_CHARACTER;
1205-
pad_character.styles = self.cursor.pending_styles;
1205+
pad_character.styles = self.cursor.pending_styles.clone();
12061206
for _ in 0..count {
12071207
self.viewport.remove(scroll_region_top);
12081208
let columns = VecDeque::from(vec![pad_character.clone(); self.width]);
@@ -1246,14 +1246,14 @@ impl Grid {
12461246
}
12471247

12481248
let mut pad_character = EMPTY_TERMINAL_CHARACTER;
1249-
pad_character.styles = self.cursor.pending_styles;
1249+
pad_character.styles = self.cursor.pending_styles.clone();
12501250
let columns = VecDeque::from(vec![pad_character; self.width]);
12511251
self.viewport.push(Row::from_columns(columns).canonical());
12521252
self.selection.move_up(1);
12531253
} else {
12541254
self.viewport.remove(scroll_region_top);
12551255
let mut pad_character = EMPTY_TERMINAL_CHARACTER;
1256-
pad_character.styles = self.cursor.pending_styles;
1256+
pad_character.styles = self.cursor.pending_styles.clone();
12571257
let columns = VecDeque::from(vec![pad_character; self.width]);
12581258
if self.viewport.len() >= scroll_region_bottom {
12591259
self.viewport
@@ -1562,7 +1562,7 @@ impl Grid {
15621562
let bottom_line_index = bottom_line_index.unwrap_or(self.height);
15631563
self.scroll_region = Some((top_line_index, bottom_line_index));
15641564
let mut pad_character = EMPTY_TERMINAL_CHARACTER;
1565-
pad_character.styles = self.cursor.pending_styles;
1565+
pad_character.styles = self.cursor.pending_styles.clone();
15661566
self.move_cursor_to(0, 0, pad_character); // DECSTBM moves the cursor to column 1 line 1 of the page
15671567
}
15681568
pub fn clear_scroll_region(&mut self) {
@@ -1634,7 +1634,7 @@ impl Grid {
16341634
let pad_character = EMPTY_TERMINAL_CHARACTER;
16351635
self.pad_current_line_until(self.cursor.x, pad_character);
16361636
}
1637-
pub fn replace_with_empty_chars(&mut self, count: usize, empty_char_style: CharacterStyles) {
1637+
pub fn replace_with_empty_chars(&mut self, count: usize, empty_char_style: RcCharacterStyles) {
16381638
let mut empty_character = EMPTY_TERMINAL_CHARACTER;
16391639
empty_character.styles = empty_char_style;
16401640
let pad_until = std::cmp::min(self.width, self.cursor.x + count);
@@ -1646,7 +1646,7 @@ impl Grid {
16461646
self.output_buffer.update_line(self.cursor.y);
16471647
}
16481648
}
1649-
fn erase_characters(&mut self, count: usize, empty_char_style: CharacterStyles) {
1649+
fn erase_characters(&mut self, count: usize, empty_char_style: RcCharacterStyles) {
16501650
let mut empty_character = EMPTY_TERMINAL_CHARACTER;
16511651
empty_character.styles = empty_char_style;
16521652
if let Some(current_row) = self.viewport.get_mut(self.cursor.y) {
@@ -2144,7 +2144,7 @@ impl Perform for Grid {
21442144
fn print(&mut self, c: char) {
21452145
let c = self.cursor.charsets[self.active_charset].map(c);
21462146

2147-
let terminal_character = TerminalCharacter::new_styled(c, self.cursor.pending_styles);
2147+
let terminal_character = TerminalCharacter::new_styled(c, self.cursor.pending_styles.clone());
21482148
self.set_preceding_character(terminal_character.clone());
21492149
self.add_character(terminal_character);
21502150
}
@@ -2160,7 +2160,7 @@ impl Perform for Grid {
21602160
},
21612161
9 => {
21622162
// tab
2163-
self.advance_to_next_tabstop(self.cursor.pending_styles);
2163+
self.advance_to_next_tabstop(self.cursor.pending_styles.clone());
21642164
},
21652165
10 | 11 | 12 => {
21662166
// 0a, newline
@@ -2288,8 +2288,9 @@ impl Perform for Grid {
22882288
if params.len() < 3 {
22892289
return;
22902290
}
2291-
self.cursor.pending_styles.link_anchor =
2292-
self.link_handler.borrow_mut().dispatch_osc8(params);
2291+
self.cursor.pending_styles.update(|styles| {
2292+
styles.link_anchor = self.link_handler.borrow_mut().dispatch_osc8(params)
2293+
})
22932294
},
22942295

22952296
// Get/set Foreground (b"10") or background (b"11") colors
@@ -2442,7 +2443,9 @@ impl Perform for Grid {
24422443
if intermediates.is_empty() {
24432444
self.cursor
24442445
.pending_styles
2445-
.add_style_from_ansi_params(&mut params_iter);
2446+
.update(|styles| {
2447+
styles.add_style_from_ansi_params(&mut params_iter)
2448+
})
24462449
}
24472450
} else if c == 'C' || c == 'a' {
24482451
// move cursor forward
@@ -2453,7 +2456,9 @@ impl Perform for Grid {
24532456
if let Some(clear_type) = params_iter.next().map(|param| param[0]) {
24542457
let mut char_to_replace = EMPTY_TERMINAL_CHARACTER;
24552458
if let Some(background_color) = self.cursor.pending_styles.background {
2456-
char_to_replace.styles.background = Some(background_color);
2459+
char_to_replace.styles.update(|styles| {
2460+
styles.background = Some(background_color)
2461+
});
24572462
}
24582463
if clear_type == 0 {
24592464
self.replace_characters_in_line_after_cursor(char_to_replace);
@@ -2467,7 +2472,9 @@ impl Perform for Grid {
24672472
// clear all (0 => below, 1 => above, 2 => all, 3 => saved)
24682473
let mut char_to_replace = EMPTY_TERMINAL_CHARACTER;
24692474
if let Some(background_color) = self.cursor.pending_styles.background {
2470-
char_to_replace.styles.background = Some(background_color);
2475+
char_to_replace.styles.update(|styles| {
2476+
styles.background = Some(background_color)
2477+
});
24712478
}
24722479
if let Some(clear_type) = params_iter.next().map(|param| param[0]) {
24732480
if clear_type == 0 {
@@ -2729,13 +2736,13 @@ impl Perform for Grid {
27292736
// delete lines if currently inside scroll region
27302737
let line_count_to_delete = next_param_or(1);
27312738
let mut pad_character = EMPTY_TERMINAL_CHARACTER;
2732-
pad_character.styles = self.cursor.pending_styles;
2739+
pad_character.styles = self.cursor.pending_styles.clone();
27332740
self.delete_lines_in_scroll_region(line_count_to_delete, pad_character);
27342741
} else if c == 'L' {
27352742
// insert blank lines if inside scroll region
27362743
let line_count_to_add = next_param_or(1);
27372744
let mut pad_character = EMPTY_TERMINAL_CHARACTER;
2738-
pad_character.styles = self.cursor.pending_styles;
2745+
pad_character.styles = self.cursor.pending_styles.clone();
27392746
self.add_empty_lines_in_scroll_region(line_count_to_add, pad_character);
27402747
} else if c == 'G' || c == '`' {
27412748
let column = next_param_or(1).saturating_sub(1);
@@ -2756,11 +2763,11 @@ impl Perform for Grid {
27562763
} else if c == 'P' {
27572764
// erase characters
27582765
let count = next_param_or(1);
2759-
self.erase_characters(count, self.cursor.pending_styles);
2766+
self.erase_characters(count, self.cursor.pending_styles.clone());
27602767
} else if c == 'X' {
27612768
// erase characters and replace with empty characters of current style
27622769
let count = next_param_or(1);
2763-
self.replace_with_empty_chars(count, self.cursor.pending_styles);
2770+
self.replace_with_empty_chars(count, self.cursor.pending_styles.clone());
27642771
} else if c == 'T' {
27652772
/*
27662773
* 124 54 T SD
@@ -2817,7 +2824,7 @@ impl Perform for Grid {
28172824
let count = next_param_or(1);
28182825
for _ in 0..count {
28192826
let mut pad_character = EMPTY_TERMINAL_CHARACTER;
2820-
pad_character.styles = self.cursor.pending_styles;
2827+
pad_character.styles = self.cursor.pending_styles.clone();
28212828
self.add_character_at_cursor_position(pad_character, true);
28222829
}
28232830
} else if c == 'b' {
@@ -2839,7 +2846,7 @@ impl Perform for Grid {
28392846
self.move_cursor_to_beginning_of_line();
28402847
} else if c == 'I' {
28412848
for _ in 0..next_param_or(1) {
2842-
self.advance_to_next_tabstop(self.cursor.pending_styles);
2849+
self.advance_to_next_tabstop(self.cursor.pending_styles.clone());
28432850
}
28442851
} else if c == 'q' {
28452852
let first_intermediate_is_space = matches!(intermediates.get(0), Some(b' '));

zellij-server/src/panes/terminal_character.rs

Lines changed: 84 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
use std::convert::From;
22
use std::fmt::{self, Debug, Display, Formatter};
33
use std::ops::{Index, IndexMut};
4+
use std::rc::Rc;
45
use unicode_width::UnicodeWidthChar;
56

67
use unicode_width::UnicodeWidthStr;
@@ -15,7 +16,7 @@ use crate::panes::alacritty_functions::parse_sgr_color;
1516
pub const EMPTY_TERMINAL_CHARACTER: TerminalCharacter = TerminalCharacter {
1617
character: ' ',
1718
width: 1,
18-
styles: RESET_STYLES,
19+
styles: RcCharacterStyles::Reset,
1920
};
2021

2122
pub const RESET_STYLES: CharacterStyles = CharacterStyles {
@@ -35,6 +36,25 @@ pub const RESET_STYLES: CharacterStyles = CharacterStyles {
3536
styled_underlines_enabled: false,
3637
};
3738

39+
// Have to manually write this out because Default::default
40+
// is not a const fn
41+
const DEFAULT_STYLES: CharacterStyles = CharacterStyles {
42+
foreground: None,
43+
background: None,
44+
underline_color: None,
45+
strike: None,
46+
hidden: None,
47+
reverse: None,
48+
slow_blink: None,
49+
fast_blink: None,
50+
underline: None,
51+
bold: None,
52+
dim: None,
53+
italic: None,
54+
link_anchor: None,
55+
styled_underlines_enabled: false,
56+
};
57+
3858
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
3959
pub enum AnsiCode {
4060
On,
@@ -129,7 +149,54 @@ impl NamedColor {
129149
}
130150
}
131151

132-
#[derive(Clone, Copy, Debug, Default)]
152+
#[derive(Clone, Debug, Default, PartialEq)]
153+
pub enum RcCharacterStyles {
154+
#[default]
155+
Default,
156+
Reset,
157+
Rc(Rc<CharacterStyles>),
158+
}
159+
160+
impl From<CharacterStyles> for RcCharacterStyles {
161+
fn from(styles: CharacterStyles) -> Self {
162+
if styles == DEFAULT_STYLES {
163+
RcCharacterStyles::Default
164+
} else if styles == RESET_STYLES {
165+
RcCharacterStyles::Reset
166+
} else {
167+
RcCharacterStyles::Rc(Rc::new(styles))
168+
}
169+
}
170+
}
171+
172+
impl std::ops::Deref for RcCharacterStyles {
173+
type Target = CharacterStyles;
174+
175+
fn deref(&self) -> &Self::Target {
176+
match self {
177+
RcCharacterStyles::Default => &DEFAULT_STYLES,
178+
RcCharacterStyles::Reset => &RESET_STYLES,
179+
RcCharacterStyles::Rc(styles) => &*styles,
180+
}
181+
}
182+
}
183+
184+
impl Display for RcCharacterStyles {
185+
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
186+
let styles: &CharacterStyles = &*self;
187+
Display::fmt(&styles, f)
188+
}
189+
}
190+
191+
impl RcCharacterStyles {
192+
pub fn update(&mut self, f: impl FnOnce(&mut CharacterStyles)) {
193+
let mut styles: CharacterStyles = **self;
194+
f(&mut styles);
195+
*self = styles.into();
196+
}
197+
}
198+
199+
#[derive(Clone, Copy, Debug)]
133200
pub struct CharacterStyles {
134201
pub foreground: Option<AnsiCode>,
135202
pub background: Option<AnsiCode>,
@@ -147,6 +214,12 @@ pub struct CharacterStyles {
147214
pub styled_underlines_enabled: bool,
148215
}
149216

217+
impl Default for CharacterStyles {
218+
fn default() -> Self {
219+
DEFAULT_STYLES
220+
}
221+
}
222+
150223
impl PartialEq for CharacterStyles {
151224
fn eq(&self, other: &Self) -> bool {
152225
self.foreground == other.foreground
@@ -813,7 +886,7 @@ impl CursorShape {
813886
pub struct Cursor {
814887
pub x: usize,
815888
pub y: usize,
816-
pub pending_styles: CharacterStyles,
889+
pub pending_styles: RcCharacterStyles,
817890
pub charsets: Charsets,
818891
shape: CursorShape,
819892
}
@@ -823,7 +896,9 @@ impl Cursor {
823896
Cursor {
824897
x,
825898
y,
826-
pending_styles: RESET_STYLES.enable_styled_underlines(styled_underlines),
899+
pending_styles: RESET_STYLES
900+
.enable_styled_underlines(styled_underlines)
901+
.into(),
827902
charsets: Default::default(),
828903
shape: CursorShape::Initial,
829904
}
@@ -839,18 +914,18 @@ impl Cursor {
839914
#[derive(Clone, PartialEq)]
840915
pub struct TerminalCharacter {
841916
pub character: char,
842-
pub styles: CharacterStyles,
917+
pub styles: RcCharacterStyles,
843918
width: u8,
844919
}
845920

846921
impl TerminalCharacter {
847922
#[inline]
848923
pub fn new(character: char) -> Self {
849-
Self::new_styled(character, CharacterStyles::default())
924+
Self::new_styled(character, Default::default())
850925
}
851926

852927
#[inline]
853-
pub fn new_styled(character: char, styles: CharacterStyles) -> Self {
928+
pub fn new_styled(character: char, styles: RcCharacterStyles) -> Self {
854929
TerminalCharacter {
855930
character,
856931
styles,
@@ -860,11 +935,11 @@ impl TerminalCharacter {
860935

861936
#[inline]
862937
pub fn new_singlewidth(character: char) -> Self {
863-
Self::new_singlewidth_styled(character, CharacterStyles::default())
938+
Self::new_singlewidth_styled(character, Default::default())
864939
}
865940

866941
#[inline]
867-
pub fn new_singlewidth_styled(character: char, styles: CharacterStyles) -> Self {
942+
pub fn new_singlewidth_styled(character: char, styles: RcCharacterStyles) -> Self {
868943
TerminalCharacter {
869944
character,
870945
styles,

zellij-server/src/panes/terminal_pane.rs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -427,8 +427,10 @@ impl Pane for TerminalPane {
427427
.grid
428428
.get_character_under_cursor()
429429
.unwrap_or(EMPTY_TERMINAL_CHARACTER);
430-
character_under_cursor.styles.background = Some(cursor_color.into());
431-
character_under_cursor.styles.foreground = Some(text_color.into());
430+
character_under_cursor.styles.update(|styles| {
431+
styles.background = Some(cursor_color.into());
432+
styles.foreground = Some(text_color.into());
433+
});
432434
// we keep track of these so that we can clear them up later (see render function)
433435
self.fake_cursor_locations.insert((cursor_y, cursor_x));
434436
let mut fake_cursor = format!(

zellij-server/src/ui/boundaries.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,8 @@ impl BoundarySymbol {
6666
TerminalCharacter::new_singlewidth_styled(
6767
character,
6868
RESET_STYLES
69-
.foreground(self.color.map(|palette_color| palette_color.into())),
69+
.foreground(self.color.map(|palette_color| palette_color.into()))
70+
.into(),
7071
)
7172
};
7273
Ok(tc)

0 commit comments

Comments
 (0)