use std::cell::{Cell, RefCell};
use rand::rngs::SmallRng;
use rand::{RngExt, SeedableRng};
pub type SelectOffsetFn = fn(u32) -> u32;
thread_local! {
static OFFSET_OVERRIDE: Cell<Option<SelectOffsetFn>> = const { Cell::new(None) };
static FALLBACK_RNG: RefCell<Option<SmallRng>> = const { RefCell::new(None) };
}
pub fn set_select_offset_override(source: Option<SelectOffsetFn>) {
OFFSET_OVERRIDE.with(|cell| cell.set(source));
}
#[doc(hidden)]
pub fn select_offset(branches: u32) -> u32 {
if branches <= 1 {
return 0;
}
match OFFSET_OVERRIDE.with(Cell::get) {
Some(source) => source(branches) % branches,
None => entropy_offset(branches),
}
}
fn entropy_offset(branches: u32) -> u32 {
FALLBACK_RNG.with(|cell| {
let mut rng = cell.borrow_mut();
let rng = rng.get_or_insert_with(|| {
use std::hash::{BuildHasher, Hasher};
let seed = std::collections::hash_map::RandomState::new()
.build_hasher()
.finish();
SmallRng::seed_from_u64(seed)
});
rng.random_range(0..branches)
})
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn zero_and_one_branch_are_always_offset_zero() {
assert_eq!(select_offset(0), 0);
assert_eq!(select_offset(1), 0);
}
#[test]
fn fallback_stays_in_range() {
set_select_offset_override(None);
for _ in 0..1000 {
let offset = select_offset(7);
assert!(offset < 7);
}
}
#[test]
fn override_wins_and_is_reduced_modulo_branches() {
fn always_five(_branches: u32) -> u32 {
5
}
set_select_offset_override(Some(always_five));
assert_eq!(select_offset(8), 5);
assert_eq!(select_offset(3), 2); set_select_offset_override(None);
}
}