use super::{
engine::{
BatchContext, DesignAccum, LaneShared, LiveProgress, Tally, build_items, run_batch_lanes,
},
scan::spawn_missing_preview_scan,
};
use crate::{
BatchModel, LibraryModel, MainWindow, SettingsModel,
bridge::{preview_render, render_thread::RenderContext},
gui::batch::batch_queue::{LanePlan, WorkQueue, local_lane_count},
settings::{LiveComputeTarget, SettingsPersister, WorkerSettings},
};
use indicatrix::renderer::gpu_backend::GpuBackend;
use indicatrix_vault::db::sqlite::Database;
use slint::{ComponentHandle, Model, Weak};
use std::{
cell::RefCell,
collections::HashMap,
rc::Rc,
sync::{
Arc, Mutex, PoisonError,
atomic::{AtomicBool, Ordering},
},
thread,
};
pub struct PreviewBatchHandle {
cancel: Arc<AtomicBool>,
}
impl PreviewBatchHandle {
pub fn cancel(&self) {
self.cancel.store(true, Ordering::Relaxed);
}
}
#[derive(Debug, Clone, Copy, Default)]
pub struct PreviewBatchOutcome {
pub generated: u32,
pub failed: u32,
pub cancelled: bool,
}
pub struct PreviewBatchSettings {
pub workers: Vec<WorkerSettings>,
pub live_compute_target: LiveComputeTarget,
pub preview_size: u32,
pub preview_spp: u32,
}
pub fn spawn_preview_batch(
ui_weak: Weak<MainWindow>,
db: Arc<Mutex<Database>>,
render_ctx: Arc<Mutex<RenderContext>>,
settings: PreviewBatchSettings,
entry_ids: Vec<i64>,
) -> PreviewBatchHandle {
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_preview_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_preview_batch_running(true));
let design_total = entry_ids.len() as u32;
let material_candidates = preview_render::ri_candidates();
let gpu = GpuBackend::acquire();
let remote_worker = settings.workers.into_iter().next();
let ctx = BatchContext {
db: &db,
material_candidates: &material_candidates,
preview_size: settings.preview_size,
preview_spp: settings.preview_spp,
};
let queue = WorkQueue::new(build_items(&entry_ids));
let design_state: Mutex<HashMap<i64, DesignAccum>> = Mutex::new(HashMap::new());
let tally = Tally::default();
let progress = Mutex::new(LiveProgress::default());
let plan = LanePlan::for_target(settings.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,
design_state: &design_state,
tally: &tally,
progress: &progress,
cancel: &cancel_thread,
design_total,
local_lane_total,
};
run_batch_lanes(
&shared,
&gpu,
plan,
remote_worker.as_ref(),
local_lane_total,
&ui_weak,
&remote_lane_done,
);
let outcome = PreviewBatchOutcome {
generated: tally.generated.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_preview_summary(
format!(
"Generated previews for {} design(s){}{}.",
outcome.generated,
if outcome.failed > 0 {
format!(", {} failed", outcome.failed)
} else {
String::new()
},
if outcome.cancelled {
" (cancelled)"
} else {
""
}
)
.into(),
);
ui.global::<BatchModel>().set_preview_done(true);
});
});
PreviewBatchHandle { cancel }
}
fn start_batch(
ui: &MainWindow,
db: &Arc<Mutex<Database>>,
render_ctx: &Arc<Mutex<RenderContext>>,
settings_store: &Arc<SettingsPersister>,
handle_slot: &Rc<RefCell<Option<PreviewBatchHandle>>>,
entry_ids: Vec<i64>,
) {
if entry_ids.is_empty() {
return;
}
let snapshot = settings_store.snapshot();
ui.global::<BatchModel>().set_preview_visible(true);
ui.global::<BatchModel>().set_preview_confirming(false);
ui.global::<BatchModel>().set_preview_done(false);
ui.global::<BatchModel>().set_preview_batch_running(true);
ui.global::<BatchModel>().set_preview_design_index(0);
ui.global::<BatchModel>()
.set_preview_design_total(entry_ids.len() as i32);
ui.global::<BatchModel>().set_preview_local_active(0);
ui.global::<BatchModel>().set_preview_local_lane_total(0);
ui.global::<BatchModel>()
.set_preview_remote_title(String::new().into());
ui.global::<BatchModel>().set_preview_remote_active(false);
ui.global::<BatchModel>()
.set_preview_summary(String::new().into());
let handle = spawn_preview_batch(
ui.as_weak(),
Arc::clone(db),
Arc::clone(render_ctx),
PreviewBatchSettings {
workers: snapshot.settings.remote_workers,
live_compute_target: snapshot.settings.live_compute_target,
preview_size: snapshot.settings.preview_size,
preview_spp: snapshot.settings.preview_spp,
},
entry_ids,
);
*handle_slot.borrow_mut() = Some(handle);
}
pub fn offer_batch_confirmation(ui: &MainWindow, ids: &[i64]) {
if ids.is_empty() {
return;
}
#[allow(
clippy::cast_possible_truncation,
reason = "entry ids are SQLite AUTOINCREMENT row ids from a several-thousand-\
row local catalogue, nowhere near i32::MAX; Slint's `[int]` model \
type has no i64 element type to use instead"
)]
let ids_i32: Vec<i32> = ids.iter().map(|&id| id as i32).collect();
ui.global::<BatchModel>()
.set_preview_offer_ids(slint::ModelRc::new(slint::VecModel::from(ids_i32)));
ui.global::<BatchModel>()
.set_preview_offer_count(ids.len() as i32);
ui.global::<BatchModel>().set_preview_confirming(true);
ui.global::<BatchModel>().set_preview_visible(true);
}
pub fn setup_preview_batch_callbacks(
ui: &MainWindow,
db: &Arc<Mutex<Database>>,
render_ctx: &Arc<Mutex<RenderContext>>,
settings_store: &Arc<SettingsPersister>,
) {
let handle: Rc<RefCell<Option<PreviewBatchHandle>>> = Rc::new(RefCell::new(None));
let settings_store_size = Arc::clone(settings_store);
ui.global::<SettingsModel>()
.on_preview_size_changed(move |size| {
settings_store_size.update(|s| s.settings.preview_size = size.max(1) as u32);
});
let settings_store_spp = Arc::clone(settings_store);
ui.global::<SettingsModel>()
.on_preview_spp_changed(move |spp| {
settings_store_spp.update(|s| s.settings.preview_spp = spp.max(1) as u32);
});
let handle_cancel = Rc::clone(&handle);
ui.global::<BatchModel>().on_preview_cancel(move || {
if let Some(h) = handle_cancel.borrow().as_ref() {
h.cancel();
}
});
let ui_weak_close = ui.as_weak();
ui.global::<BatchModel>().on_preview_close(move || {
if let Some(ui) = ui_weak_close.upgrade() {
ui.global::<BatchModel>().set_preview_visible(false);
ui.global::<BatchModel>().set_preview_done(false);
}
});
let ui_weak_dismiss = ui.as_weak();
ui.global::<BatchModel>().on_preview_dismiss_offer(move || {
if let Some(ui) = ui_weak_dismiss.upgrade() {
ui.global::<BatchModel>().set_preview_visible(false);
ui.global::<BatchModel>().set_preview_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_preview_generate_confirmed(move || {
if let Some(ui) = ui_weak_confirm.upgrade() {
let ids: Vec<i64> = ui
.global::<BatchModel>()
.get_preview_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_generate_previews_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);
spawn_missing_preview_scan(ui.as_weak(), db_scan, |ui, ids| {
offer_batch_confirmation(ui, &ids);
});
}