use super::{
apply::{BackgroundSolveResult, apply_background_solve_result},
runtime::{PendingDispatch, RUNTIME},
};
use crate::{
EditorModel, EditorTierItem, MainWindow,
bridge::render_thread::RenderContext,
gui::editor::state::{
apply_multi_selection, design_to_gpu_planes, manufacturability_warnings_tagged,
status_text_and_is_problem, status_text_and_is_problem_from_solved, tier_items,
tier_items_from_solved, yield_report_texts, yield_report_texts_from_solved,
},
};
use indicatrix::{
geometry::{
GpuFacetPlane, ToolPrimitive,
meet_solver::{SolveError, SolvedTier},
},
optics::materials::GemMaterial,
};
use indicatrix_cut_core::{Design, DesignSolveError};
use indicatrix_editor::solve_policy::SOLVING_TICK_INTERVAL as TICK_INTERVAL;
use slint::{ComponentHandle, Weak};
use std::{
collections::BTreeSet,
sync::{
Arc, Mutex, PoisonError,
atomic::{AtomicBool, AtomicU64, Ordering},
},
thread,
time::{Duration, Instant},
};
pub(super) use indicatrix_editor::solve_policy::solving_banner;
pub(in crate::gui::editor) use indicatrix_editor::solve_policy::{
likely_hit_plane_cap, solve_cancellably, too_many_planes_message,
};
pub(in crate::gui::editor) fn cancel_in_flight_solve() {
RUNTIME.with(|cell| {
let mut rt = cell.borrow_mut();
if let Some(cancel) = rt.current_cancel.as_ref() {
cancel.store(true, Ordering::Relaxed);
}
rt.current_seq += 1;
rt.debounce = None;
rt.pending_dispatch = None;
rt.idle_replan = None;
});
}
fn cancel_in_flight_solve_from_activity(ui_weak: &Weak<MainWindow>) {
cancel_in_flight_solve();
let Some(ui) = ui_weak.upgrade() else {
return;
};
let model = ui.global::<EditorModel>();
model.set_solve_running(false);
model.set_solve_state("stale".into());
model.set_status_text("Solve abandoned -- click Solve when you are ready.".into());
model.set_status_is_problem(true);
}
pub(super) fn is_current(seq: u64) -> bool {
RUNTIME.with(|cell| cell.borrow().current_seq == seq)
}
struct PanelInputs {
tiers: Vec<EditorTierItem>,
status_text: String,
status_is_problem: bool,
warnings: Vec<(usize, String)>,
yield_texts: (String, String, String, String),
planes: Vec<GpuFacetPlane>,
tools: Vec<ToolPrimitive>,
placements: Vec<(usize, usize)>,
too_many_planes: bool,
}
fn panel_inputs(
design: &Design,
solved: Option<&Vec<SolvedTier>>,
custom_materials: &[GemMaterial],
custom_sg: &[(String, f64)],
) -> PanelInputs {
let n_d = design.effective_refractive_index_with(custom_materials);
let too_many_planes = solved.is_some_and(|solved| likely_hit_plane_cap(solved))
&& too_many_planes_message(design).is_some();
let (tools, placements) =
super::replan::design_to_gpu_tools_from_solved(design, solved.map(Vec::as_slice));
let Some(solved) = solved else {
let (status_text, status_is_problem) = status_text_and_is_problem(design);
return PanelInputs {
tiers: tier_items(design, n_d),
status_text,
status_is_problem,
warnings: manufacturability_warnings_tagged(design, None),
yield_texts: yield_report_texts(design, custom_sg),
planes: design_to_gpu_planes(design),
tools,
placements,
too_many_planes,
};
};
let (status_text, status_is_problem) = status_text_and_is_problem_from_solved(design, solved);
PanelInputs {
tiers: tier_items_from_solved(design, solved, n_d),
status_text,
status_is_problem,
warnings: manufacturability_warnings_tagged(design, Some(solved)),
yield_texts: yield_report_texts_from_solved(design, solved, custom_sg),
planes: super::replan::design_to_gpu_planes_from_solved(design, solved),
tools,
placements,
too_many_planes,
}
}
fn spawn_solving_ticker(ui: &MainWindow, seq: u64, tier_count: usize) -> Arc<AtomicBool> {
let done_flag = Arc::new(AtomicBool::new(false));
let done_ticker = Arc::clone(&done_flag);
let ticker_ui = ui.as_weak();
thread::spawn(move || {
let start = Instant::now();
loop {
thread::sleep(TICK_INTERVAL);
if done_ticker.load(Ordering::Relaxed) {
break;
}
let elapsed = start.elapsed();
let _ = ticker_ui.upgrade_in_event_loop(move |ui| {
if is_current(seq) {
ui.global::<EditorModel>()
.set_status_text(solving_banner(tier_count, elapsed).into());
}
});
}
});
done_flag
}
pub(super) fn queue_or_claim_solve_slot(
design: &Design,
generation: &Arc<AtomicU64>,
started_generation: u64,
multi_selected: &BTreeSet<usize>,
cancel: &Arc<AtomicBool>,
) -> bool {
RUNTIME.with(|cell| {
let mut rt = cell.borrow_mut();
if rt.solve_in_flight {
rt.pending_dispatch = Some(PendingDispatch {
design: design.clone(),
started_generation,
generation: Arc::clone(generation),
multi_selected: multi_selected.clone(),
});
true
} else {
rt.solve_in_flight = true;
rt.current_cancel = Some(Arc::clone(cancel));
false
}
})
}
fn register_solve_activity(ui: &MainWindow, tier_count: usize) {
let activity_id = RUNTIME
.with(|cell| cell.borrow().activity.clone())
.map(|a| {
let ui_weak = ui.as_weak();
a.start(
"solve",
solving_banner(tier_count, Duration::ZERO),
Some(Box::new(move || {
cancel_in_flight_solve_from_activity(&ui_weak);
})),
)
});
RUNTIME.with(|cell| cell.borrow_mut().activity_id = activity_id);
}
fn solve_and_build_result(
design: Design,
cancel: &AtomicBool,
multi_selected: &BTreeSet<usize>,
custom_materials: &[GemMaterial],
custom_sg: &[(String, f64)],
start: Instant,
) -> BackgroundSolveResult {
let solved_result = solve_cancellably(&design, cancel);
let multi_selected_count = multi_selected.len();
let n_d = design.effective_refractive_index_with(custom_materials);
if matches!(
solved_result,
Err(DesignSolveError::Solve(SolveError::Cancelled))
) || cancel.load(Ordering::Relaxed)
{
return BackgroundSolveResult::cancelled(design, start.elapsed(), multi_selected_count);
}
let PanelInputs {
mut tiers,
status_text,
status_is_problem,
warnings,
yield_texts,
planes,
tools,
placements,
too_many_planes,
} = panel_inputs(
&design,
solved_result.as_ref().ok(),
custom_materials,
custom_sg,
);
apply_multi_selection(&mut tiers, multi_selected);
let solved = solved_result.ok();
let gear = (
design.meta.gear_teeth_abs(),
design.meta.gear_reference_angle as f32,
);
BackgroundSolveResult {
elapsed: start.elapsed(),
tiers,
status_text,
status_is_problem,
warnings,
yield_texts,
planes,
tools,
placements,
solved,
too_many_planes,
gear,
multi_selected_count,
n_d,
panicked: false,
design,
}
}
pub(in crate::gui::editor) fn dispatch_background_solve(
ui: &MainWindow,
render_ctx: &Arc<Mutex<RenderContext>>,
design: Design,
generation: &Arc<AtomicU64>,
multi_selected: BTreeSet<usize>,
started_generation: u64,
) {
let cancel = Arc::new(AtomicBool::new(false));
if queue_or_claim_solve_slot(
&design,
generation,
started_generation,
&multi_selected,
&cancel,
) {
return;
}
let tier_count = design.tiers.len();
let seq = RUNTIME.with(|cell| {
let mut rt = cell.borrow_mut();
rt.current_seq += 1;
rt.current_seq
});
ui.global::<EditorModel>().set_solve_running(true);
ui.global::<EditorModel>().set_solve_state("solving".into());
ui.global::<EditorModel>()
.set_status_text(solving_banner(tier_count, Duration::ZERO).into());
ui.global::<EditorModel>().set_status_is_problem(false);
register_solve_activity(ui, tier_count);
let done_flag = spawn_solving_ticker(ui, seq, tier_count);
let (custom_materials, custom_sg) = {
let ctx = render_ctx.lock().unwrap_or_else(PoisonError::into_inner);
(
Arc::clone(&ctx.custom_materials),
Arc::clone(&ctx.custom_material_specific_gravity),
)
};
let ui_weak = ui.as_weak();
let render_ctx_for_apply = Arc::clone(render_ctx);
let generation = Arc::clone(generation);
let multi_selected_count = multi_selected.len();
thread::spawn(move || {
let start = Instant::now();
let design_for_panic = design.clone();
let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
solve_and_build_result(
design,
&cancel,
&multi_selected,
&custom_materials,
&custom_sg,
start,
)
}))
.unwrap_or_else(|_| {
BackgroundSolveResult::panicked(design_for_panic, start.elapsed(), multi_selected_count)
});
done_flag.store(true, Ordering::Relaxed);
let _ = ui_weak.upgrade_in_event_loop(move |ui| {
apply_background_solve_result(
&ui,
&render_ctx_for_apply,
seq,
started_generation,
&generation,
result,
);
});
});
}