use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering};
#[cfg(not(target_arch = "wasm32"))]
use std::thread;
use crate::plan::Issue;
#[derive(Debug, Default)]
pub struct Progress {
done: AtomicUsize,
is_cancelled: AtomicBool,
}
impl Progress {
#[must_use]
pub fn done(&self) -> usize {
self.done.load(Ordering::Relaxed)
}
pub fn cancel(&self) {
self.is_cancelled.store(true, Ordering::Relaxed);
}
#[must_use]
pub fn is_cancelled(&self) -> bool {
self.is_cancelled.load(Ordering::Relaxed)
}
}
#[derive(Debug, Clone, PartialEq)]
pub enum RunError {
Cancelled,
Refused(Vec<Issue>),
}
impl RunError {
pub(crate) fn refused(path: &str, message: &str) -> Self {
Self::Refused(vec![Issue {
path: path.to_owned(),
message: message.to_owned(),
}])
}
}
fn run_range<T>(
range: std::ops::Range<usize>,
progress: &Progress,
one: &(impl Fn(usize) -> Option<T> + Sync),
) -> Vec<Option<T>> {
range
.take_while(|_| !progress.is_cancelled())
.map(|at| {
let step = one(at);
progress.done.fetch_add(1, Ordering::Relaxed);
step
})
.collect()
}
pub(crate) fn run_all<T: Send>(
count: usize,
progress: &Progress,
one: impl Fn(usize) -> Option<T> + Sync,
) -> Result<Vec<T>, RunError> {
let finished = run_chunks(count, progress, &one);
if progress.is_cancelled() {
return Err(RunError::Cancelled);
}
Ok(finished.into_iter().flatten().flatten().collect())
}
#[cfg(not(target_arch = "wasm32"))]
fn run_chunks<T: Send>(
count: usize,
progress: &Progress,
one: &(impl Fn(usize) -> Option<T> + Sync),
) -> Vec<Vec<Option<T>>> {
let threads = thread::available_parallelism()
.map_or(1, usize::from)
.min(count.max(1));
let chunk = count.div_ceil(threads).max(1);
thread::scope(|scope| {
let handles: Vec<_> = (0..count)
.step_by(chunk)
.map(|first| {
let range = first..(first + chunk).min(count);
scope.spawn(move || run_range(range, progress, one))
})
.collect();
handles
.into_iter()
.map(|handle| handle.join().expect("a search step does not panic"))
.collect()
})
}
#[cfg(target_arch = "wasm32")]
fn run_chunks<T: Send>(
count: usize,
progress: &Progress,
one: &(impl Fn(usize) -> Option<T> + Sync),
) -> Vec<Vec<Option<T>>> {
vec![run_range(0..count, progress, one)]
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn one_thread_answers_what_many_do() {
let square = |at: usize| (!at.is_multiple_of(3)).then_some(at * at);
let progress = Progress::default();
let serial: Vec<usize> = run_range(0..40, &progress, &square)
.into_iter()
.flatten()
.collect();
let threaded = run_all(40, &Progress::default(), square).unwrap();
assert_eq!(serial, threaded);
assert_eq!(progress.done(), 40);
}
}