use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::{LazyLock, Mutex};
use rand::SeedableRng;
use rand::rngs::StdRng;
static ACTIVE: AtomicBool = AtomicBool::new(false);
static SEEDED: LazyLock<Mutex<StdRng>> = LazyLock::new(|| match *crate::cnf::RAND_SEED {
Some(seed) => {
ACTIVE.store(true, Ordering::Relaxed);
Mutex::new(StdRng::seed_from_u64(seed))
}
None => Mutex::new(StdRng::seed_from_u64(0)),
});
#[inline]
pub fn with_rng<T>(f: impl FnOnce(&mut dyn rand::RngCore) -> T) -> T {
LazyLock::force(&SEEDED);
if ACTIVE.load(Ordering::Relaxed) {
return f(&mut *SEEDED.lock().unwrap_or_else(|e| e.into_inner()));
}
f(&mut rand::rng())
}
pub fn reseed(seed: u64) {
*SEEDED.lock().unwrap_or_else(|e| e.into_inner()) = StdRng::seed_from_u64(seed);
ACTIVE.store(true, Ordering::Relaxed);
}
#[cfg(test)]
mod tests {
use rand::Rng;
use super::*;
#[test]
#[serial_test::serial]
fn reseed_makes_output_deterministic() {
struct Restore(bool);
impl Drop for Restore {
fn drop(&mut self) {
ACTIVE.store(self.0, Ordering::Relaxed);
}
}
let _restore = Restore(ACTIVE.load(Ordering::Relaxed));
fn draw() -> (Vec<u64>, Vec<f64>, String) {
let ints = (0..16).map(|_| with_rng(|r| r.random_range(0u64..1_000_000))).collect();
let floats = (0..16).map(|_| with_rng(|r| r.random::<f64>())).collect();
let s: String =
with_rng(|r| (0..64).map(|_| r.random_range(b'a'..=b'z') as char).collect());
(ints, floats, s)
}
reseed(42);
let first = draw();
reseed(42);
let second = draw();
assert_eq!(first, second);
reseed(43);
let third = draw();
assert_ne!(first, third);
}
}