use crate::call_catching_panics::call_catching_panics;
use crate::side_by_side_threads::run_all_side_by_side;
use std::sync::atomic::{AtomicUsize, Ordering};
pub trait ConcurrentWork<T, R> {
fn run(&self, item: &T) -> R;
}
impl<T, R, Job: Fn(&T) -> R> ConcurrentWork<T, R> for Job {
fn run(&self, item: &T) -> R {
return self(item);
}
}
pub trait ItemLabel<T> {
fn label(&self, item: &T) -> String;
}
impl<T, Namer: Fn(&T) -> String> ItemLabel<T> for Namer {
fn label(&self, item: &T) -> String {
return self(item);
}
}
type WorkerState<'a, T, R> = (&'a [T], &'a AtomicUsize, &'a (dyn ItemLabel<T> + Sync), &'a (dyn ConcurrentWork<T, R> + Sync));
fn run_job_for_item<T, R>((job, item): (&(dyn ConcurrentWork<T, R> + Sync), &T)) -> R {
return job.run(item);
}
fn take_items<T, R>((items, next, label, job): WorkerState<'_, T, R>) -> Vec<(usize, Result<R, String>)> {
let mut done: Vec<(usize, Result<R, String>)> = Vec::new();
loop {
let index: usize = next.fetch_add(1, Ordering::Relaxed);
let Some(item) = items.get(index) else { break };
let caught: std::thread::Result<R> = call_catching_panics((job, item), run_job_for_item::<T, R>);
let outcome: Result<R, String> = match caught {
Ok(value) => Ok(value),
Err(payload) => Err(format!("{} stopped because its worker panicked: {}", label.label(item), panic_words(payload.as_ref()))),
};
done.push((index, outcome));
}
return done;
}
fn result_index<R>(numbered: &(usize, Result<R, String>)) -> usize {
return numbered.0;
}
pub fn map_concurrent<T: Sync, R: Send>(
items: &[T],
workers: usize,
label: &(dyn ItemLabel<T> + Sync),
job: &(dyn ConcurrentWork<T, R> + Sync),
) -> Vec<Result<R, String>> {
let next: AtomicUsize = AtomicUsize::new(0);
let mut worker_states: Vec<WorkerState<'_, T, R>> = Vec::new();
for _worker in 0..workers.max(1).min(items.len().max(1)) {
worker_states.push((items, &next, label, job));
}
let mut results: Vec<(usize, Result<R, String>)> = Vec::new();
for joined in run_all_side_by_side(worker_states, take_items::<T, R>) {
match joined {
Ok(done) => results.extend(done),
Err(payload) => std::panic::resume_unwind(payload),
}
}
results.sort_by_key(result_index);
let mut ordered: Vec<Result<R, String>> = Vec::new();
for (_index, result) in results {
ordered.push(result);
}
return ordered;
}
fn panic_words(payload: &(dyn std::any::Any + Send)) -> String {
if let Some(text) = payload.downcast_ref::<&str>() {
return (*text).to_string();
}
if let Some(text) = payload.downcast_ref::<String>() {
return text.clone();
}
return String::from("no message");
}