use std::sync::Mutex;
const MAX_THREADS: usize = 16;
#[cfg(all(any(feature = "encoder", feature = "point_cloud_decode"), not(fuzzing)))]
pub(crate) const ATTRIBUTES_MIN_VALUES: usize = 1 << 17;
#[cfg(all(any(feature = "encoder", feature = "point_cloud_decode"), fuzzing))]
pub(crate) const ATTRIBUTES_MIN_VALUES: usize = 64;
#[cfg(all(feature = "encoder", not(fuzzing)))]
pub(crate) const PIECE: usize = 1 << 16;
#[cfg(all(feature = "encoder", fuzzing))]
pub(crate) const PIECE: usize = 16;
#[cfg(feature = "encoder")]
pub(crate) const PASS_MIN_VALUES: usize = MAX_THREADS * PIECE;
pub(crate) fn resolve(requested: i32) -> usize {
if cfg!(target_arch = "wasm32") {
return 1;
}
if requested > 0 {
return (requested as usize).min(MAX_THREADS);
}
available()
}
pub(crate) fn run_workers<T: Send>(threads: usize, worker: impl Fn() -> T + Sync) -> Vec<T> {
std::thread::scope(|scope| {
let spawned: Vec<_> = (1..threads)
.map_while(|_| {
std::thread::Builder::new()
.spawn_scoped(scope, &worker)
.ok()
})
.collect();
let mut results = vec![worker()];
results.extend(spawned.into_iter().map(|thread| {
thread
.join()
.unwrap_or_else(|panic| std::panic::resume_unwind(panic))
}));
results
})
}
#[cfg(not(target_arch = "wasm32"))]
fn available() -> usize {
std::thread::available_parallelism().map_or(1, |n| n.get().min(MAX_THREADS))
}
#[cfg(target_arch = "wasm32")]
fn available() -> usize {
let _ = MAX_THREADS;
1
}
#[cfg(feature = "encoder")]
pub(crate) fn map<T: Send>(
count: usize,
threads: usize,
work: impl Fn(usize) -> T + Sync,
) -> Vec<T> {
let threads = threads.min(count);
if threads <= 1 || cfg!(target_arch = "wasm32") {
return (0..count).map(work).collect();
}
let next = Mutex::new(0usize);
let results: Mutex<Vec<Option<T>>> = Mutex::new((0..count).map(|_| None).collect());
run_workers(threads, || loop {
let index = {
let mut next = next.lock().unwrap();
let index = *next;
*next += 1;
index
};
if index >= count {
break;
}
let value = work(index);
results.lock().unwrap()[index] = Some(value);
});
results
.into_inner()
.unwrap()
.into_iter()
.map(|value| value.expect("every index was worked"))
.collect()
}
#[cfg(feature = "encoder")]
pub(crate) fn for_each_piece_mut<T: Send>(
pieces: Vec<&mut [T]>,
threads: usize,
work: impl Fn(&mut [T]) + Sync,
) {
let threads = threads.min(pieces.len());
if threads <= 1 || cfg!(target_arch = "wasm32") {
for piece in pieces {
work(piece);
}
return;
}
let queue = Mutex::new(pieces.into_iter());
run_workers(threads, || loop {
let Some(piece) = queue.lock().unwrap().next() else {
break;
};
work(piece);
});
}
#[cfg(feature = "encoder")]
pub(crate) fn min_max(values: &[i32], threads: usize) -> Option<(i32, i32)> {
let fold = |values: &[i32]| {
values
.iter()
.fold((i32::MAX, i32::MIN), |(min, max), &value| {
(min.min(value), max.max(value))
})
};
if values.is_empty() {
return None;
}
if threads <= 1 || values.len() < PASS_MIN_VALUES {
return Some(fold(values));
}
map(values.len().div_ceil(PIECE), threads, |piece| {
fold(&values[piece * PIECE..((piece + 1) * PIECE).min(values.len())])
})
.into_iter()
.reduce(|(min_a, max_a), (min_b, max_b)| (min_a.min(min_b), max_a.max(max_b)))
}
pub(crate) fn for_each_chunk_mut<T: Send>(
data: &mut [T],
chunk: usize,
threads: usize,
work: impl Fn(usize, &mut [T]) + Sync,
) {
let chunk = chunk.max(1);
let chunks = data.len().div_ceil(chunk);
let threads = threads.min(chunks);
if threads <= 1 || cfg!(target_arch = "wasm32") {
for (index, piece) in data.chunks_mut(chunk).enumerate() {
work(index, piece);
}
return;
}
let queue = Mutex::new(data.chunks_mut(chunk).enumerate());
run_workers(threads, || loop {
let Some((index, piece)) = queue.lock().unwrap().next() else {
break;
};
work(index, piece);
});
}
#[cfg(test)]
mod tests {
use super::*;
#[cfg(feature = "encoder")]
#[test]
fn results_come_back_in_index_order_whatever_the_thread_count() {
for threads in [1, 2, 7, 16] {
let squares = map(100, threads, |i| i * i);
assert_eq!(squares, (0..100).map(|i| i * i).collect::<Vec<_>>());
}
}
#[test]
fn a_chunk_of_zero_is_a_chunk_of_one_on_any_number_of_threads() {
for threads in [1, 3] {
let mut data = vec![0u32; 10];
for_each_chunk_mut(&mut data, 0, threads, |index, piece| {
assert_eq!(piece.len(), 1);
piece[0] = index as u32;
});
assert_eq!(data, (0..10).collect::<Vec<u32>>(), "{threads} threads");
}
}
#[test]
fn every_chunk_is_worked_once_with_its_own_index() {
for threads in [1, 3, 8] {
let mut data = vec![0u32; 1000];
for_each_chunk_mut(&mut data, 64, threads, |index, piece| {
for value in piece.iter_mut() {
*value = index as u32 + 1;
}
});
for (i, value) in data.iter().enumerate() {
assert_eq!(
*value,
(i / 64) as u32 + 1,
"element {i} at {threads} threads"
);
}
}
}
#[test]
fn a_request_of_zero_means_the_machine_and_anything_else_is_taken_as_given() {
assert!((1..=MAX_THREADS).contains(&resolve(0)));
assert_eq!(resolve(1), 1);
if !cfg!(target_arch = "wasm32") {
assert_eq!(resolve(3), 3);
assert_eq!(resolve(100_000), MAX_THREADS, "a request past the most");
}
}
#[test]
fn the_calling_thread_is_one_of_the_workers() {
let caller = std::thread::current().id();
let ran = run_workers(4, || std::thread::current().id());
assert_eq!(ran.len(), 4);
assert_eq!(ran[0], caller);
assert_eq!(run_workers(1, || std::thread::current().id()), [caller]);
}
}