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
This commit is contained in:
Josh McKinney 2026-01-02 12:15:58 -08:00 committed by GitHub
parent ab753387cc
commit 3cfa4bc8be
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 20 additions and 4 deletions

View file

@ -1577,11 +1577,20 @@ impl ChatComposer {
selection_active: bool,
scroll_position: Option<(usize, usize)>,
copy_selection_key: KeyBinding,
) {
) -> bool {
if self.transcript_scrolled == scrolled
&& self.transcript_selection_active == selection_active
&& self.transcript_scroll_position == scroll_position
&& self.transcript_copy_selection_key == copy_selection_key
{
return false;
}
self.transcript_scrolled = scrolled;
self.transcript_selection_active = selection_active;
self.transcript_scroll_position = scroll_position;
self.transcript_copy_selection_key = copy_selection_key;
true
}
fn sync_popups(&mut self) {

View file

@ -389,13 +389,15 @@ impl BottomPane {
scroll_position: Option<(usize, usize)>,
copy_selection_key: crate::key_hint::KeyBinding,
) {
self.composer.set_transcript_ui_state(
let updated = self.composer.set_transcript_ui_state(
scrolled,
selection_active,
scroll_position,
copy_selection_key,
);
self.request_redraw();
if updated {
self.request_redraw();
}
}
/// Show a generic list selection view with the provided items.

View file

@ -968,6 +968,7 @@ impl ChatWidget {
if let Some(cell) = cell {
self.bottom_pane.hide_status_indicator();
self.add_boxed_history(cell);
self.request_redraw();
}
if is_idle {
self.app_event_tx.send(AppEvent::StopCommitAnimation);
@ -1009,6 +1010,7 @@ impl ChatWidget {
#[inline]
fn handle_streaming_delta(&mut self, delta: String) {
// Before streaming agent content, flush any active exec cell group.
let mut needs_redraw = self.active_cell.is_some();
self.flush_active_cell();
if self.stream_controller.is_none() {
@ -1019,6 +1021,7 @@ impl ChatWidget {
.map(super::status_indicator_widget::StatusIndicatorWidget::elapsed_seconds);
self.add_to_history(history_cell::FinalMessageSeparator::new(elapsed_seconds));
self.needs_final_message_separator = false;
needs_redraw = true;
}
self.stream_controller = Some(StreamController::new(
self.last_rendered_width.get().map(|w| w.saturating_sub(2)),
@ -1029,7 +1032,9 @@ impl ChatWidget {
{
self.app_event_tx.send(AppEvent::StartCommitAnimation);
}
self.request_redraw();
if needs_redraw {
self.request_redraw();
}
}
pub(crate) fn handle_exec_end_now(&mut self, ev: ExecCommandEndEvent) {