use super::{RunProvenance, clear_analysis_results};
use crate::{
EditorModel, MainWindow,
bridge::render_thread::RenderContext,
gui::{
editor::{
optimize_solve::OptimizeSolveOutcome,
state::EditorState,
view::{
SolidLastSolved, build_optimize_preview_design, optimize_change_rows,
optimize_result_rows, optimize_status_text, refresh_all_now,
submit_design_ghost_preview, submit_preview_replan,
},
},
show_toast,
solid_preview::preview_state::SolidPreviewState,
},
};
use indicatrix_cut_core::{Design, DesignSolveError, OptimizeOutcome};
use slint::{ComponentHandle, ModelRc, VecModel};
use std::{
cell::RefCell,
collections::BTreeSet,
rc::Rc,
sync::{Arc, Mutex, atomic::Ordering as AtomicOrdering},
};
pub(super) fn handle_optimize_outcome(
ui: &MainWindow,
outcome: OptimizeSolveOutcome,
provenance: &RunProvenance,
pending_optimize: &Arc<Mutex<Option<(OptimizeOutcome, u64)>>>,
design: &Design,
) {
ui.global::<EditorModel>().set_optimize_running(false);
if provenance.design_replaced() {
clear_analysis_results(ui);
return;
}
match outcome {
OptimizeSolveOutcome::Failed { error } => {
let status = if matches!(error, DesignSolveError::MissingAnchor(_)) {
format!(
"Optimize failed: {error} -- add an Exact scale value tier there \
and Solve first."
)
} else {
format!("Optimize failed: {error}.")
};
ui.global::<EditorModel>()
.set_optimize_status(status.clone().into());
ui.global::<EditorModel>()
.set_optimize_status_is_problem(true);
ui.global::<EditorModel>().set_optimize_can_apply(false);
show_toast(ui, &status, "error");
}
OptimizeSolveOutcome::Completed { outcome }
| OptimizeSolveOutcome::Cancelled { outcome } => {
let stale = provenance.is_stale();
let no_net_improvement =
!outcome.changes.is_empty() && outcome.after_score >= outcome.before_score;
let status = if no_net_improvement {
format!(
"No net improvement at full tilt fidelity -- not recommended \
({})",
optimize_status_text(&outcome)
)
} else {
optimize_status_text(&outcome)
};
ui.global::<EditorModel>()
.set_optimize_status(status.clone().into());
ui.global::<EditorModel>()
.set_optimize_status_is_problem(no_net_improvement);
ui.global::<EditorModel>()
.set_optimize_result_rows(ModelRc::new(VecModel::from(optimize_result_rows(
&outcome,
))));
ui.global::<EditorModel>()
.set_optimize_change_rows(ModelRc::new(VecModel::from(optimize_change_rows(
&outcome, design,
))));
let can_apply = !outcome.changes.is_empty() && !stale;
ui.global::<EditorModel>().set_optimize_can_apply(can_apply);
ui.global::<EditorModel>().set_optimize_stale(stale);
*pending_optimize
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner) =
can_apply.then_some((outcome, provenance.started_generation));
if stale {
show_toast(
ui,
"The design changed while Optimize was running -- its result is \
shown for reference but can no longer be applied. Re-run \
Optimize on the current design.",
"info",
);
} else {
show_toast(
ui,
&format!("Optimize finished: {status}"),
if no_net_improvement {
"info"
} else {
"success"
},
);
if can_apply {
ui.global::<EditorModel>().set_inspector_tab(2);
ui.global::<EditorModel>().set_inspector_collapsed(false);
}
}
}
}
}
pub(in crate::gui::editor) fn setup_optimize_cancel_callback(
ui: &MainWindow,
state: &Rc<RefCell<EditorState>>,
) {
let state = Rc::clone(state);
let ui_weak = ui.as_weak();
ui.global::<EditorModel>().on_optimize_cancel(move || {
if let Some(handle) = state.borrow().optimize.as_ref() {
handle.cancel();
}
if let Some(ui) = ui_weak.upgrade() {
ui.global::<EditorModel>()
.set_optimize_status("Cancelling -- finishing the current evaluation...".into());
ui.global::<EditorModel>()
.set_optimize_status_is_problem(false);
}
});
}
pub(in crate::gui::editor) fn setup_optimize_apply_callback(
ui: &MainWindow,
state: &Rc<RefCell<EditorState>>,
render_ctx: &Arc<Mutex<RenderContext>>,
preview_state: &Arc<SolidPreviewState>,
solid_last_solved: &SolidLastSolved,
) {
let state = Rc::clone(state);
let render_ctx = Arc::clone(render_ctx);
let preview_state = Arc::clone(preview_state);
let solid_last_solved = Arc::clone(solid_last_solved);
let ui_weak = ui.as_weak();
ui.global::<EditorModel>().on_optimize_apply(move || {
let Some(ui) = ui_weak.upgrade() else {
return;
};
let mut st = state.borrow_mut();
let snapshot = st
.pending_optimize
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner)
.clone();
let Some((outcome, started_generation)) = snapshot else {
return;
};
if st.generation.load(AtomicOrdering::Relaxed) != started_generation {
ui.global::<EditorModel>().set_optimize_can_apply(false);
show_toast(
&ui,
"Result discarded: the design changed while Optimize was running. Re-run \
Optimize.",
"warning",
);
return;
}
match st.apply_optimize_outcome(&outcome) {
Ok(applied) => {
*st.pending_optimize
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner) = None;
ui.global::<EditorModel>().set_optimize_can_apply(false);
drop(st);
refresh_all_now(
&ui,
&render_ctx,
&preview_state,
&solid_last_solved,
&state,
false,
);
let applied_status = format!(
"Applied: {}",
ui.global::<EditorModel>().get_optimize_status()
);
ui.global::<EditorModel>()
.set_optimize_status(applied_status.into());
show_toast(
&ui,
&format!("Applied {applied} tier change(s) from Optimize."),
"success",
);
}
Err(e) => {
ui.global::<EditorModel>().set_optimize_can_apply(false);
show_toast(&ui, &e.to_string(), "error");
}
}
});
}
pub(in crate::gui::editor) fn setup_optimize_preview_callback(
ui: &MainWindow,
state: &Rc<RefCell<EditorState>>,
render_ctx: &Arc<Mutex<RenderContext>>,
preview_state: &Arc<SolidPreviewState>,
solid_last_solved: &SolidLastSolved,
) {
let state = Rc::clone(state);
let render_ctx = Arc::clone(render_ctx);
let preview_state = Arc::clone(preview_state);
let solid_last_solved = Arc::clone(solid_last_solved);
let ui_weak = ui.as_weak();
ui.global::<EditorModel>()
.on_optimize_preview_toggled(move |enabled: bool| {
let Some(ui) = ui_weak.upgrade() else {
return;
};
let st = state.borrow();
let pending = enabled
.then(|| {
st.pending_optimize
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner)
.clone()
})
.flatten();
let current_generation = st.generation.load(AtomicOrdering::Relaxed);
if let Some((outcome, started_generation)) = pending
&& started_generation == current_generation
{
let candidate = build_optimize_preview_design(&st.design, &outcome);
if submit_design_ghost_preview(&ui, &render_ctx, &preview_state, &candidate) {
return;
}
}
submit_preview_replan(
&ui,
&render_ctx,
&preview_state,
&solid_last_solved,
&st,
BTreeSet::new(),
false,
);
});
}