diff --git a/src/bin/consciousness.rs b/src/bin/consciousness.rs index ab49e92..f9b5220 100644 --- a/src/bin/consciousness.rs +++ b/src/bin/consciousness.rs @@ -909,9 +909,10 @@ async fn run(cli: cli::CliArgs) -> Result<()> { // Crossterm event stream let mut reader = EventStream::new(); - // Render timer: 20fps + // Render timer — only draws when dirty let mut render_interval = tokio::time::interval(Duration::from_millis(50)); render_interval.set_missed_tick_behavior(tokio::time::MissedTickBehavior::Skip); + let mut dirty = true; // draw first frame // Hide terminal cursor — tui-textarea renders its own cursor as a styled cell terminal.hide_cursor()?; @@ -934,13 +935,16 @@ async fn run(cli: cli::CliArgs) -> Result<()> { continue; } app.handle_key(key); + dirty = true; } Some(Ok(Event::Mouse(mouse))) => { app.handle_mouse(mouse); + dirty = true; } Some(Ok(Event::Resize(w, h))) => { app.handle_resize(w, h); terminal.clear()?; + dirty = true; } Some(Err(_)) => break, None => break, @@ -951,14 +955,16 @@ async fn run(cli: cli::CliArgs) -> Result<()> { // Input from observation socket clients Some(line) = observe_input_rx.recv() => { app.submitted.push(line); + dirty = true; } // Turn completed in background task Some((result, target)) = turn_rx.recv() => { session.handle_turn_result(result, target).await; + dirty = true; } - // Render tick + // Render tick — only redraws if dirty _ = render_interval.tick() => { app.running_processes = session.process_tracker.list().await.len() as u32; } @@ -966,11 +972,13 @@ async fn run(cli: cli::CliArgs) -> Result<()> { // DMN timer (only when no turn is running) _ = tokio::time::sleep(timeout), if !session.turn_in_progress => { session.dmn_tick(); + dirty = true; } // UI messages (lowest priority — processed in bulk during render) Some(msg) = ui_rx.recv() => { app.handle_ui_message(msg); + dirty = true; } } @@ -999,9 +1007,16 @@ async fn run(cli: cli::CliArgs) -> Result<()> { } } - // Drain pending UI messages and redraw - drain_ui_messages(&mut ui_rx, &mut app); - terminal.draw(|f| app.draw(f))?; + // Drain pending UI messages + if drain_ui_messages(&mut ui_rx, &mut app) { + dirty = true; + } + + // Only redraw when something changed + if dirty { + terminal.draw(|f| app.draw(f))?; + dirty = false; + } if app.should_quit { break; @@ -1015,10 +1030,13 @@ async fn run(cli: cli::CliArgs) -> Result<()> { // --- Free functions --- -fn drain_ui_messages(rx: &mut ui_channel::UiReceiver, app: &mut tui::App) { +fn drain_ui_messages(rx: &mut ui_channel::UiReceiver, app: &mut tui::App) -> bool { + let mut any = false; while let Ok(msg) = rx.try_recv() { app.handle_ui_message(msg); + any = true; } + any } async fn run_tool_tests(ui_tx: &ui_channel::UiSender, tracker: &tools::ProcessTracker) {