use super::{
dispatch::is_current,
runtime::{PendingDispatch, RUNTIME},
};
use crate::{
AngleItem, EditorModel, EditorTierItem, MainWindow, SolidPreviewModel, TiltModel,
bridge::render_thread::{PlanesOwner, RenderContext},
gui::{
editor::{
state::{
cutting_instructions_rows, girdle_and_ratio_texts, preform_mm_texts,
preform_y_offset_mm_text, proportions_texts, push_multi_selected_count, push_tiers,
},
view::{
facet_count_from_solved, girdle_and_ratio_texts_from_solved,
preform_mm_texts_from_solved, proportions_texts_from_solved,
push_proportion_verdicts_from_solved, scaled_viewport_size,
},
},
render::camera_lighting::contained_request_size,
show_toast,
solid_preview::{cut_slider, preview_state::CameraPose},
},
};
use indicatrix::geometry::{GpuFacetPlane, ToolPrimitive, meet_solver::SolvedTier};
use indicatrix_cut_core::Design;
use indicatrix_solid::preview::StoneGeometryBuf;
use slint::{ComponentHandle, ModelRc, SharedString, VecModel};
use std::{
sync::{
Arc, Mutex, PoisonError,
atomic::{AtomicU64, Ordering},
},
time::Duration,
};
pub(super) struct BackgroundSolveResult {
pub(super) elapsed: Duration,
pub(super) tiers: Vec<EditorTierItem>,
pub(super) status_text: String,
pub(super) status_is_problem: bool,
pub(super) warnings: Vec<(usize, String)>,
pub(super) yield_texts: (String, String, String, String),
pub(super) planes: Vec<GpuFacetPlane>,
pub(super) tools: Vec<ToolPrimitive>,
pub(super) placements: Vec<(usize, usize)>,
pub(super) solved: Option<Vec<SolvedTier>>,
pub(super) too_many_planes: bool,
pub(super) gear: (u32, f32),
pub(super) multi_selected_count: usize,
pub(super) n_d: f64,
pub(super) panicked: bool,
pub(super) design: Design,
}
impl BackgroundSolveResult {
pub(super) const fn cancelled(
design: Design,
elapsed: Duration,
multi_selected_count: usize,
) -> Self {
Self {
elapsed,
tiers: Vec::new(),
status_text: String::new(),
status_is_problem: false,
warnings: Vec::new(),
yield_texts: (String::new(), String::new(), String::new(), String::new()),
planes: Vec::new(),
tools: Vec::new(),
placements: Vec::new(),
solved: None,
too_many_planes: false,
gear: (
design.meta.gear_teeth_abs(),
design.meta.gear_reference_angle as f32,
),
multi_selected_count,
n_d: 0.0,
panicked: false,
design,
}
}
pub(super) fn panicked(design: Design, elapsed: Duration, multi_selected_count: usize) -> Self {
Self {
status_text: "Solve failed unexpectedly (internal error) -- try again or simplify \
the design."
.to_string(),
status_is_problem: true,
panicked: true,
..Self::cancelled(design, elapsed, multi_selected_count)
}
}
}
fn free_in_flight_slot() -> Option<PendingDispatch> {
RUNTIME.with(|cell| {
let mut rt = cell.borrow_mut();
rt.solve_in_flight = false;
rt.current_cancel = None;
if let (Some(activity), Some(id)) = (rt.activity.clone(), rt.activity_id.take()) {
activity.finish(id);
}
rt.pending_dispatch.take()
})
}
fn maybe_toast_too_many_planes(ui: &MainWindow, result: &BackgroundSolveResult) {
let already_toasted = RUNTIME.with(|cell| {
let mut rt = cell.borrow_mut();
let already = rt.too_many_planes_toasted;
rt.too_many_planes_toasted = result.too_many_planes;
already
});
if result.too_many_planes && !already_toasted {
show_toast(ui, &result.status_text, "warning");
}
}
pub(super) fn apply_background_solve_result(
ui: &MainWindow,
render_ctx: &Arc<Mutex<RenderContext>>,
seq: u64,
started_generation: u64,
generation: &Arc<AtomicU64>,
result: BackgroundSolveResult,
) {
let pending = free_in_flight_slot();
if !is_current(seq) {
dispatch_pending(ui, render_ctx, pending);
return;
}
super::scheduling::record_solve_duration(result.elapsed);
ui.global::<EditorModel>().set_solve_running(false);
if generation.load(Ordering::Relaxed) != started_generation {
dispatch_pending(ui, render_ctx, pending);
return;
}
if result.panicked {
ui.global::<EditorModel>()
.set_status_text(result.status_text.clone().into());
ui.global::<EditorModel>().set_status_is_problem(true);
ui.global::<EditorModel>().set_solve_state("failed".into());
show_toast(ui, &result.status_text, "error");
dispatch_pending(ui, render_ctx, pending);
return;
}
maybe_toast_too_many_planes(ui, &result);
push_solve_dependent_background_fields(ui, &result);
push_tiers(ui, result.tiers);
push_multi_selected_count(ui, result.multi_selected_count);
ui.global::<EditorModel>()
.set_status_text(result.status_text.into());
ui.global::<EditorModel>()
.set_status_is_problem(result.status_is_problem);
ui.global::<EditorModel>().set_solve_state(
if result.status_is_problem {
"failed"
} else {
"solved"
}
.into(),
);
ui.global::<EditorModel>()
.set_last_solve_duration_ms(i32::try_from(result.elapsed.as_millis()).unwrap_or(i32::MAX));
crate::gui::editor::guide::check_design_progress(ui, &result.design);
let warning_tiers: Vec<i32> = result
.warnings
.iter()
.map(|(index, _)| i32::try_from(*index).unwrap_or(i32::MAX))
.collect();
ui.global::<EditorModel>()
.set_manufacturability_warnings(ModelRc::new(VecModel::from(
result
.warnings
.into_iter()
.map(|(_, text)| SharedString::from(text))
.collect::<Vec<_>>(),
)));
ui.global::<EditorModel>()
.set_manufacturability_warning_tiers(ModelRc::new(VecModel::from(warning_tiers)));
let (vol_yield_text, carat_text, sg_used_text, fit_text) = result.yield_texts;
ui.global::<EditorModel>()
.set_volumetric_yield_text(vol_yield_text.into());
ui.global::<EditorModel>()
.set_carat_weight_text(carat_text.into());
ui.global::<EditorModel>()
.set_specific_gravity_used_text(sg_used_text.into());
ui.global::<EditorModel>()
.set_preform_fit_warning(fit_text.into());
if !push_viewport_after_background_solve(
ui,
render_ctx,
started_generation,
StoneGeometryBuf {
planes: result.planes,
tools: result.tools,
placements: result.placements,
},
result.gear,
result.solved,
&result.design,
) {
dispatch_pending(ui, render_ctx, pending);
return;
}
dispatch_pending(ui, render_ctx, pending);
}
fn push_viewport_after_background_solve(
ui: &MainWindow,
render_ctx: &Arc<Mutex<RenderContext>>,
started_generation: u64,
stone: StoneGeometryBuf,
gear: (u32, f32),
solved: Option<Vec<SolvedTier>>,
design: &Design,
) -> bool {
let stone = cut_slider::stone_at_cut(
stone,
design,
solved.as_deref(),
cut_slider::sync_model(ui, design),
);
let planes = Arc::new(stone.planes);
let mut ctx = render_ctx.lock().unwrap_or_else(PoisonError::into_inner);
if !ctx.claim_active_geometry(
Arc::clone(&planes),
Arc::new(stone.tools.clone()),
stone.placements.clone(),
Some(gear),
PlanesOwner::Editor {
generation: started_generation,
},
) {
return false;
}
ctx.dirty = true;
let design_gear = ctx.design_gear;
crate::gui::editor::push_viewport_owner(ui, ctx.planes_owner);
let camera = CameraPose {
yaw: ctx.yaw,
pitch: ctx.pitch,
distance: ctx.distance,
};
let render_size = (ctx.width, ctx.height);
drop(ctx);
let redraw_stone = StoneGeometryBuf {
planes: planes.as_ref().clone(),
tools: stone.tools,
placements: stone.placements,
};
let view_mode = ui.global::<SolidPreviewModel>().get_view_mode() as u8;
let size = contained_request_size(view_mode, scaled_viewport_size(ui), render_size);
let (preview_state, solid_last_solved) = RUNTIME.with(|cell| {
let rt = cell.borrow();
(rt.preview_state.clone(), rt.solid_last_solved.clone())
});
if let Some(preview_state) = preview_state {
preview_state.request_redraw_geometry(redraw_stone, camera, size, view_mode, design_gear);
}
if let (Some(solved), Some(cache)) = (solved, solid_last_solved) {
*cache.lock().unwrap_or_else(PoisonError::into_inner) = Some((started_generation, solved));
}
ui.global::<TiltModel>()
.set_cached_curve_material("".into());
if ui.global::<TiltModel>().get_dialog_open() {
ui.global::<TiltModel>().invoke_request_tilt_profile_axes();
}
true
}
fn push_solve_dependent_background_fields(ui: &MainWindow, result: &BackgroundSolveResult) {
let solved_slice = result.solved.as_deref();
let (table_pct, crown_height, pavilion_depth, total_depth, length_to_width) = solved_slice
.map_or_else(
|| proportions_texts(&result.design),
|solved| proportions_texts_from_solved(&result.design, solved),
);
ui.global::<EditorModel>()
.set_proportions_table_pct(table_pct.into());
ui.global::<EditorModel>()
.set_proportions_crown_height(crown_height.into());
ui.global::<EditorModel>()
.set_proportions_pavilion_depth(pavilion_depth.into());
ui.global::<EditorModel>()
.set_proportions_total_depth(total_depth.into());
ui.global::<EditorModel>()
.set_proportions_length_to_width(length_to_width.into());
let (girdle_thickness, crown_to_width, pavilion_to_width, girdle_to_width) = solved_slice
.map_or_else(
|| girdle_and_ratio_texts(&result.design),
|solved| girdle_and_ratio_texts_from_solved(&result.design, solved),
);
ui.global::<EditorModel>()
.set_girdle_thickness_text(girdle_thickness.into());
ui.global::<EditorModel>()
.set_crown_to_width_text(crown_to_width.into());
ui.global::<EditorModel>()
.set_pavilion_to_width_text(pavilion_to_width.into());
ui.global::<EditorModel>()
.set_girdle_to_width_text(girdle_to_width.into());
let (preform_half_width_mm, preform_depth_mm) = solved_slice.map_or_else(
|| preform_mm_texts(&result.design),
|solved| preform_mm_texts_from_solved(&result.design, solved),
);
ui.global::<EditorModel>()
.set_preform_half_width_mm_text(preform_half_width_mm.into());
ui.global::<EditorModel>()
.set_preform_depth_mm_text(preform_depth_mm.into());
let mm_per_unit =
solved_slice.and_then(|solved| result.design.yield_report(solved).mm_per_unit);
ui.global::<EditorModel>().set_preform_y_offset_mm(
preform_y_offset_mm_text(result.design.preform_y_offset, mm_per_unit).into(),
);
let facet_count = solved_slice.map_or(0, |solved| {
i32::try_from(facet_count_from_solved(&result.design, solved)).unwrap_or(i32::MAX)
});
ui.global::<EditorModel>().set_facet_count(facet_count);
if let Some(solved) = solved_slice {
let rows: Vec<AngleItem> = cutting_instructions_rows(&result.design, solved);
ui.global::<EditorModel>()
.set_cutting_rows(ModelRc::new(VecModel::from(rows)));
} else {
ui.global::<EditorModel>()
.set_cutting_rows(ModelRc::new(VecModel::from(Vec::<AngleItem>::new())));
}
push_proportion_verdicts_from_solved(ui, &result.design, solved_slice, result.n_d);
}
fn dispatch_pending(
ui: &MainWindow,
render_ctx: &Arc<Mutex<RenderContext>>,
pending: Option<PendingDispatch>,
) {
if let Some(pending) = pending {
super::dispatch::dispatch_background_solve(
ui,
render_ctx,
pending.design,
&pending.generation,
pending.multi_selected,
pending.started_generation,
);
}
}