use super::{
auto_solve::{editor_state, solid_last_solved},
native_io::{SolveFailure, resolve_solve_at},
};
use crate::{
MainWindow, ViewportModel,
bridge::render_thread::{PlanesOwner, RenderContext},
gui::solid_preview::{cut_slider, preview_state::SolidLastSolved},
};
use indicatrix::geometry::meet_solver::SolvedTier;
use indicatrix_cut_core::Design;
use indicatrix_solid::preview::StoneGeometryBuf;
use slint::ComponentHandle;
use std::sync::{
Arc, Mutex, PoisonError,
atomic::{AtomicU64, Ordering},
};
#[derive(Debug, Clone, PartialEq, Eq)]
pub(in crate::gui) enum Withheld {
Unsolvable(String),
Stale,
Displaced,
}
impl Withheld {
#[must_use]
pub(in crate::gui) fn export_message(&self) -> String {
match self {
Self::Unsolvable(reason) => {
format!("Nothing was exported: the design does not solve ({reason}).")
}
Self::Stale => "Nothing was exported: the design changed while the finished stone \
was being prepared. Start the export again."
.to_string(),
Self::Displaced => "Nothing was exported: preparing the finished stone was \
interrupted by a newer request. Start the export again."
.to_string(),
}
}
}
pub(in crate::gui) type Finished = Result<Option<StoneGeometryBuf>, Withheld>;
fn no_facets() -> Withheld {
Withheld::Unsolvable("the finished stone has no facets".to_string())
}
#[must_use]
fn is_stale(job_generation: u64, live: Option<u64>) -> bool {
live.is_some_and(|live| live != job_generation)
}
#[must_use]
pub(in crate::gui) const fn shows_editor_design(owner: PlanesOwner) -> bool {
matches!(owner, PlanesOwner::Editor { .. })
}
pub(in crate::gui) fn push_viewport_owner(ui: &MainWindow, owner: PlanesOwner) {
ui.global::<ViewportModel>()
.set_shows_editor_design(shows_editor_design(owner));
}
#[must_use]
fn whole_stone(design: &Design, solved: &[SolvedTier]) -> Option<StoneGeometryBuf> {
let stone = cut_slider::cut_geometry(design, Some(solved), None);
(!stone.planes.is_empty()).then_some(stone)
}
#[must_use]
fn masts_for_generation(
cached: Option<&(u64, Vec<SolvedTier>)>,
generation: u64,
design: &Design,
) -> Option<Vec<SolvedTier>> {
let (stored_for, masts) = cached?;
(*stored_for == generation && masts.len() == design.tiers.len()).then(|| masts.clone())
}
fn read_cached_masts(
cache: &SolidLastSolved,
generation: u64,
design: &Design,
) -> Option<Vec<SolvedTier>> {
let guard = cache.lock().unwrap_or_else(PoisonError::into_inner);
masts_for_generation(guard.as_ref(), generation, design)
}
fn remember_masts(cache: &SolidLastSolved, generation: u64, solved: Vec<SolvedTier>) {
let mut guard = cache.lock().unwrap_or_else(PoisonError::into_inner);
if guard
.as_ref()
.is_none_or(|(stored_for, _)| *stored_for < generation)
{
*guard = Some((generation, solved));
}
}
static SOLVE_GATE: Mutex<()> = Mutex::new(());
fn solve_design(design: &Design) -> Result<Vec<SolvedTier>, String> {
design.solve().map_err(|error| error.to_string())
}
pub(in crate::gui) struct FinishedStoneJob {
design: Design,
generation: u64,
masts: Option<Vec<SolvedTier>>,
cache: Option<SolidLastSolved>,
live_generation: Option<Arc<AtomicU64>>,
}
impl FinishedStoneJob {
#[must_use]
fn when_cut(
design: &Design,
generation: u64,
cut_steps: Option<usize>,
cache: Option<SolidLastSolved>,
) -> Option<Self> {
cut_steps?;
let masts = cache
.as_ref()
.and_then(|cache| read_cached_masts(cache, generation, design));
Some(Self {
design: design.clone(),
generation,
masts,
cache,
live_generation: None,
})
}
#[must_use]
fn following(mut self, live: Arc<AtomicU64>) -> Self {
self.live_generation = Some(live);
self
}
fn is_stale(&self) -> bool {
is_stale(
self.generation,
self.live_generation
.as_ref()
.map(|live| live.load(Ordering::Relaxed)),
)
}
const fn holds_masts(&self) -> bool {
self.masts.is_some()
}
fn stone_from_held_masts(&self) -> Option<StoneGeometryBuf> {
whole_stone(&self.design, self.masts.as_deref()?)
}
fn solve_and_remember(
&self,
solve: &impl Fn(&Design) -> Result<Vec<SolvedTier>, String>,
) -> Result<Vec<SolvedTier>, Withheld> {
let solved = solve(&self.design).map_err(Withheld::Unsolvable)?;
if let Some(cache) = &self.cache {
remember_masts(cache, self.generation, solved.clone());
}
Ok(solved)
}
pub(in crate::gui) fn resolve(self) -> Result<StoneGeometryBuf, Withheld> {
self.resolve_with(solve_design)
}
fn resolve_with(
mut self,
solve: impl Fn(&Design) -> Result<Vec<SolvedTier>, String>,
) -> Result<StoneGeometryBuf, Withheld> {
let held = self.masts.take().or_else(|| self.cached_masts());
let masts = if let Some(masts) = held {
masts
} else {
self.solve_behind_gate(&solve)?
};
if self.is_stale() {
return Err(Withheld::Stale);
}
whole_stone(&self.design, &masts).ok_or_else(no_facets)
}
fn solve_behind_gate(
&self,
solve: &impl Fn(&Design) -> Result<Vec<SolvedTier>, String>,
) -> Result<Vec<SolvedTier>, Withheld> {
if self.is_stale() {
return Err(Withheld::Stale);
}
let _single_flight = SOLVE_GATE.lock().unwrap_or_else(PoisonError::into_inner);
if let Some(masts) = self.cached_masts() {
return Ok(masts);
}
if self.is_stale() {
return Err(Withheld::Stale);
}
self.solve_and_remember(solve)
}
fn cached_masts(&self) -> Option<Vec<SolvedTier>> {
self.cache
.as_ref()
.and_then(|cache| read_cached_masts(cache, self.generation, &self.design))
}
}
#[must_use]
pub(in crate::gui) fn finished_stone_job(
ui: &MainWindow,
render_ctx: &Arc<Mutex<RenderContext>>,
) -> Option<FinishedStoneJob> {
let owner = RenderContext::lock(render_ctx).planes_owner;
if !shows_editor_design(owner) {
return None;
}
let state = editor_state()?;
let state = state.try_borrow().ok()?;
let cut_steps = cut_slider::current_steps(ui, &state.design);
FinishedStoneJob::when_cut(
&state.design,
state.generation.load(Ordering::Relaxed),
cut_steps,
solid_last_solved(),
)
.map(|job| job.following(Arc::clone(&state.generation)))
}
fn landed(
design: &Design,
generation: u64,
live: Option<u64>,
result: Result<Vec<SolvedTier>, SolveFailure>,
remember: impl FnOnce(Vec<SolvedTier>),
) -> Finished {
let stale = is_stale(generation, live);
match result {
Ok(solved) => {
let stone = whole_stone(design, &solved);
remember(solved);
if stale {
Err(Withheld::Stale)
} else {
stone.map(Some).ok_or_else(no_facets)
}
}
Err(SolveFailure::Failed(reason)) if !stale => Err(Withheld::Unsolvable(reason)),
Err(SolveFailure::Superseded) if !stale => Err(Withheld::Displaced),
Err(_) => Err(Withheld::Stale),
}
}
pub(in crate::gui) fn finished_stone_then(
ui: &MainWindow,
render_ctx: &Arc<Mutex<RenderContext>>,
while_solving: impl FnOnce(&MainWindow),
then: impl FnOnce(&MainWindow, Finished) + 'static,
) {
let Some(job) = finished_stone_job(ui, render_ctx) else {
then(ui, Ok(None));
return;
};
if job.holds_masts() {
then(
ui,
job.stone_from_held_masts().map(Some).ok_or_else(no_facets),
);
return;
}
while_solving(ui);
let FinishedStoneJob {
design,
generation,
cache,
live_generation,
..
} = job;
resolve_solve_at(
ui,
Arc::new(design),
generation,
move |ui, design, result| {
let live = live_generation
.as_ref()
.map(|live| live.load(Ordering::Relaxed));
then(
ui,
landed(&design, generation, live, result, |solved| {
if let Some(cache) = &cache {
remember_masts(cache, generation, solved);
}
}),
);
},
);
}
#[cfg(test)]
mod tests;