use super::{
engine::{BatchContext, LaneShared, LiveProgress, Tally, run_batch_lanes},
scan::spawn_missing_tilt_curve_scan,
};
use crate::{
BatchModel, LibraryModel, MainWindow,
bridge::{preview_render, render_thread::RenderContext},
gui::batch::batch_queue::{LanePlan, WorkQueue, local_lane_count},
settings::{LiveComputeTarget, SettingsPersister, WorkerSettings},
};
use indicatrix_vault::db::sqlite::Database;
use slint::{ComponentHandle, Model, Weak};
use std::{
cell::RefCell,
rc::Rc,
sync::{
Arc, Mutex, PoisonError,
atomic::{AtomicBool, Ordering},
},
thread,
};
pub struct TiltBatchHandle {
cancel: Arc<AtomicBool>,
}
impl TiltBatchHandle {
pub fn cancel(&self) {
self.cancel.store(true, Ordering::Relaxed);
}
}
#[derive(Debug, Clone, Copy, Default)]
pub struct TiltBatchOutcome {
pub computed: u32,
pub failed: u32,
pub cancelled: bool,
}
pub fn spawn_tilt_batch(
ui_weak: Weak<MainWindow>,
db: Arc<Mutex<Database>>,
render_ctx: Arc<Mutex<RenderContext>>,
workers: Vec<WorkerSettings>,
live_compute_target: LiveComputeTarget,
entry_ids: Vec<i64>,
) -> TiltBatchHandle {
let cancel = Arc::new(AtomicBool::new(false));
let cancel_thread = Arc::clone(&cancel);
thread::spawn(move || {
struct BusyGuard {
render_ctx: Arc<Mutex<RenderContext>>,
ui_weak: Weak<MainWindow>,
}
impl Drop for BusyGuard {
fn drop(&mut self) {
self.render_ctx
.lock()
.unwrap_or_else(PoisonError::into_inner)
.export_active = false;
let ui_weak = self.ui_weak.clone();
let _ = ui_weak.upgrade_in_event_loop(move |ui| {
ui.global::<BatchModel>().set_tilt_batch_running(false);
});
}
}
{
let mut ctx = render_ctx.lock().unwrap_or_else(PoisonError::into_inner);
ctx.export_active = true;
}
let _busy_guard = BusyGuard {
render_ctx: Arc::clone(&render_ctx),
ui_weak: ui_weak.clone(),
};
let _ = ui_weak
.upgrade_in_event_loop(|ui| ui.global::<BatchModel>().set_tilt_batch_running(true));
let design_total = entry_ids.len() as u32;
let material_candidates = preview_render::ri_candidates();
let remote_worker = workers.into_iter().next();
let ctx = BatchContext {
db: &db,
material_candidates: &material_candidates,
};
let queue = WorkQueue::new(entry_ids);
let tally = Tally::default();
let progress = Mutex::new(LiveProgress::default());
let plan = LanePlan::for_target(live_compute_target, remote_worker.is_some());
let remote_lane_done = AtomicBool::new(!plan.run_remote);
let local_lane_total = if plan.run_local {
local_lane_count() as u32
} else {
0
};
let shared = LaneShared {
ctx: &ctx,
queue: &queue,
tally: &tally,
progress: &progress,
cancel: &cancel_thread,
design_total,
local_lane_total,
};
run_batch_lanes(
&shared,
plan,
remote_worker.as_ref(),
local_lane_total,
&ui_weak,
&remote_lane_done,
);
let outcome = TiltBatchOutcome {
computed: tally.computed.load(Ordering::Relaxed),
failed: tally.failed.load(Ordering::Relaxed),
cancelled: cancel_thread.load(Ordering::Relaxed),
};
let _ = ui_weak.upgrade_in_event_loop(move |ui| {
ui.global::<BatchModel>().set_tilt_summary(
format!(
"Computed tilt curves for {} design(s){}{}.",
outcome.computed,
if outcome.failed > 0 {
format!(", {} failed", outcome.failed)
} else {
String::new()
},
if outcome.cancelled {
" (cancelled)"
} else {
""
}
)
.into(),
);
ui.global::<BatchModel>().set_tilt_done(true);
});
});
TiltBatchHandle { cancel }
}
fn start_batch(
ui: &MainWindow,
db: &Arc<Mutex<Database>>,
render_ctx: &Arc<Mutex<RenderContext>>,
settings_store: &Arc<SettingsPersister>,
handle_slot: &Rc<RefCell<Option<TiltBatchHandle>>>,
entry_ids: Vec<i64>,
) {
if entry_ids.is_empty() {
return;
}
let snapshot = settings_store.snapshot();
ui.global::<BatchModel>().set_tilt_visible(true);
ui.global::<BatchModel>().set_tilt_confirming(false);
ui.global::<BatchModel>().set_tilt_done(false);
ui.global::<BatchModel>().set_tilt_batch_running(true);
ui.global::<BatchModel>().set_tilt_design_index(0);
ui.global::<BatchModel>()
.set_tilt_design_total(entry_ids.len() as i32);
ui.global::<BatchModel>().set_tilt_local_active(0);
ui.global::<BatchModel>().set_tilt_local_lane_total(0);
ui.global::<BatchModel>()
.set_tilt_remote_title(String::new().into());
ui.global::<BatchModel>().set_tilt_remote_active(false);
ui.global::<BatchModel>()
.set_tilt_summary(String::new().into());
let handle = spawn_tilt_batch(
ui.as_weak(),
Arc::clone(db),
Arc::clone(render_ctx),
snapshot.settings.remote_workers,
snapshot.settings.live_compute_target,
entry_ids,
);
*handle_slot.borrow_mut() = Some(handle);
}
fn offer_batch_confirmation(ui: &MainWindow, ids: &[i64]) {
if ids.is_empty() {
return;
}
let ids_i32: Vec<i32> = ids.iter().map(|&id| id as i32).collect();
ui.global::<BatchModel>()
.set_tilt_offer_ids(slint::ModelRc::new(slint::VecModel::from(ids_i32)));
ui.global::<BatchModel>()
.set_tilt_offer_count(ids.len() as i32);
ui.global::<BatchModel>().set_tilt_confirming(true);
ui.global::<BatchModel>().set_tilt_visible(true);
}
pub fn setup_tilt_batch_callbacks(
ui: &MainWindow,
db: &Arc<Mutex<Database>>,
render_ctx: &Arc<Mutex<RenderContext>>,
settings_store: &Arc<SettingsPersister>,
) {
let handle: Rc<RefCell<Option<TiltBatchHandle>>> = Rc::new(RefCell::new(None));
let handle_cancel = Rc::clone(&handle);
ui.global::<BatchModel>().on_tilt_cancel(move || {
if let Some(h) = handle_cancel.borrow().as_ref() {
h.cancel();
}
});
let ui_weak_close = ui.as_weak();
ui.global::<BatchModel>().on_tilt_close(move || {
if let Some(ui) = ui_weak_close.upgrade() {
ui.global::<BatchModel>().set_tilt_visible(false);
ui.global::<BatchModel>().set_tilt_done(false);
}
});
let ui_weak_dismiss = ui.as_weak();
ui.global::<BatchModel>().on_tilt_dismiss_offer(move || {
if let Some(ui) = ui_weak_dismiss.upgrade() {
ui.global::<BatchModel>().set_tilt_visible(false);
ui.global::<BatchModel>().set_tilt_confirming(false);
}
});
let db_confirm = Arc::clone(db);
let render_ctx_confirm = Arc::clone(render_ctx);
let settings_store_confirm = Arc::clone(settings_store);
let handle_confirm = Rc::clone(&handle);
let ui_weak_confirm = ui.as_weak();
ui.global::<BatchModel>()
.on_tilt_generate_confirmed(move || {
if let Some(ui) = ui_weak_confirm.upgrade() {
let ids: Vec<i64> = ui
.global::<BatchModel>()
.get_tilt_offer_ids()
.iter()
.map(i64::from)
.collect();
start_batch(
&ui,
&db_confirm,
&render_ctx_confirm,
&settings_store_confirm,
&handle_confirm,
ids,
);
}
});
let db_entry = Arc::clone(db);
let render_ctx_entry = Arc::clone(render_ctx);
let settings_store_entry = Arc::clone(settings_store);
let handle_entry = Rc::clone(&handle);
let ui_weak_entry = ui.as_weak();
ui.global::<LibraryModel>()
.on_compute_tilt_curves_for_entry(move |id: i32| {
if let Some(ui) = ui_weak_entry.upgrade() {
start_batch(
&ui,
&db_entry,
&render_ctx_entry,
&settings_store_entry,
&handle_entry,
vec![i64::from(id)],
);
}
});
let db_scan = Arc::clone(db);
let ui_weak_scan = ui.as_weak();
ui.global::<LibraryModel>()
.on_compute_missing_tilt_curves(move || {
if let Some(ui) = ui_weak_scan.upgrade() {
crate::gui::show_toast(
&ui,
"Scanning the catalogue for missing tilt curves...",
"info",
);
spawn_missing_tilt_curve_scan(ui.as_weak(), Arc::clone(&db_scan), |ui, ids| {
if ids.is_empty() {
crate::gui::show_toast(
ui,
"Every visible design already has tilt curves.",
"success",
);
} else {
offer_batch_confirmation(ui, &ids);
}
});
}
});
}