Skip to content

Commit 3cfa4bc

Browse files
authored
perf(tui2): reduce unnecessary redraws (#8681)
This reduces unnecessary frame scheduling in codex-tui2. Changes: - Gate redraw scheduling for streaming deltas when nothing visible changes. - Avoid a redraw feedback loop from footer transcript UI state updates. Why: - Streaming deltas can arrive at very high frequency; redrawing on every delta can drive a near-constant render loop. - BottomPane was requesting another frame after every Draw even when the derived transcript UI state was unchanged. Testing: - cargo test -p codex-tui2 Manual sampling: - sample "$(pgrep -n codex-tui2)" 3 -file /tmp/tui2.idle.after.sample.txt - sample "$(pgrep -n codex-tui2)" 3 -file /tmp/tui2.streaming.after.sample.txt
1 parent ab75338 commit 3cfa4bc

File tree

3 files changed

+20
-4
lines changed

3 files changed

+20
-4
lines changed

codex-rs/tui2/src/bottom_pane/chat_composer.rs

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1577,11 +1577,20 @@ impl ChatComposer {
15771577
selection_active: bool,
15781578
scroll_position: Option<(usize, usize)>,
15791579
copy_selection_key: KeyBinding,
1580-
) {
1580+
) -> bool {
1581+
if self.transcript_scrolled == scrolled
1582+
&& self.transcript_selection_active == selection_active
1583+
&& self.transcript_scroll_position == scroll_position
1584+
&& self.transcript_copy_selection_key == copy_selection_key
1585+
{
1586+
return false;
1587+
}
1588+
15811589
self.transcript_scrolled = scrolled;
15821590
self.transcript_selection_active = selection_active;
15831591
self.transcript_scroll_position = scroll_position;
15841592
self.transcript_copy_selection_key = copy_selection_key;
1593+
true
15851594
}
15861595

15871596
fn sync_popups(&mut self) {

codex-rs/tui2/src/bottom_pane/mod.rs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -389,13 +389,15 @@ impl BottomPane {
389389
scroll_position: Option<(usize, usize)>,
390390
copy_selection_key: crate::key_hint::KeyBinding,
391391
) {
392-
self.composer.set_transcript_ui_state(
392+
let updated = self.composer.set_transcript_ui_state(
393393
scrolled,
394394
selection_active,
395395
scroll_position,
396396
copy_selection_key,
397397
);
398-
self.request_redraw();
398+
if updated {
399+
self.request_redraw();
400+
}
399401
}
400402

401403
/// Show a generic list selection view with the provided items.

codex-rs/tui2/src/chatwidget.rs

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -968,6 +968,7 @@ impl ChatWidget {
968968
if let Some(cell) = cell {
969969
self.bottom_pane.hide_status_indicator();
970970
self.add_boxed_history(cell);
971+
self.request_redraw();
971972
}
972973
if is_idle {
973974
self.app_event_tx.send(AppEvent::StopCommitAnimation);
@@ -1009,6 +1010,7 @@ impl ChatWidget {
10091010
#[inline]
10101011
fn handle_streaming_delta(&mut self, delta: String) {
10111012
// Before streaming agent content, flush any active exec cell group.
1013+
let mut needs_redraw = self.active_cell.is_some();
10121014
self.flush_active_cell();
10131015

10141016
if self.stream_controller.is_none() {
@@ -1019,6 +1021,7 @@ impl ChatWidget {
10191021
.map(super::status_indicator_widget::StatusIndicatorWidget::elapsed_seconds);
10201022
self.add_to_history(history_cell::FinalMessageSeparator::new(elapsed_seconds));
10211023
self.needs_final_message_separator = false;
1024+
needs_redraw = true;
10221025
}
10231026
self.stream_controller = Some(StreamController::new(
10241027
self.last_rendered_width.get().map(|w| w.saturating_sub(2)),
@@ -1029,7 +1032,9 @@ impl ChatWidget {
10291032
{
10301033
self.app_event_tx.send(AppEvent::StartCommitAnimation);
10311034
}
1032-
self.request_redraw();
1035+
if needs_redraw {
1036+
self.request_redraw();
1037+
}
10331038
}
10341039

10351040
pub(crate) fn handle_exec_end_now(&mut self, ev: ExecCommandEndEvent) {

0 commit comments

Comments
 (0)