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, remote_lane_count},
regenerate_all::{
ScopeKind, all_choice_label, chosen_ids, missing_among, missing_choice_label,
next_scope_generation, scope_is_current,
},
},
settings::{LiveComputeTarget, SettingsPersister, WorkerSettings},
};
use indicatrix_vault::db::sqlite::Database;
use slint::{ComponentHandle, Model, Weak};
use std::{
cell::RefCell,
collections::BTreeSet,
rc::Rc,
sync::{
Arc, Mutex,
atomic::{AtomicBool, AtomicU64, 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 angle_table: usize,
pub cancelled: bool,
}
pub struct TiltBatchSettings {
pub worker: Option<WorkerSettings>,
pub live_compute_target: LiveComputeTarget,
pub remote_batch_lanes: u32,
}
struct BusyGuard {
render_ctx: Arc<Mutex<RenderContext>>,
ui_weak: Weak<MainWindow>,
}
impl Drop for BusyGuard {
fn drop(&mut self) {
RenderContext::lock(&self.render_ctx).export_active_count -= 1;
let ui_weak = self.ui_weak.clone();
let _ = ui_weak.upgrade_in_event_loop(move |ui| {
ui.global::<BatchModel>().set_tilt_batch_running(false);
});
}
}
fn push_summary(ui_weak: &Weak<MainWindow>, outcome: TiltBatchOutcome) {
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()
},
super::super::angle_table_summary(outcome.angle_table),
if outcome.cancelled {
" (cancelled)"
} else {
""
}
)
.into(),
);
ui.global::<BatchModel>().set_tilt_done(true);
});
}
pub fn spawn_tilt_batch(
ui_weak: Weak<MainWindow>,
db: Arc<Mutex<Database>>,
render_ctx: Arc<Mutex<RenderContext>>,
settings: TiltBatchSettings,
entry_ids: Vec<i64>,
batch_id: u64,
current_batch_id: Arc<AtomicU64>,
) -> TiltBatchHandle {
let TiltBatchSettings {
worker: remote_worker,
live_compute_target,
remote_batch_lanes,
} = settings;
let cancel = Arc::new(AtomicBool::new(false));
let cancel_thread = Arc::clone(&cancel);
thread::spawn(move || {
RenderContext::lock(&render_ctx).export_active_count += 1;
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 angle_table_entries = Mutex::new(BTreeSet::new());
let ctx = BatchContext {
db: &db,
material_candidates: &material_candidates,
angle_table_entries: &angle_table_entries,
};
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 remote_lane_total = if plan.run_remote {
remote_lane_count(remote_batch_lanes) as u32
} else {
0
};
let shared = LaneShared {
ctx: &ctx,
queue: &queue,
tally: &tally,
progress: &progress,
cancel: &cancel_thread,
design_total,
local_lane_total,
remote_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),
angle_table: super::super::angle_table_count(&angle_table_entries),
cancelled: cancel_thread.load(Ordering::Relaxed),
};
if current_batch_id.load(Ordering::SeqCst) == batch_id {
push_summary(&ui_weak, outcome);
}
});
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>>>,
current_batch_id: &Arc<AtomicU64>,
entry_ids: Vec<i64>,
) {
if entry_ids.is_empty() {
return;
}
if ui.global::<BatchModel>().get_tilt_batch_running() {
crate::gui::show_toast(
ui,
"A tilt-curve batch is already running -- wait for it to finish or cancel \
it first.",
"info",
);
return;
}
let snapshot = settings_store.snapshot();
ui.global::<BatchModel>().set_tilt_single(false);
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_eta(String::new().into());
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_remote_in_flight(0);
ui.global::<BatchModel>()
.set_tilt_summary(String::new().into());
let batch_id = current_batch_id.fetch_add(1, Ordering::SeqCst) + 1;
let handle = spawn_tilt_batch(
ui.as_weak(),
Arc::clone(db),
Arc::clone(render_ctx),
TiltBatchSettings {
worker: snapshot.settings.remote_worker(),
live_compute_target: snapshot.settings.live_compute_target,
remote_batch_lanes: snapshot.settings.remote_batch_lanes,
},
entry_ids,
batch_id,
Arc::clone(current_batch_id),
);
*handle_slot.borrow_mut() = Some(handle);
}
pub 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_offer_regenerate(false);
ui.global::<BatchModel>().set_tilt_single(false);
ui.global::<BatchModel>().set_tilt_confirming(true);
ui.global::<BatchModel>().set_tilt_visible(true);
}
thread_local! {
static SCOPE_DB: RefCell<Option<Arc<Mutex<Database>>>> = const { RefCell::new(None) };
}
pub fn offer_regeneration(ui: &MainWindow, ids: &[i64]) {
if ids.is_empty() {
return;
}
offer_batch_confirmation(ui, ids);
let model = ui.global::<BatchModel>();
model.set_tilt_offer_regenerate(true);
model.set_tilt_offer_missing_only(true);
model.set_tilt_offer_missing_count(-1);
model.set_tilt_offer_missing_ids(slint::ModelRc::default());
model.set_tilt_offer_missing_label(missing_choice_label(None).into());
model.set_tilt_offer_all_label(all_choice_label(ids.len()).into());
let Some(db) = SCOPE_DB.with(|db| db.borrow().clone()) else {
return;
};
let generation = next_scope_generation(ScopeKind::Tilt);
let all = ids.to_vec();
spawn_missing_tilt_curve_scan(ui.as_weak(), db, move |ui, missing| {
if !scope_is_current(ScopeKind::Tilt, generation) {
return;
}
let missing = missing_among(&all, &missing);
let model = ui.global::<BatchModel>();
model.set_tilt_offer_missing_count(missing.len() as i32);
model.set_tilt_offer_missing_label(missing_choice_label(Some(missing.len())).into());
let missing_i32: Vec<i32> = missing.iter().map(|&id| id as i32).collect();
model.set_tilt_offer_missing_ids(slint::ModelRc::new(slint::VecModel::from(missing_i32)));
});
}
pub fn setup_tilt_batch_callbacks(
ui: &MainWindow,
db: &Arc<Mutex<Database>>,
render_ctx: &Arc<Mutex<RenderContext>>,
settings_store: &Arc<SettingsPersister>,
) {
SCOPE_DB.with(|scope_db| *scope_db.borrow_mut() = Some(Arc::clone(db)));
let handle: Rc<RefCell<Option<TiltBatchHandle>>> = Rc::new(RefCell::new(None));
let current_batch_id: Arc<AtomicU64> = Arc::new(AtomicU64::new(0));
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);
ui.global::<BatchModel>().set_tilt_single(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 current_batch_id_confirm = Arc::clone(¤t_batch_id);
let ui_weak_confirm = ui.as_weak();
ui.global::<BatchModel>()
.on_tilt_generate_confirmed(move || {
if let Some(ui) = ui_weak_confirm.upgrade() {
let model = ui.global::<BatchModel>();
let to_ids =
|ids: slint::ModelRc<i32>| ids.iter().map(i64::from).collect::<Vec<i64>>();
let ids = chosen_ids(
model.get_tilt_offer_regenerate(),
model.get_tilt_offer_missing_only(),
to_ids(model.get_tilt_offer_ids()),
to_ids(model.get_tilt_offer_missing_ids()),
);
start_batch(
&ui,
&db_confirm,
&render_ctx_confirm,
&settings_store_confirm,
&handle_confirm,
¤t_batch_id_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 current_batch_id_entry = Arc::clone(¤t_batch_id);
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,
¤t_batch_id_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);
}
});
}
});
}