pub mod batch;
pub mod filename_template;
mod icc_profile;
mod params;
mod preview;
pub mod remote;
mod sample_cursor;
mod scene_snapshot;
mod tonemap_png;
mod worker;
#[cfg(test)]
mod hybrid_export_tests;
#[cfg(test)]
mod tests;
pub use filename_template::{DEFAULT_TEMPLATE, TemplateContext, resolve_export_path};
pub use params::{ComputeTarget, ExportParams, validate_export_params};
pub use remote::probe_remote;
pub use scene_snapshot::SceneSnapshot;
use crate::settings::{LocalComputeTarget, WorkerSettings};
use indicatrix::color::ColorSpace;
use slint::{ComponentHandle, Rgba8Pixel, SharedPixelBuffer, Weak};
use std::{
path::PathBuf,
sync::{
Arc,
atomic::{AtomicBool, Ordering},
},
thread,
};
use worker::run_export;
#[derive(Debug)]
pub enum ExportOutcome {
Completed(PathBuf),
Cancelled,
Failed(String),
}
pub struct ExportProgress {
pub fraction: f32,
pub samples_done: u32,
pub samples_total: u32,
pub preview: Option<SharedPixelBuffer<Rgba8Pixel>>,
pub note: Option<String>,
}
pub struct ExportHandle {
cancel: Arc<AtomicBool>,
}
impl ExportHandle {
pub fn cancel(&self) {
self.cancel.store(true, Ordering::Relaxed);
}
}
#[expect(
clippy::too_many_arguments,
reason = "every argument is a distinct piece of one export request's own identity \
(scene, output params/path/colour-space, the new compute-target/workers \
choice, and the two UI callbacks) -- bundling them into a struct would \
just move the same count into field access, not reduce it"
)]
pub fn spawn_export<T, P, D>(
ui_weak: Weak<T>,
scene: SceneSnapshot,
params: ExportParams,
color_space: ColorSpace,
output_path: PathBuf,
compute_target: ComputeTarget,
workers: Vec<WorkerSettings>,
local_compute: LocalComputeTarget,
on_progress: P,
on_done: D,
) -> ExportHandle
where
T: ComponentHandle + 'static,
P: Fn(&T, ExportProgress) + Send + 'static + Clone,
D: Fn(&T, ExportOutcome) + Send + 'static + Clone,
{
let cancel = Arc::new(AtomicBool::new(false));
let cancel_worker = cancel.clone();
let ui_weak_done = ui_weak.clone();
thread::spawn(move || {
let progress_ui_weak = ui_weak;
let report_progress = move |progress: ExportProgress| {
let on_progress = on_progress.clone();
let _ = progress_ui_weak.upgrade_in_event_loop(move |ui| on_progress(&ui, progress));
};
let outcome = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
run_export(
&scene,
params,
color_space,
&output_path,
compute_target,
&workers,
local_compute,
&cancel_worker,
report_progress,
)
}))
.unwrap_or_else(|payload| {
let message = payload
.downcast_ref::<&str>()
.map(|s| (*s).to_string())
.or_else(|| payload.downcast_ref::<String>().cloned())
.unwrap_or_else(|| "the export worker panicked".to_string());
ExportOutcome::Failed(format!("Export failed unexpectedly: {message}"))
});
let _ = ui_weak_done.upgrade_in_event_loop(move |ui| on_done(&ui, outcome));
});
ExportHandle { cancel }
}