use std::ops::Range;
use henad_core::authoring::primitives::rng::xorshift64;
use rayon::prelude::*;
pub const STATS_CHUNK: usize = 8192;
#[macro_export]
macro_rules! for_each_chunk_mut {
($items:expr, $chunk:expr, min_leaf $min:expr, |$c:ident, $base:ident, $slice:ident| $body:block) => {{
let chunk = ($chunk).max(1);
let min_leaf = ($min).max(1);
{
use $crate::__macro_support::rayon::prelude::*;
$items
.par_chunks_mut(chunk)
.enumerate()
.with_min_len(min_leaf)
.for_each(|($c, $slice)| {
let $base = $c * chunk;
$body
});
}
}};
($items:expr, $chunk:expr, |$c:ident, $base:ident, $slice:ident| $body:block) => {{
let chunk = ($chunk).max(1);
{
use $crate::__macro_support::rayon::prelude::*;
$items.par_chunks_mut(chunk).enumerate().for_each(|($c, $slice)| {
let $base = $c * chunk;
$body
});
}
}};
($a:expr, $b:expr, $d:expr, $chunk:expr, |$c:ident, $base:ident, $sa:ident, $sb:ident, $sd:ident| $body:block) => {{
let chunk = ($chunk).max(1);
{
use $crate::__macro_support::rayon::prelude::*;
$a.par_chunks_mut(chunk)
.zip($b.par_chunks_mut(chunk))
.zip($d.par_chunks_mut(chunk))
.enumerate()
.for_each(|($c, (($sa, $sb), $sd))| {
let $base = $c * chunk;
$body
});
}
}};
}
#[inline]
fn chunk_range(c: usize, chunk: usize, len: usize) -> Range<usize> {
let start = c * chunk;
start..(start + chunk).min(len)
}
pub fn reduce_chunks<A, M, F>(len: usize, chunk: usize, map: M, fold: F, init: A) -> A
where
A: Send,
M: Fn(Range<usize>) -> A + Send + Sync,
F: Fn(A, A) -> A,
{
let chunk = chunk.max(1);
let n = len.div_ceil(chunk);
let partials: Vec<A> = (0..n)
.into_par_iter()
.map(|c| map(chunk_range(c, chunk, len)))
.collect();
partials.into_iter().fold(init, fold)
}
#[inline]
pub fn chunk_seed(base: u64, tick: u64, c: usize) -> u64 {
let mixed = base ^ tick.wrapping_mul(0x9E37_79B9_7F4A_7C15) ^ (c as u64).wrapping_mul(0xBF58_476D_1CE4_E5B9);
xorshift64(mixed | 1)
}
#[inline]
pub fn advance_tick_seed(seed: u64, tick: u64) -> u64 {
xorshift64(seed ^ tick)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn every_index_is_visited_exactly_once() {
for len in [0usize, 1, 7, 4096, 4097, 8193] {
let mut items = vec![0u32; len];
for_each_chunk_mut!(items, 100, |_c, base, slice| {
for (k, v) in slice.iter_mut().enumerate() {
*v = (base + k) as u32 + 1;
}
});
let expected: Vec<u32> = (1..=len as u32).collect();
assert_eq!(items, expected, "len {len}");
}
}
#[test]
fn reduction_covers_a_ragged_tail() {
for len in [0usize, 1, 99, 100, 101] {
let total = reduce_chunks(len, 10, |r| r.sum::<usize>(), |a, b| a + b, 0);
assert_eq!(total, (0..len).sum::<usize>(), "len {len}");
}
}
#[test]
fn reduction_folds_in_chunk_order() {
let order = reduce_chunks(
50,
10,
|r| vec![r.start],
|mut a, b| {
a.extend(b);
a
},
Vec::new(),
);
assert_eq!(order, vec![0, 10, 20, 30, 40]);
}
#[test]
fn chunk_seeds_differ_by_chunk_and_tick() {
let a = chunk_seed(1, 0, 0);
assert_ne!(a, chunk_seed(1, 0, 1), "chunk index must change the stream");
assert_ne!(a, chunk_seed(1, 1, 0), "tick must change the stream");
assert_eq!(a, chunk_seed(1, 0, 0), "seeding must be reproducible");
}
#[test]
fn chunk_seed_is_never_zero() {
for tick in 0..64u64 {
for c in 0..64 {
assert_ne!(chunk_seed(0, tick, c), 0, "tick {tick} chunk {c}");
}
}
}
}