diff options
| author | Ogulcan Celik <ogulcancelik@gmail.com> | 2026-06-02 02:27:14 +0300 |
|---|---|---|
| committer | Ogulcan Celik <ogulcancelik@gmail.com> | 2026-06-02 02:31:12 +0300 |
| commit | cc527d45fe4821cb11c484372a9f25c51c02e078 (patch) | |
| tree | 2e3411b39576b0910eba75e2b5b77c5e4e398e74 /src/protocol | |
| parent | f7d315d494b6a3d583574012526d15c1ddc82bfe (diff) | |
perf: retain terminal render updates
Diffstat (limited to 'src/protocol')
| -rw-r--r-- | src/protocol/render_ansi.rs | 88 | ||||
| -rw-r--r-- | src/protocol/wire.rs | 4 |
2 files changed, 90 insertions, 2 deletions
diff --git a/src/protocol/render_ansi.rs b/src/protocol/render_ansi.rs index 8caa98d0..1c28b00b 100644 --- a/src/protocol/render_ansi.rs +++ b/src/protocol/render_ansi.rs @@ -63,6 +63,9 @@ impl BlitEncoder { let full = force_full || prev.is_none() || prev.is_some_and(|p| p.width != frame.width || p.height != frame.height); + let prof_stats = + crate::render_prof::enabled().then(|| compute_prof_blit_stats(frame, prev, full)); + let prof_started = crate::render_prof::timer(); let mut bytes = Vec::new(); let mut next_last_visible_cursor = self.last_visible_cursor; let mut next_last_cursor_shape = self.last_cursor_shape; @@ -73,6 +76,18 @@ impl BlitEncoder { &mut next_last_visible_cursor, &mut next_last_cursor_shape, ); + if let Some(stats) = prof_stats { + crate::render_prof::duration_since("ansi_encode.total", prof_started); + crate::render_prof::counter("ansi_encode.bytes", bytes.len() as u64); + crate::render_prof::counter("ansi_encode.scanned_cells", stats.scanned_cells); + crate::render_prof::counter("ansi_encode.changed_cells", stats.changed_cells); + crate::render_prof::counter("ansi_encode.changed_runs", stats.changed_runs); + if full { + crate::render_prof::event("ansi_encode.full"); + } else { + crate::render_prof::event("ansi_encode.partial"); + } + } EncodedBlit { bytes, full, @@ -90,6 +105,79 @@ impl BlitEncoder { pub(crate) fn is_current(&self, frame: &FrameData) -> bool { self.last_frame.as_ref() == Some(frame) } + + pub(crate) fn last_frame(&self) -> Option<&FrameData> { + self.last_frame.as_ref() + } +} + +#[derive(Clone, Copy, Default)] +struct ProfBlitStats { + scanned_cells: u64, + changed_cells: u64, + changed_runs: u64, +} + +fn compute_prof_blit_stats( + frame: &FrameData, + prev: Option<&FrameData>, + full: bool, +) -> ProfBlitStats { + let Some(prev) = prev.filter(|_| !full) else { + let changed_cells = frame.cells.iter().filter(|cell| !cell.skip).count() as u64; + return ProfBlitStats { + scanned_cells: frame.cells.len() as u64, + changed_cells, + changed_runs: changed_cells, + }; + }; + if prev.width != frame.width || prev.height != frame.height { + let changed_cells = frame.cells.iter().filter(|cell| !cell.skip).count() as u64; + return ProfBlitStats { + scanned_cells: frame.cells.len() as u64, + changed_cells, + changed_runs: changed_cells, + }; + } + + let sanitized_hyperlinks = sanitized_frame_hyperlinks(frame); + let prev_sanitized_hyperlinks = sanitized_frame_hyperlinks(prev); + let mut stats = ProfBlitStats { + scanned_cells: frame.cells.len() as u64, + changed_cells: 0, + changed_runs: 0, + }; + for row in 0..frame.height { + let mut in_run = false; + let mut invalidated = 0usize; + let mut to_skip = 0usize; + for col in 0..frame.width { + let idx = (row as usize) * (frame.width as usize) + (col as usize); + let cell = &frame.cells[idx]; + let prev_cell = &prev.cells[idx]; + let changed = !cell.skip + && (!cells_visually_equal( + &sanitized_hyperlinks, + cell, + &prev_sanitized_hyperlinks, + prev_cell, + ) || invalidated > 0) + && to_skip == 0; + if changed { + stats.changed_cells += 1; + if !in_run { + stats.changed_runs += 1; + in_run = true; + } + } else { + in_run = false; + } + to_skip = cell_width(cell).saturating_sub(1); + let affected_width = cmp::max(cell_width(cell), cell_width(prev_cell)); + invalidated = cmp::max(affected_width, invalidated).saturating_sub(1); + } + } + stats } // --------------------------------------------------------------------------- diff --git a/src/protocol/wire.rs b/src/protocol/wire.rs index 576c4135..a64a06d5 100644 --- a/src/protocol/wire.rs +++ b/src/protocol/wire.rs @@ -401,7 +401,7 @@ pub enum ServerMessage { /// - Named colors (Reset, Black, …, White) → `0x00_00_00_XX` where XX is 0..=16 /// - Indexed palette → `0x01_00_00_XX` where XX is the palette index /// - RGB → `0x02_RR_GG_BB` with components in the lower 3 bytes -fn color_to_u32(color: ratatui::style::Color) -> u32 { +pub(crate) fn color_to_u32(color: ratatui::style::Color) -> u32 { match color { ratatui::style::Color::Reset => 0x00_00_00_00, ratatui::style::Color::Black => 0x00_00_00_01, @@ -463,7 +463,7 @@ fn u32_to_color(val: u32) -> ratatui::style::Color { } /// Converts a ratatui `Modifier` bitmask to a u16 for wire transport. -fn modifier_to_u16(modifier: ratatui::style::Modifier) -> u16 { +pub(crate) fn modifier_to_u16(modifier: ratatui::style::Modifier) -> u16 { modifier.bits() } |
