use crate::MainWindow;
use indicatrix::geometry::meet_solver::SolvedTier;
use indicatrix_cut_core::Design;
use slint::ComponentHandle;
use std::{
cell::{Cell, RefCell},
collections::{HashMap, VecDeque},
fmt,
sync::Arc,
};
use crate::gui::editor::solve_service::{SolveKind, SolveOutcome, SolveRequest, SolveService};
#[derive(Clone, Debug, PartialEq, Eq)]
pub(in crate::gui::editor) enum SolveFailure {
Superseded,
Failed(String),
}
impl fmt::Display for SolveFailure {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Superseded => f.write_str("the solve was displaced by a newer request"),
Self::Failed(message) => f.write_str(message),
}
}
}
pub(super) fn classify_solve_result(
superseded: bool,
outcome: SolveOutcome,
) -> Result<Vec<SolvedTier>, SolveFailure> {
if superseded {
return Err(SolveFailure::Superseded);
}
match outcome {
SolveOutcome::Solved(result) => result.map_err(|e| SolveFailure::Failed(e.to_string())),
SolveOutcome::Verified(_) => Err(SolveFailure::Failed(
"unexpected solve result kind".to_string(),
)),
}
}
#[derive(Debug, Default)]
pub(super) struct SolveLane {
queued: VecDeque<u64>,
running: Option<u64>,
}
impl SolveLane {
pub(super) fn enqueue(&mut self, key: u64) {
self.queued.push_back(key);
}
pub(super) fn requeue_front(&mut self, key: u64) {
self.queued.push_front(key);
}
pub(super) fn start_next(&mut self) -> Option<u64> {
if self.running.is_some() {
return None;
}
let key = self.queued.pop_front()?;
self.running = Some(key);
Some(key)
}
pub(super) fn finish(&mut self, key: u64) -> bool {
if self.running == Some(key) {
self.running = None;
true
} else {
false
}
}
}
#[must_use]
pub(super) fn solve_matches_design(
cached: Option<&[SolvedTier]>,
design: &Design,
) -> Option<Vec<SolvedTier>> {
let cached = cached?;
(cached.len() == design.tiers.len()).then(|| cached.to_vec())
}
fn cached_solve_matching(design: &Design) -> Option<Vec<SolvedTier>> {
let cache = crate::gui::editor::auto_solve::solid_last_solved()?;
let guard = cache
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner);
solve_matches_design(guard.as_ref().map(|(_, solved)| solved.as_slice()), design)
}
thread_local! {
static LOCAL_SOLVE_CACHE: RefCell<Option<(u64, Vec<SolvedTier>)>> = const { RefCell::new(None) };
}
fn local_cache_matching(design: &Design, generation: u64) -> Option<Vec<SolvedTier>> {
LOCAL_SOLVE_CACHE.with(|cell| {
let cached = cell.borrow();
let (cached_generation, solved) = cached.as_ref()?;
(*cached_generation == generation)
.then(|| solve_matches_design(Some(solved.as_slice()), design))?
})
}
type SolveContinuation = Box<dyn FnOnce(&MainWindow, Result<Vec<SolvedTier>, SolveFailure>)>;
const SUPERSEDED_REISSUES: u8 = 2;
struct PendingSolve {
design: Arc<Design>,
on_done: SolveContinuation,
reissues_left: u8,
}
thread_local! {
static SOLVE_SERVICE: RefCell<Option<SolveService>> = const { RefCell::new(None) };
static PENDING_SOLVES: RefCell<HashMap<u64, PendingSolve>> = RefCell::new(HashMap::new());
static SOLVE_LANE: RefCell<SolveLane> = RefCell::new(SolveLane::default());
static NEXT_SOLVE_KEY: Cell<u64> = const { Cell::new(0) };
}
fn dispatch_next_solve() {
loop {
let Some(key) = SOLVE_LANE.with(|lane| lane.borrow_mut().start_next()) else {
return;
};
let design = PENDING_SOLVES.with(|pending| {
pending
.borrow()
.get(&key)
.map(|entry| Arc::clone(&entry.design))
});
if let Some(design) = design {
SOLVE_SERVICE.with(|cell| {
if let Some(service) = cell.borrow().as_ref() {
service.submit(SolveRequest {
design,
generation: key,
kind: SolveKind::Full,
});
}
});
return;
}
SOLVE_LANE.with(|lane| lane.borrow_mut().finish(key));
}
}
fn ensure_solve_service(ui: &MainWindow) {
SOLVE_SERVICE.with(|cell| {
if cell.borrow().is_some() {
return;
}
let service = SolveService::new(
ui.as_weak(),
|_ui, _progress| {},
|ui, result| {
let key = result.generation;
SOLVE_LANE.with(|lane| lane.borrow_mut().finish(key));
let Some(mut pending) = PENDING_SOLVES.with(|c| c.borrow_mut().remove(&key)) else {
dispatch_next_solve();
return;
};
let outcome = classify_solve_result(result.superseded, result.outcome);
if matches!(outcome, Err(SolveFailure::Superseded)) && pending.reissues_left > 0 {
pending.reissues_left -= 1;
PENDING_SOLVES.with(|c| c.borrow_mut().insert(key, pending));
SOLVE_LANE.with(|lane| lane.borrow_mut().requeue_front(key));
dispatch_next_solve();
return;
}
dispatch_next_solve();
(pending.on_done)(ui, outcome);
},
);
*cell.borrow_mut() = Some(service);
});
}
fn submit_full_solve(
ui: &MainWindow,
design: Arc<Design>,
on_done: impl FnOnce(&MainWindow, Result<Vec<SolvedTier>, SolveFailure>) + 'static,
) {
ensure_solve_service(ui);
let key = NEXT_SOLVE_KEY.with(|c| {
let key = c.get();
c.set(key + 1);
key
});
PENDING_SOLVES.with(|cell| {
cell.borrow_mut().insert(
key,
PendingSolve {
design,
on_done: Box::new(on_done),
reissues_left: SUPERSEDED_REISSUES,
},
);
});
SOLVE_LANE.with(|lane| lane.borrow_mut().enqueue(key));
dispatch_next_solve();
}
pub(in crate::gui::editor) fn resolve_solved_then(
ui: &MainWindow,
design: Arc<Design>,
then: impl FnOnce(&MainWindow, Arc<Design>, Result<Vec<SolvedTier>, String>) + 'static,
) {
if let Some(solved) = cached_solve_matching(&design) {
then(ui, design, Ok(solved));
return;
}
let for_submit = Arc::clone(&design);
submit_full_solve(ui, for_submit, move |ui, result| {
then(ui, design, result.map_err(|failure| failure.to_string()));
});
}
pub(in crate::gui::editor) fn resolve_solve_at(
ui: &MainWindow,
design: Arc<Design>,
generation: u64,
then: impl FnOnce(&MainWindow, Arc<Design>, Result<Vec<SolvedTier>, SolveFailure>) + 'static,
) {
if let Some(solved) = local_cache_matching(&design, generation) {
then(ui, design, Ok(solved));
return;
}
let for_submit = Arc::clone(&design);
submit_full_solve(ui, for_submit, move |ui, result| {
if let Ok(solved) = &result {
LOCAL_SOLVE_CACHE.with(|cell| *cell.borrow_mut() = Some((generation, solved.clone())));
}
then(ui, design, result);
});
}