use super::auto_solve::solve_cancellably;
use indicatrix::geometry::{
meet_solver::{
SolveControl, SolveError, SolveProgress, SolvedTier, VerifiedSolveReport,
solve_meet_points_verified_with,
},
stone_metrics::ExternalProportions,
};
use indicatrix_cut_core::{Design, DesignSolveError};
use slint::{ComponentHandle, Weak};
use std::{
cell::Cell,
sync::{
Arc, Condvar, Mutex, PoisonError,
atomic::{AtomicBool, AtomicU64, Ordering},
},
thread,
time::{Duration, Instant},
};
const PROGRESS_THROTTLE: Duration = Duration::from_millis(100);
pub enum SolveKind {
Full,
Verified {
targets: ExternalProportions,
compute_baseline: bool,
},
GhostPreview,
}
pub struct VerifiedSolve {
pub solved: Vec<SolvedTier>,
pub report: VerifiedSolveReport,
pub baseline: Option<Vec<SolvedTier>>,
}
pub struct SolveRequest {
pub design: Arc<Design>,
pub generation: u64,
pub kind: SolveKind,
}
pub enum SolveOutcome {
Solved(Result<Vec<SolvedTier>, DesignSolveError>),
Verified(Result<VerifiedSolve, SolveError>),
}
pub struct SolveResult {
pub generation: u64,
pub elapsed: Duration,
pub outcome: SolveOutcome,
pub superseded: bool,
}
#[derive(Debug, Clone, Copy)]
pub struct SolveProgressReport {
pub generation: u64,
pub progress: SolveProgress,
}
pub struct SolveHandle {
pub generation: u64,
cancel: Arc<AtomicBool>,
}
impl SolveHandle {
pub fn cancel(&self) {
self.cancel.store(true, Ordering::Relaxed);
}
}
struct Queued {
request: SolveRequest,
seq: u64,
cancel: Arc<AtomicBool>,
}
struct Mailbox {
slot: Mutex<Option<Queued>>,
ready: Condvar,
shutdown: AtomicBool,
}
impl Mailbox {
const fn new() -> Self {
Self {
slot: Mutex::new(None),
ready: Condvar::new(),
shutdown: AtomicBool::new(false),
}
}
fn put(&self, item: Queued) {
{
let mut slot = self.slot.lock().unwrap_or_else(PoisonError::into_inner);
*slot = Some(item);
}
self.ready.notify_one();
}
fn request_shutdown(&self) {
self.shutdown.store(true, Ordering::Relaxed);
self.ready.notify_one();
}
fn take_blocking(&self) -> Option<Queued> {
let mut slot = self.slot.lock().unwrap_or_else(PoisonError::into_inner);
loop {
if let Some(item) = slot.take() {
return Some(item);
}
if self.shutdown.load(Ordering::Relaxed) {
return None;
}
slot = self
.ready
.wait(slot)
.unwrap_or_else(PoisonError::into_inner);
}
}
#[cfg(test)]
fn try_take(&self) -> Option<Queued> {
let mut slot = self.slot.lock().unwrap_or_else(PoisonError::into_inner);
slot.take()
}
}
struct ProgressThrottle {
last_forwarded: Cell<Option<Instant>>,
last_phase_sweep: Cell<Option<(indicatrix::geometry::meet_solver::SolvePhase, u32)>>,
}
impl ProgressThrottle {
const fn new() -> Self {
Self {
last_forwarded: Cell::new(None),
last_phase_sweep: Cell::new(None),
}
}
fn should_forward(&self, progress: SolveProgress) -> bool {
let key = (progress.phase, progress.sweep);
let new_phase_sweep = self.last_phase_sweep.get() != Some(key);
let interval_elapsed = self
.last_forwarded
.get()
.is_none_or(|t| t.elapsed() >= PROGRESS_THROTTLE);
if !new_phase_sweep && !interval_elapsed {
return false;
}
self.last_phase_sweep.set(Some(key));
self.last_forwarded.set(Some(Instant::now()));
true
}
}
fn run_solve(
request: &SolveRequest,
cancel: &AtomicBool,
on_progress: &dyn Fn(SolveProgress),
) -> SolveOutcome {
let control = SolveControl::with_cancel(cancel).reporting(on_progress);
match &request.kind {
SolveKind::Full => SolveOutcome::Solved(request.design.solve_with(&control)),
SolveKind::GhostPreview => SolveOutcome::Solved(solve_cancellably(&request.design, cancel)),
SolveKind::Verified {
targets,
compute_baseline,
} => {
let baseline = (*compute_baseline)
.then(|| solve_cancellably(&request.design, cancel).ok())
.flatten();
let gear_teeth_abs = request.design.meta.gear_teeth_abs();
let tiers = request.design.meet_tier_inputs();
SolveOutcome::Verified(
solve_meet_points_verified_with(gear_teeth_abs, &tiers, targets, &[], &control)
.map(|(solved, report)| VerifiedSolve {
solved,
report,
baseline,
}),
)
}
}
}
pub struct SolveService {
mailbox: Arc<Mailbox>,
latest_seq: Arc<AtomicU64>,
}
impl SolveService {
pub fn new<T, P, D>(ui_weak: Weak<T>, on_progress: P, on_result: D) -> Self
where
T: ComponentHandle + 'static,
P: Fn(&T, SolveProgressReport) + Send + Clone + 'static,
D: Fn(&T, SolveResult) + Send + Clone + 'static,
{
let mailbox = Arc::new(Mailbox::new());
let latest_seq = Arc::new(AtomicU64::new(0));
let worker_mailbox = Arc::clone(&mailbox);
let worker_seq = Arc::clone(&latest_seq);
thread::spawn(move || {
worker_loop(
&ui_weak,
&worker_mailbox,
&worker_seq,
on_progress,
on_result,
);
});
Self {
mailbox,
latest_seq,
}
}
pub fn submit(&self, request: SolveRequest) -> SolveHandle {
let seq = self.latest_seq.fetch_add(1, Ordering::Relaxed) + 1;
let cancel = Arc::new(AtomicBool::new(false));
let generation = request.generation;
self.mailbox.put(Queued {
request,
seq,
cancel: Arc::clone(&cancel),
});
SolveHandle { generation, cancel }
}
}
impl Drop for SolveService {
fn drop(&mut self) {
self.mailbox.request_shutdown();
}
}
fn worker_loop<T, P, D>(
ui_weak: &Weak<T>,
mailbox: &Mailbox,
latest_seq: &AtomicU64,
on_progress: P,
on_result: D,
) where
T: ComponentHandle + 'static,
P: Fn(&T, SolveProgressReport) + Send + Clone + 'static,
D: Fn(&T, SolveResult) + Send + Clone + 'static,
{
loop {
let Some(Queued {
request,
seq,
cancel,
}) = mailbox.take_blocking()
else {
return;
};
let generation = request.generation;
let start = Instant::now();
let throttle = ProgressThrottle::new();
let outcome = run_solve(&request, &cancel, &|progress| {
if latest_seq.load(Ordering::Relaxed) != seq || !throttle.should_forward(progress) {
return;
}
let ui = ui_weak.clone();
let on_progress = on_progress.clone();
let _ = ui.upgrade_in_event_loop(move |ui| {
on_progress(
&ui,
SolveProgressReport {
generation,
progress,
},
);
});
});
let superseded = latest_seq.load(Ordering::Relaxed) != seq;
let result = SolveResult {
generation,
elapsed: start.elapsed(),
outcome,
superseded,
};
let ui = ui_weak.clone();
let on_result = on_result.clone();
let _ = ui.upgrade_in_event_loop(move |ui| {
on_result(&ui, result);
});
}
}
#[cfg(test)]
mod tests;