use crate::engine::activation::RunContext;
#[inline]
pub(crate) fn splitmix64(mut x: u64) -> u64 {
x = x.wrapping_add(0x9E37_79B9_7F4A_7C15);
x = (x ^ (x >> 30)).wrapping_mul(0xBF58_476D_1CE4_E5B9);
x = (x ^ (x >> 27)).wrapping_mul(0x94D0_49BB_1331_11EB);
x ^ (x >> 31)
}
#[derive(Clone, Copy, Debug)]
pub struct DetRng {
state: u64,
}
impl DetRng {
#[inline]
pub fn from_context(context: RunContext, salt: u64) -> Self {
let mut state = splitmix64(context.simulation_seed);
state = splitmix64(state ^ context.tick);
state = splitmix64(state ^ (context.system_id as u64));
state = splitmix64(state ^ salt);
Self { state }
}
#[inline]
pub fn from_seed(seed: u64) -> Self {
Self {
state: splitmix64(seed),
}
}
#[inline]
pub fn next_u64(&mut self) -> u64 {
self.state = self.state.wrapping_add(0x9E37_79B9_7F4A_7C15);
let mut z = self.state;
z = (z ^ (z >> 30)).wrapping_mul(0xBF58_476D_1CE4_E5B9);
z = (z ^ (z >> 27)).wrapping_mul(0x94D0_49BB_1331_11EB);
z ^ (z >> 31)
}
#[inline]
pub fn next_f64(&mut self) -> f64 {
(self.next_u64() >> 11) as f64 * (1.0 / (1u64 << 53) as f64)
}
#[inline]
pub fn next_f32(&mut self) -> f32 {
(self.next_u64() >> 40) as f32 * (1.0 / (1u64 << 24) as f32)
}
#[inline]
pub fn next_below(&mut self, upper: u64) -> u64 {
((self.next_u64() as u128 * upper as u128) >> 64) as u64
}
#[inline]
pub fn next_index(&mut self, upper: usize) -> usize {
self.next_below(upper as u64) as usize
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn identical_context_and_salt_reproduce_sequences() {
let ctx = RunContext {
simulation_seed: 42,
tick: 7,
system_id: 3,
};
let mut a = DetRng::from_context(ctx, 1001);
let mut b = DetRng::from_context(ctx, 1001);
for _ in 0..64 {
assert_eq!(a.next_u64(), b.next_u64());
}
}
#[test]
fn distinct_salts_produce_distinct_streams() {
let ctx = RunContext {
simulation_seed: 42,
tick: 7,
system_id: 3,
};
let mut a = DetRng::from_context(ctx, 0);
let mut b = DetRng::from_context(ctx, 1);
let same = (0..16).filter(|_| a.next_u64() == b.next_u64()).count();
assert_eq!(same, 0);
}
#[test]
fn unit_floats_stay_in_range() {
let mut rng = DetRng::from_seed(9);
for _ in 0..1024 {
let x = rng.next_f64();
assert!((0.0..1.0).contains(&x));
let y = rng.next_f32();
assert!((0.0..1.0).contains(&y));
}
}
#[test]
fn next_below_respects_bounds() {
let mut rng = DetRng::from_seed(11);
assert_eq!(rng.next_below(0), 0);
for _ in 0..1024 {
assert!(rng.next_below(10) < 10);
assert!(rng.next_index(3) < 3);
}
}
}