#[derive(Debug, Clone, Copy)]
pub(crate) struct ParticleRng {
state: u64,
}
impl ParticleRng {
pub(crate) fn new(seed: u64) -> Self {
let mut z = seed.wrapping_add(0x9E3779B97F4A7C15);
z = (z ^ (z >> 30)).wrapping_mul(0xBF58476D1CE4E5B9);
z = (z ^ (z >> 27)).wrapping_mul(0x94D049BB133111EB);
z ^= z >> 31;
Self {
state: if z == 0 { 0x9E3779B97F4A7C15 } else { z },
}
}
pub(crate) fn next_u64(&mut self) -> u64 {
let mut x = self.state;
x ^= x >> 12;
x ^= x << 25;
x ^= x >> 27;
self.state = x;
x.wrapping_mul(0x2545F4914F6CDD1D)
}
pub(crate) fn f32(&mut self) -> f32 {
((self.next_u64() >> 40) as f32) / (1u64 << 24) as f32
}
pub(crate) fn range(&mut self, min: f32, max: f32) -> f32 {
if min >= max {
min
} else {
min + self.f32() * (max - min)
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn same_seed_same_sequence() {
let mut a = ParticleRng::new(42);
let mut b = ParticleRng::new(42);
for _ in 0..16 {
assert_eq!(a.next_u64(), b.next_u64());
}
}
#[test]
fn different_seeds_differ() {
let mut a = ParticleRng::new(1);
let mut b = ParticleRng::new(2);
let a_seq: Vec<u64> = (0..16).map(|_| a.next_u64()).collect();
let b_seq: Vec<u64> = (0..16).map(|_| b.next_u64()).collect();
assert_ne!(a_seq, b_seq);
}
#[test]
fn range_stays_within_bounds() {
let mut rng = ParticleRng::new(7);
let mut spread = false;
for _ in 0..1000 {
let v = rng.range(0.3, 1.5);
assert!(
(0.3..1.5).contains(&v),
"value {v} out of [0.3, 1.5)"
);
spread |= v != 0.3;
}
for _ in 0..1000 {
let v = rng.range(-20.0, 20.0);
assert!(
(-20.0..20.0).contains(&v),
"value {v} out of [-20.0, 20.0)"
);
spread |= v != -20.0;
}
assert!(
spread,
"range returned the lower bound every draw"
);
}
#[test]
fn seed_zero_does_not_lock() {
let mut rng = ParticleRng::new(0);
let outputs: Vec<u64> = (0..10).map(|_| rng.next_u64()).collect();
let all_equal = outputs.iter().all(|v| *v == outputs[0]);
assert!(!all_equal, "seed 0 produced a constant stream");
}
}