use core::time::Duration;
use crate::util::saturating_mul_f64;
pub trait Jitter {
fn next_unit_f64(&mut self) -> f64;
#[inline]
fn apply(&mut self, base: Duration, factor: f64) -> Duration {
if factor <= 0.0 || base.is_zero() {
return base;
}
let factor = factor.min(1.0);
let r = self.next_unit_f64();
let scale = (1.0 - factor) + r * (2.0 * factor);
saturating_mul_f64(base, scale)
}
}
impl<J: Jitter + ?Sized> Jitter for &mut J {
#[inline]
fn next_unit_f64(&mut self) -> f64 {
(**self).next_unit_f64()
}
}
#[derive(Debug, Clone, Copy, Default)]
pub struct NoJitter;
impl Jitter for NoJitter {
#[inline]
fn next_unit_f64(&mut self) -> f64 {
0.5
}
}
#[derive(Debug, Clone, Copy)]
pub struct SplitMix64 {
state: u64,
}
impl SplitMix64 {
#[inline]
#[must_use]
pub const fn new(seed: u64) -> Self {
Self { state: 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)
}
}
impl Default for SplitMix64 {
#[inline]
fn default() -> Self {
Self::new(0x2545_F491_4F6C_DD1D)
}
}
impl Jitter for SplitMix64 {
#[inline]
fn next_unit_f64(&mut self) -> f64 {
const SCALE: f64 = 1.0 / (1u64 << 53) as f64;
(self.next_u64() >> 11) as f64 * SCALE
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn no_jitter_is_identity() {
let mut j = NoJitter;
let base = Duration::from_millis(100);
assert_eq!(j.apply(base, 0.5), base);
assert_eq!(j.apply(base, 1.0), base);
}
#[test]
fn unit_values_are_in_range() {
let mut rng = SplitMix64::new(7);
for _ in 0..10_000 {
let v = rng.next_unit_f64();
assert!((0.0..1.0).contains(&v), "value out of range: {v}");
}
}
#[test]
fn jitter_stays_within_symmetric_bounds() {
let mut rng = SplitMix64::new(99);
let base = Duration::from_millis(1000);
for _ in 0..10_000 {
let d = rng.apply(base, 0.5);
assert!(d >= Duration::from_millis(500));
assert!(d < Duration::from_millis(1500));
}
}
#[test]
fn seeded_streams_are_reproducible() {
let mut a = SplitMix64::new(123);
let mut b = SplitMix64::new(123);
for _ in 0..100 {
assert_eq!(a.next_u64(), b.next_u64());
}
}
}