#[derive(Debug, Clone)]
pub struct AgentRng {
state: u64,
}
impl AgentRng {
pub(crate) fn derive(seed: &str, index: usize, name: &str) -> Self {
let mut hash = 0xcbf2_9ce4_8422_2325_u64;
let mut mix = |bytes: &[u8]| {
for &byte in bytes {
hash ^= u64::from(byte);
hash = hash.wrapping_mul(0x0000_0100_0000_01b3);
}
};
mix(seed.as_bytes());
mix(&index.to_le_bytes());
mix(name.as_bytes());
Self::from_u64(hash)
}
pub fn from_u64(seed: u64) -> Self {
Self { state: seed | 1 }
}
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)
}
pub fn choose_index(&mut self, len: usize) -> Option<usize> {
if len == 0 {
return None;
}
let len_u64 = u64::try_from(len).unwrap_or(u64::MAX);
let pick = self.next_u64() % len_u64;
Some(usize::try_from(pick).unwrap_or(len - 1))
}
}
#[cfg(test)]
mod tests {
use super::AgentRng;
#[test]
fn derivation_is_deterministic_and_agent_specific() {
let mut first = AgentRng::derive("seed-v1", 0, "alice");
let mut again = AgentRng::derive("seed-v1", 0, "alice");
let first_seq: Vec<u64> = (0..4).map(|_| first.next_u64()).collect();
let again_seq: Vec<u64> = (0..4).map(|_| again.next_u64()).collect();
assert_eq!(
first_seq, again_seq,
"same seed and identity yields the same stream"
);
let mut other = AgentRng::derive("seed-v1", 1, "bob");
let other_seq: Vec<u64> = (0..4).map(|_| other.next_u64()).collect();
assert_ne!(
first_seq, other_seq,
"a different agent yields a different stream"
);
}
}