use super::*;
#[derive(Debug, Default)]
pub(super) struct DrainLoopBudgetState {
draw_before_next_drain: bool,
pub(super) continue_after_pending_draw: bool,
input_paint_barrier: bool,
#[cfg(any(test, debug_assertions))]
pub(super) input_paint_started: Option<Instant>,
}
impl DrainLoopBudgetState {
pub(super) fn should_drain_at_loop_top(&self) -> bool {
!self.draw_before_next_drain
}
pub(super) fn defer_animation_draw_for_queued_input(
&mut self,
animation_only: bool,
input_queued: bool,
startup_paint_pending: bool,
) -> bool {
if !animation_only
|| !input_queued
|| startup_paint_pending
|| self.draw_before_next_drain
|| self.input_paint_barrier
{
return false;
}
self.input_paint_barrier = true;
true
}
pub(super) fn request_input_paint(
&mut self,
redraw_intent: &mut RedrawIntent,
perf_enabled: bool,
input_received_at: Instant,
) {
self.input_paint_barrier = true;
#[cfg(any(test, debug_assertions))]
if perf_enabled {
let earliest = self
.input_paint_started
.map_or(input_received_at, |started| started.min(input_received_at));
self.input_paint_started = Some(earliest);
}
#[cfg(not(any(test, debug_assertions)))]
let _ = (perf_enabled, input_received_at);
redraw_intent.request_immediate();
}
pub(super) fn input_paint_barrier_pending(&self) -> bool {
self.input_paint_barrier
}
#[cfg(any(test, debug_assertions))]
pub(super) fn take_input_paint_started(&mut self) -> Option<Instant> {
self.input_paint_started.take()
}
pub(super) fn apply_outcome(&mut self, outcome: &DrainOutcome) -> bool {
let changed = outcome.result.changed;
if outcome.exhausted && changed {
self.draw_before_next_drain = true;
self.continue_after_pending_draw = true;
}
changed
}
pub(super) fn after_draw(&mut self) -> bool {
self.draw_before_next_drain = false;
self.input_paint_barrier = false;
#[cfg(any(test, debug_assertions))]
{
self.input_paint_started = None;
}
std::mem::take(&mut self.continue_after_pending_draw)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(super) enum PostDrainFlow {
Continue,
Exit,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(super) struct PostDrainProcessing {
pub(super) flow: PostDrainFlow,
pub(super) redraw_requested: bool,
pub(super) skip_loop: bool,
}
pub(super) struct PostDrainContext<'a> {
pub(super) receiver: &'a Receiver<TuiEvent>,
pub(super) input_events: &'a TerminalInputBridge,
pub(super) terminal_area: ratatui::layout::Rect,
pub(super) pending_input_events: &'a mut VecDeque<TerminalInputEvent>,
pub(super) drain_budget_state: &'a mut DrainLoopBudgetState,
pub(super) finish_completed_worker: bool,
}
impl MissionControlApp {
pub(super) fn process_post_drain(
&mut self,
ui_state: &mut state::MissionControlState,
drain_outcome: &mut DrainOutcome,
autocomplete_candidates: &mut Vec<state::AutocompleteCandidate>,
context: PostDrainContext<'_>,
) -> Result<PostDrainProcessing> {
let mut redraw_requested = false;
redraw_requested |= self.reap_session_maintenance(ui_state);
redraw_requested |= self.reap_auth_worker(ui_state);
redraw_requested |= self.reconcile_system_prompt_worker(ui_state);
redraw_requested |= self.reap_panel_layout_persistence(ui_state);
if self.reap_settings_persistence_worker(ui_state) {
refresh_skill_autocomplete_candidates(autocomplete_candidates, &self.skills);
if self.settings_persistence.take_runtime_refresh_requested() {
refresh_file_autocomplete_candidates(
autocomplete_candidates,
&self.state.cwd,
self.config.file_autocomplete_respects_gitignore,
);
ui_state.recompute_autocomplete(autocomplete_candidates);
}
redraw_requested = true;
}
if self.reap_rewind_workers(ui_state) {
redraw_requested = true;
}
if self.handle_rewind_drain(ui_state, &drain_outcome.result) {
redraw_requested = true;
}
if self.reap_export_workers(ui_state) {
redraw_requested = true;
}
if self.handle_export_drain(ui_state, &drain_outcome.result) {
redraw_requested = true;
}
if self.reap_theme_workers(ui_state) {
redraw_requested = true;
}
if self.reap_fast_mode_persistence_workers(ui_state) {
redraw_requested = true;
}
if self.handle_fast_mode_persistence_drain(ui_state, &drain_outcome.result) {
redraw_requested = true;
}
if self.reap_model_selection_workers(ui_state) {
redraw_requested = true;
}
if self.handle_model_selection_drain(ui_state, &drain_outcome.result) {
redraw_requested = true;
}
for refresh in std::mem::take(&mut drain_outcome.result.compaction_runtime_refresh) {
if self.apply_compaction_runtime_refresh(ui_state, refresh) {
redraw_requested = true;
}
}
if self.handle_model_catalog_drain(ui_state, &drain_outcome.result) {
redraw_requested = true;
}
for (request_id, result) in std::mem::take(&mut drain_outcome.result.theme_catalog_loaded) {
if self.handle_theme_catalog(ui_state, request_id, result) {
redraw_requested = true;
}
}
for (request_id, id, revision, result) in
std::mem::take(&mut drain_outcome.result.theme_persisted)
{
if self.handle_theme_persisted(ui_state, request_id, id, revision, result) {
redraw_requested = true;
}
}
for (provider_id, provider_result) in
std::mem::take(&mut drain_outcome.result.custom_provider_finished)
{
self.reconcile_worker_final_event_effect(
ui_state,
WorkerFinalEventEffect::CustomProviderFinished {
provider_id,
result: provider_result,
},
);
redraw_requested = true;
}
let critical_result_available = self.startup_readiness == StartupReadiness::Loading
&& (!drain_outcome.result.startup_critical_loaded.is_empty()
|| !self.deferred_startup_critical_loaded.is_empty());
if critical_result_available && self.startup_input_boundary.is_none() {
self.startup_input_boundary = Some(StartupInputBoundary::capture(
context.pending_input_events.len(),
context.input_events.receiver.len(),
));
}
let startup_input_boundary_open = self
.startup_input_boundary
.is_some_and(|boundary| !boundary.is_complete());
if startup_input_boundary_open {
self.deferred_startup_critical_loaded
.append(&mut drain_outcome.result.startup_critical_loaded);
} else if !self.deferred_startup_critical_loaded.is_empty() {
let deferred = std::mem::take(&mut self.deferred_startup_critical_loaded);
drain_outcome
.result
.startup_critical_loaded
.splice(0..0, deferred);
}
self.request_initial_prompt_flush(context.input_events)?;
match self.submit_initial_prompt_if_ready(
ui_state,
context.receiver,
context.terminal_area,
&context.input_events.receiver,
context.pending_input_events,
)? {
Some(actions::AppControlFlow::Exit) => {
return Ok(PostDrainProcessing {
flow: PostDrainFlow::Exit,
redraw_requested,
skip_loop: false,
});
}
Some(actions::AppControlFlow::Continue) => redraw_requested = true,
None => {}
}
if let Some(error) = self.startup_failure.clone() {
return Err(anyhow::anyhow!(error));
}
if self.handle_startup_drain(ui_state, &mut drain_outcome.result, autocomplete_candidates) {
redraw_requested = true;
}
if self.startup_readiness != StartupReadiness::Loading {
self.startup_input_boundary = None;
}
self.check_startup_worker_health(ui_state);
if let Some(error) = self.startup_failure.clone() {
return Err(anyhow::anyhow!(error));
}
if self.handle_background_session_drain(ui_state, &drain_outcome.result, context.receiver) {
redraw_requested = true;
}
redraw_requested |= self.poll_summarizer(ui_state);
self.request_initial_prompt_flush(context.input_events)?;
match self.submit_initial_prompt_if_ready(
ui_state,
context.receiver,
context.terminal_area,
&context.input_events.receiver,
context.pending_input_events,
)? {
Some(actions::AppControlFlow::Exit) => {
return Ok(PostDrainProcessing {
flow: PostDrainFlow::Exit,
redraw_requested,
skip_loop: false,
});
}
Some(actions::AppControlFlow::Continue) => redraw_requested = true,
None => {}
}
if let Some(error) = self.startup_failure.clone() {
return Err(anyhow::anyhow!(error));
}
if self.sync_steering_feedback(ui_state) {
redraw_requested = true;
}
let run_finished_worker_ids = drain_outcome.result.run_finished_worker_ids.clone();
let model_catalog_finished = drain_outcome.result.model_catalog_finished;
for worker_id in run_finished_worker_ids {
if self
.worker
.as_ref()
.is_some_and(|worker| worker.outcome.worker_id() == worker_id)
{
self.active_run = false;
if self.join_completed_worker(ui_state) {
redraw_requested = true;
}
}
}
if context.finish_completed_worker
&& self
.worker
.as_ref()
.is_some_and(|worker| worker.handle.is_finished())
&& self.finish_worker_if_ready(ui_state)
{
redraw_requested = true;
}
if context.drain_budget_state.apply_outcome(drain_outcome) {
redraw_requested = true;
}
if model_catalog_finished {
self.model_catalog_loading = false;
}
let skip_loop = drain_outcome.exhausted
&& !drain_outcome.result.changed
&& context.pending_input_events.is_empty();
Ok(PostDrainProcessing {
flow: PostDrainFlow::Continue,
redraw_requested,
skip_loop,
})
}
}