use indicatrix::geometry::{
meet_solver::{MeetTierInput, VerifiedSolveReport, solve_meet_points_verified},
stone_metrics::ExternalProportions,
};
use slint::{ComponentHandle, Weak};
use std::{
sync::{
Arc,
atomic::{AtomicBool, Ordering},
},
thread,
time::{Duration, Instant},
};
const TICK_INTERVAL: Duration = Duration::from_millis(250);
pub struct DeepSolveHandle {
cancel: Arc<AtomicBool>,
}
impl DeepSolveHandle {
pub fn cancel(&self) {
self.cancel.store(true, Ordering::Relaxed);
}
}
#[derive(Debug, Clone, Copy)]
pub struct DeepSolveProgress {
pub elapsed: Duration,
}
pub enum DeepSolveOutcome {
Completed {
report: VerifiedSolveReport,
},
Cancelled,
}
pub fn spawn_deep_solve<T, P, D>(
ui_weak: Weak<T>,
gear_teeth_abs: u32,
tiers: Vec<MeetTierInput>,
targets: ExternalProportions,
on_progress: P,
on_done: D,
) -> DeepSolveHandle
where
T: ComponentHandle + 'static,
P: Fn(&T, DeepSolveProgress) + Send + 'static + Clone,
D: FnOnce(&T, DeepSolveOutcome) + Send + 'static,
{
let cancel = Arc::new(AtomicBool::new(false));
let cancel_ticker = Arc::clone(&cancel);
let cancel_worker = Arc::clone(&cancel);
let done_flag = Arc::new(AtomicBool::new(false));
let done_ticker = Arc::clone(&done_flag);
let ticker_ui = ui_weak.clone();
thread::spawn(move || {
let start = Instant::now();
loop {
thread::sleep(TICK_INTERVAL);
if done_ticker.load(Ordering::Relaxed) || cancel_ticker.load(Ordering::Relaxed) {
break;
}
let elapsed = start.elapsed();
let on_progress = on_progress.clone();
let _ = ticker_ui.upgrade_in_event_loop(move |ui| {
on_progress(&ui, DeepSolveProgress { elapsed });
});
}
});
thread::spawn(move || {
let (_solved, report) = solve_meet_points_verified(gear_teeth_abs, &tiers, &targets, &[]);
done_flag.store(true, Ordering::Relaxed);
let cancelled = cancel_worker.load(Ordering::Relaxed);
let outcome = outcome_for(cancelled, report);
let _ = ui_weak.upgrade_in_event_loop(move |ui| {
on_done(&ui, outcome);
});
});
DeepSolveHandle { cancel }
}
const fn outcome_for(cancelled: bool, report: VerifiedSolveReport) -> DeepSolveOutcome {
if cancelled {
DeepSolveOutcome::Cancelled
} else {
DeepSolveOutcome::Completed { report }
}
}
#[cfg(test)]
mod tests {
use super::*;
fn dummy_report(accepted: bool) -> VerifiedSolveReport {
VerifiedSolveReport {
initial_score: 0.5,
score_after_calibration: 0.3,
final_score: 0.1,
accepted,
overrides_applied: 2,
anchor_moves_applied: 1,
pipeline_runs: 12,
}
}
#[test]
fn cancel_sets_the_flag_the_worker_and_ticker_threads_poll() {
let flag = Arc::new(AtomicBool::new(false));
let handle = DeepSolveHandle {
cancel: Arc::clone(&flag),
};
assert!(!flag.load(Ordering::Relaxed));
handle.cancel();
assert!(flag.load(Ordering::Relaxed));
}
#[test]
fn outcome_for_reports_cancelled_and_discards_the_report_when_cancelled_is_true() {
let outcome = outcome_for(true, dummy_report(true));
assert!(matches!(outcome, DeepSolveOutcome::Cancelled));
}
#[test]
fn outcome_for_reports_completed_with_the_report_when_not_cancelled() {
let report = dummy_report(false);
let outcome = outcome_for(false, report);
match outcome {
DeepSolveOutcome::Completed { report: r } => {
assert_eq!(r.accepted, report.accepted);
assert_eq!(r.pipeline_runs, report.pipeline_runs);
assert_eq!(r.final_score, report.final_score);
}
DeepSolveOutcome::Cancelled => panic!("expected Completed, got Cancelled"),
}
}
#[test]
fn outcome_for_ignores_the_reports_own_accepted_flag_when_cancelled() {
let outcome = outcome_for(true, dummy_report(true));
assert!(matches!(outcome, DeepSolveOutcome::Cancelled));
}
#[test]
fn progress_carries_the_elapsed_duration_it_was_built_with() {
let progress = DeepSolveProgress {
elapsed: Duration::from_millis(1500),
};
assert_eq!(progress.elapsed, Duration::from_millis(1500));
}
#[test]
fn progress_is_copy_not_just_clone() {
let progress = DeepSolveProgress {
elapsed: Duration::from_secs(3),
};
let copied = progress;
assert_eq!(copied.elapsed, progress.elapsed);
}
}