use std::sync::atomic::{AtomicU32, AtomicU64, Ordering};
use std::sync::Mutex;
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
#[repr(u32)]
pub enum Phase {
NarrowPhase = 0,
SelfIntersections = 1,
CandidatePoints = 2,
Registries = 3,
Arrangements = 4,
Cells = 5,
Winding = 6,
Assemble = 7,
ExactBoolean = 8,
}
impl Phase {
pub const ALL: [Phase; 9] = [
Phase::NarrowPhase,
Phase::SelfIntersections,
Phase::CandidatePoints,
Phase::Registries,
Phase::Arrangements,
Phase::Cells,
Phase::Winding,
Phase::Assemble,
Phase::ExactBoolean,
];
pub fn name(self) -> &'static str {
match self {
Phase::NarrowPhase => "narrow phase",
Phase::SelfIntersections => "self intersections",
Phase::CandidatePoints => "candidate points",
Phase::Registries => "registries",
Phase::Arrangements => "arrangements",
Phase::Cells => "cells",
Phase::Winding => "winding",
Phase::Assemble => "assemble",
Phase::ExactBoolean => "exact boolean",
}
}
pub fn id(self) -> u32 {
self as u32
}
pub fn from_id(id: u32) -> Option<Phase> {
Phase::ALL.get(id as usize).copied()
}
}
type Callback = Box<dyn Fn(Phase, Option<f64>) + Send + Sync>;
const REPORTS_PER_PHASE: u64 = 100;
pub struct ProgressReporter {
callback: Mutex<Callback>,
phase: AtomicU32,
done: AtomicU64,
total: AtomicU64,
next: AtomicU64,
step: AtomicU64,
}
impl std::fmt::Debug for ProgressReporter {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("ProgressReporter")
.field("phase", &Phase::from_id(self.phase.load(Ordering::Relaxed)))
.field("done", &self.done.load(Ordering::Relaxed))
.field("total", &self.total.load(Ordering::Relaxed))
.finish()
}
}
impl ProgressReporter {
pub fn new<F>(callback: F) -> Self
where
F: Fn(Phase, Option<f64>) + Send + Sync + 'static,
{
Self {
callback: Mutex::new(Box::new(callback)),
phase: AtomicU32::new(Phase::NarrowPhase.id()),
done: AtomicU64::new(0),
total: AtomicU64::new(0),
next: AtomicU64::new(u64::MAX),
step: AtomicU64::new(u64::MAX),
}
}
pub fn begin_phase(&self, phase: Phase, total: u64) {
let step = (total / REPORTS_PER_PHASE).max(1);
self.phase.store(phase.id(), Ordering::Relaxed);
self.total.store(total, Ordering::Relaxed);
self.done.store(0, Ordering::Relaxed);
self.step.store(step, Ordering::Relaxed);
self.next
.store(if total == 0 { u64::MAX } else { step }, Ordering::Relaxed);
self.emit(phase, if total == 0 { None } else { Some(0.0) });
}
#[inline]
pub fn advance(&self, n: u64) {
let done = self.done.fetch_add(n, Ordering::Relaxed) + n;
if done < self.next.load(Ordering::Relaxed) {
return;
}
self.report_at(done);
}
#[cold]
fn report_at(&self, done: u64) {
let step = self.step.load(Ordering::Relaxed);
self.next.store(done.saturating_add(step), Ordering::Relaxed);
let total = self.total.load(Ordering::Relaxed);
let Some(phase) = Phase::from_id(self.phase.load(Ordering::Relaxed)) else {
return;
};
let fraction = if total == 0 {
None
} else {
Some((done as f64 / total as f64).clamp(0.0, 1.0))
};
self.emit(phase, fraction);
}
fn emit(&self, phase: Phase, fraction: Option<f64>) {
if let Ok(cb) = self.callback.lock() {
cb(phase, fraction);
}
}
}
#[inline]
pub fn begin_phase(progress: Option<&ProgressReporter>, phase: Phase, total: u64) {
if let Some(p) = progress {
p.begin_phase(phase, total);
}
}
#[cfg(feature = "parallel")]
pub fn maybe_par_map_ct_progress<T, F>(
n: usize,
threshold: usize,
token: Option<&crate::cancel::CancelToken>,
progress: Option<&ProgressReporter>,
f: F,
) -> Option<Vec<T>>
where
T: Send,
F: Fn(usize) -> T + Sync + Send,
{
match progress {
None => crate::par::maybe_par_map_ct(n, threshold, token, f),
Some(p) => crate::par::maybe_par_map_ct(n, threshold, token, |i| {
let out = f(i);
p.advance(1);
out
}),
}
}
#[cfg(not(feature = "parallel"))]
pub fn maybe_par_map_ct_progress<T, F>(
n: usize,
threshold: usize,
token: Option<&crate::cancel::CancelToken>,
progress: Option<&ProgressReporter>,
f: F,
) -> Option<Vec<T>>
where
F: Fn(usize) -> T,
{
match progress {
None => crate::par::maybe_par_map_ct(n, threshold, token, f),
Some(p) => crate::par::maybe_par_map_ct(n, threshold, token, |i| {
let out = f(i);
p.advance(1);
out
}),
}
}
#[cfg(test)]
#[path = "progress_tests.rs"]
mod tests;