Skip to content

Commit ec42b42

Browse files
authored
Use dynamic storage for zerowidth characters
The zerowidth characters were conventionally stored in a [char; 5]. This creates problems both by limiting the maximum number of zerowidth characters and by increasing the cell size beyond what is necessary even when no zerowidth characters are used. Instead of storing zerowidth characters as a slice, a new CellExtra struct is introduced which can store arbitrary optional cell data that is rarely required. Since this is stored behind an optional pointer (Option<Box<CellExtra>>), the initialization and dropping in the case of no extra data are extremely cheap and the size penalty to cells without this extra data is limited to 8 instead of 20 bytes. The most noticible difference with this PR should be a reduction in memory size of up to at least 30% (1.06G -> 733M, 100k scrollback, 72 lines, 280 columns). Since the zerowidth characters are now stored dynamically, the limit of 5 per cell is also no longer present.
1 parent 9028fb4 commit ec42b42

File tree

60 files changed

+629
-598
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

60 files changed

+629
-598
lines changed

Diff for: CHANGELOG.md

+2
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
3636
- Use yellow/red from the config for error and warning messages instead of fixed colors
3737
- Existing CLI parameters are now passed to instances spawned using `SpawnNewInstance`
3838
- Wayland's Client side decorations now use the search bar colors
39+
- Reduce memory usage by up to at least 30% with a full scrollback buffer
40+
- The number of zerowidth characters per cell is no longer limited to 5
3941

4042
### Fixed
4143

Diff for: alacritty/src/display.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -473,10 +473,10 @@ impl Display {
473473
// Iterate over all non-empty cells in the grid.
474474
for cell in grid_cells {
475475
// Update URL underlines.
476-
urls.update(size_info.cols(), cell);
476+
urls.update(size_info.cols(), &cell);
477477

478478
// Update underline/strikeout.
479-
lines.update(cell);
479+
lines.update(&cell);
480480

481481
// Draw the cell.
482482
api.render_cell(cell, glyph_cache);

Diff for: alacritty/src/event.rs

+1-2
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,6 @@ use alacritty_terminal::grid::{Dimensions, Scroll};
3434
use alacritty_terminal::index::{Boundary, Column, Direction, Line, Point, Side};
3535
use alacritty_terminal::selection::{Selection, SelectionType};
3636
use alacritty_terminal::sync::FairMutex;
37-
use alacritty_terminal::term::cell::Cell;
3837
use alacritty_terminal::term::{ClipboardType, SizeInfo, Term, TermMode};
3938
#[cfg(not(windows))]
4039
use alacritty_terminal::tty;
@@ -1174,7 +1173,7 @@ impl<N: Notify + OnResize> Processor<N> {
11741173
fn write_ref_test_results<T>(&self, terminal: &Term<T>) {
11751174
// Dump grid state.
11761175
let mut grid = terminal.grid().clone();
1177-
grid.initialize_all(Cell::default());
1176+
grid.initialize_all();
11781177
grid.truncate();
11791178

11801179
let serialized_grid = json::to_string(&grid).expect("serialize grid");

Diff for: alacritty/src/renderer/mod.rs

+30-38
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ use notify::{watcher, DebouncedEvent, RecursiveMode, Watcher};
1919

2020
use alacritty_terminal::config::Cursor;
2121
use alacritty_terminal::index::{Column, Line};
22-
use alacritty_terminal::term::cell::{self, Flags};
22+
use alacritty_terminal::term::cell::Flags;
2323
use alacritty_terminal::term::color::Rgb;
2424
use alacritty_terminal::term::{CursorKey, RenderableCell, RenderableCellContent, SizeInfo};
2525
use alacritty_terminal::thread;
@@ -436,7 +436,7 @@ impl Batch {
436436
Self { tex: 0, instances: Vec::with_capacity(BATCH_MAX) }
437437
}
438438

439-
pub fn add_item(&mut self, cell: RenderableCell, glyph: &Glyph) {
439+
pub fn add_item(&mut self, cell: &RenderableCell, glyph: &Glyph) {
440440
if self.is_empty() {
441441
self.tex = glyph.tex_id;
442442
}
@@ -953,11 +953,7 @@ impl<'a> RenderApi<'a> {
953953
.map(|(i, c)| RenderableCell {
954954
line,
955955
column: Column(i),
956-
inner: RenderableCellContent::Chars({
957-
let mut chars = [' '; cell::MAX_ZEROWIDTH_CHARS + 1];
958-
chars[0] = c;
959-
chars
960-
}),
956+
inner: RenderableCellContent::Chars((c, None)),
961957
flags: Flags::empty(),
962958
bg_alpha,
963959
fg,
@@ -971,7 +967,7 @@ impl<'a> RenderApi<'a> {
971967
}
972968

973969
#[inline]
974-
fn add_render_item(&mut self, cell: RenderableCell, glyph: &Glyph) {
970+
fn add_render_item(&mut self, cell: &RenderableCell, glyph: &Glyph) {
975971
// Flush batch if tex changing.
976972
if !self.batch.is_empty() && self.batch.tex != glyph.tex_id {
977973
self.render_batch();
@@ -985,8 +981,8 @@ impl<'a> RenderApi<'a> {
985981
}
986982
}
987983

988-
pub fn render_cell(&mut self, cell: RenderableCell, glyph_cache: &mut GlyphCache) {
989-
let chars = match cell.inner {
984+
pub fn render_cell(&mut self, mut cell: RenderableCell, glyph_cache: &mut GlyphCache) {
985+
let (mut c, zerowidth) = match cell.inner {
990986
RenderableCellContent::Cursor(cursor_key) => {
991987
// Raw cell pixel buffers like cursors don't need to go through font lookup.
992988
let metrics = glyph_cache.metrics;
@@ -1000,10 +996,10 @@ impl<'a> RenderApi<'a> {
1000996
self.cursor_config.thickness(),
1001997
))
1002998
});
1003-
self.add_render_item(cell, glyph);
999+
self.add_render_item(&cell, glyph);
10041000
return;
10051001
},
1006-
RenderableCellContent::Chars(chars) => chars,
1002+
RenderableCellContent::Chars((c, ref mut zerowidth)) => (c, zerowidth.take()),
10071003
};
10081004

10091005
// Get font key for cell.
@@ -1014,37 +1010,33 @@ impl<'a> RenderApi<'a> {
10141010
_ => glyph_cache.font_key,
10151011
};
10161012

1017-
// Don't render text of HIDDEN cells.
1018-
let mut chars = if cell.flags.contains(Flags::HIDDEN) {
1019-
[' '; cell::MAX_ZEROWIDTH_CHARS + 1]
1020-
} else {
1021-
chars
1022-
};
1023-
1024-
// Render tabs as spaces in case the font doesn't support it.
1025-
if chars[0] == '\t' {
1026-
chars[0] = ' ';
1013+
// Ignore hidden cells and render tabs as spaces to prevent font issues.
1014+
let hidden = cell.flags.contains(Flags::HIDDEN);
1015+
if c == '\t' || hidden {
1016+
c = ' ';
10271017
}
10281018

1029-
let mut glyph_key = GlyphKey { font_key, size: glyph_cache.font_size, c: chars[0] };
1019+
let mut glyph_key = GlyphKey { font_key, size: glyph_cache.font_size, c };
10301020

10311021
// Add cell to batch.
10321022
let glyph = glyph_cache.get(glyph_key, self);
1033-
self.add_render_item(cell, glyph);
1034-
1035-
// Render zero-width characters.
1036-
for c in (&chars[1..]).iter().filter(|c| **c != ' ') {
1037-
glyph_key.c = *c;
1038-
let mut glyph = *glyph_cache.get(glyph_key, self);
1039-
1040-
// The metrics of zero-width characters are based on rendering
1041-
// the character after the current cell, with the anchor at the
1042-
// right side of the preceding character. Since we render the
1043-
// zero-width characters inside the preceding character, the
1044-
// anchor has been moved to the right by one cell.
1045-
glyph.left += glyph_cache.metrics.average_advance as i16;
1046-
1047-
self.add_render_item(cell, &glyph);
1023+
self.add_render_item(&cell, glyph);
1024+
1025+
// Render visible zero-width characters.
1026+
if let Some(zerowidth) = zerowidth.filter(|_| !hidden) {
1027+
for c in zerowidth {
1028+
glyph_key.c = c;
1029+
let mut glyph = *glyph_cache.get(glyph_key, self);
1030+
1031+
// The metrics of zero-width characters are based on rendering
1032+
// the character after the current cell, with the anchor at the
1033+
// right side of the preceding character. Since we render the
1034+
// zero-width characters inside the preceding character, the
1035+
// anchor has been moved to the right by one cell.
1036+
glyph.left += glyph_cache.metrics.average_advance as i16;
1037+
1038+
self.add_render_item(&cell, &glyph);
1039+
}
10481040
}
10491041
}
10501042
}

Diff for: alacritty/src/renderer/rects.rs

+5-5
Original file line numberDiff line numberDiff line change
@@ -150,14 +150,14 @@ impl RenderLines {
150150

151151
/// Update the stored lines with the next cell info.
152152
#[inline]
153-
pub fn update(&mut self, cell: RenderableCell) {
154-
self.update_flag(cell, Flags::UNDERLINE);
155-
self.update_flag(cell, Flags::DOUBLE_UNDERLINE);
156-
self.update_flag(cell, Flags::STRIKEOUT);
153+
pub fn update(&mut self, cell: &RenderableCell) {
154+
self.update_flag(&cell, Flags::UNDERLINE);
155+
self.update_flag(&cell, Flags::DOUBLE_UNDERLINE);
156+
self.update_flag(&cell, Flags::STRIKEOUT);
157157
}
158158

159159
/// Update the lines for a specific flag.
160-
fn update_flag(&mut self, cell: RenderableCell, flag: Flags) {
160+
fn update_flag(&mut self, cell: &RenderableCell, flag: Flags) {
161161
if !cell.flags.contains(flag) {
162162
return;
163163
}

Diff for: alacritty/src/url.rs

+10-12
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ impl Url {
4848
pub struct Urls {
4949
locator: UrlLocator,
5050
urls: Vec<Url>,
51-
scheme_buffer: Vec<RenderableCell>,
51+
scheme_buffer: Vec<(Point, Rgb)>,
5252
last_point: Option<Point>,
5353
state: UrlLocation,
5454
}
@@ -71,10 +71,10 @@ impl Urls {
7171
}
7272

7373
// Update tracked URLs.
74-
pub fn update(&mut self, num_cols: Column, cell: RenderableCell) {
74+
pub fn update(&mut self, num_cols: Column, cell: &RenderableCell) {
7575
// Convert cell to character.
76-
let c = match cell.inner {
77-
RenderableCellContent::Chars(chars) => chars[0],
76+
let c = match &cell.inner {
77+
RenderableCellContent::Chars((c, _zerowidth)) => *c,
7878
RenderableCellContent::Cursor(_) => return,
7979
};
8080

@@ -109,9 +109,8 @@ impl Urls {
109109
self.urls.push(Url { lines: Vec::new(), end_offset, num_cols });
110110

111111
// Push schemes into URL.
112-
for scheme_cell in self.scheme_buffer.split_off(0) {
113-
let point = scheme_cell.into();
114-
self.extend_url(point, point, scheme_cell.fg, end_offset);
112+
for (scheme_point, scheme_fg) in self.scheme_buffer.split_off(0) {
113+
self.extend_url(scheme_point, scheme_point, scheme_fg, end_offset);
115114
}
116115

117116
// Push the new cell into URL.
@@ -120,7 +119,7 @@ impl Urls {
120119
(UrlLocation::Url(_length, end_offset), UrlLocation::Url(..)) => {
121120
self.extend_url(point, end, cell.fg, end_offset);
122121
},
123-
(UrlLocation::Scheme, _) => self.scheme_buffer.push(cell),
122+
(UrlLocation::Scheme, _) => self.scheme_buffer.push((cell.into(), cell.fg)),
124123
(UrlLocation::Reset, _) => self.reset(),
125124
_ => (),
126125
}
@@ -196,13 +195,12 @@ mod tests {
196195
use super::*;
197196

198197
use alacritty_terminal::index::{Column, Line};
199-
use alacritty_terminal::term::cell::MAX_ZEROWIDTH_CHARS;
200198

201199
fn text_to_cells(text: &str) -> Vec<RenderableCell> {
202200
text.chars()
203201
.enumerate()
204202
.map(|(i, c)| RenderableCell {
205-
inner: RenderableCellContent::Chars([c; MAX_ZEROWIDTH_CHARS + 1]),
203+
inner: RenderableCellContent::Chars((c, None)),
206204
line: Line(0),
207205
column: Column(i),
208206
fg: Default::default(),
@@ -223,7 +221,7 @@ mod tests {
223221
let mut urls = Urls::new();
224222

225223
for cell in input {
226-
urls.update(Column(num_cols), cell);
224+
urls.update(Column(num_cols), &cell);
227225
}
228226

229227
let url = urls.urls.first().unwrap();
@@ -239,7 +237,7 @@ mod tests {
239237
let mut urls = Urls::new();
240238

241239
for cell in input {
242-
urls.update(Column(num_cols), cell);
240+
urls.update(Column(num_cols), &cell);
243241
}
244242

245243
assert_eq!(urls.urls.len(), 3);

0 commit comments

Comments
 (0)