weavegraph 0.7.0

Graph-driven, concurrent agent workflow framework with versioned state, deterministic barrier merges, and rich diagnostics.
Documentation
//! Deterministic random number generation for reproducible behavior and testing.
//!
//! Seeded wrappers around [`rand::rngs::StdRng`] that produce the same sequence
//! for any given seed, enabling reproducible test scenarios and deterministic
//! workflow execution.

use rand::rngs::StdRng;
use rand::{RngExt, SeedableRng};
use std::collections::HashMap;

/// Seeded deterministic RNG that produces the same sequence for a given seed.
///
/// # Examples
///
/// ```rust
/// use weavegraph::utils::deterministic_rng::DeterministicRng;
///
/// let mut rng = DeterministicRng::new(42);
/// let value1 = rng.random_u64();
///
/// let mut rng2 = DeterministicRng::new(42);
/// assert_eq!(value1, rng2.random_u64());
/// ```
#[derive(Debug)]
pub struct DeterministicRng {
    rng: StdRng,
    seed: u64,
}

impl DeterministicRng {
    /// Create a new RNG seeded with `seed`.
    #[must_use]
    pub fn new(seed: u64) -> Self {
        Self {
            rng: StdRng::seed_from_u64(seed),
            seed,
        }
    }

    /// Return the original seed.
    #[must_use]
    pub fn seed(&self) -> u64 {
        self.seed
    }

    /// Reset the sequence to its initial state.
    pub fn reset(&mut self) {
        self.rng = StdRng::seed_from_u64(self.seed);
    }

    /// Create a child RNG seeded from the next value in this sequence.
    #[must_use]
    pub fn fork(&mut self) -> Self {
        Self::new(self.random_u64())
    }

    /// Draw a random `u64`.
    pub fn random_u64(&mut self) -> u64 {
        self.rng.random()
    }

    /// Draw a random `u32`.
    pub fn random_u32(&mut self) -> u32 {
        self.rng.random()
    }

    /// Draw a random `bool`.
    pub fn random_bool(&mut self) -> bool {
        self.rng.random()
    }

    /// Draw a random `f64` in `[0.0, 1.0)`.
    pub fn random_f64(&mut self) -> f64 {
        self.rng.random()
    }

    /// Draw a `u32` in `[min, max)`. Returns `min` when `min >= max`.
    pub fn random_range_u32(&mut self, min: u32, max: u32) -> u32 {
        if min >= max {
            return min;
        }
        min + (self.rng.random::<u32>() % (max - min))
    }

    /// Generate a random lowercase-ASCII string of length `len`.
    ///
    /// # Examples
    ///
    /// ```rust
    /// use weavegraph::utils::deterministic_rng::DeterministicRng;
    ///
    /// let mut rng = DeterministicRng::new(42);
    /// let s = rng.random_string(8);
    /// assert_eq!(s.len(), 8);
    /// assert!(s.chars().all(|c| c.is_ascii_lowercase()));
    /// ```
    pub fn random_string(&mut self, len: usize) -> String {
        (0..len)
            .map(|_| (b'a' + self.random_range_u32(0, 26) as u8) as char)
            .collect()
    }

    /// Generate a random alphanumeric string of length `len`.
    ///
    /// # Examples
    ///
    /// ```rust
    /// use weavegraph::utils::deterministic_rng::DeterministicRng;
    ///
    /// let mut rng = DeterministicRng::new(42);
    /// let id = rng.random_alphanumeric(12);
    /// assert_eq!(id.len(), 12);
    /// assert!(id.chars().all(|c| c.is_ascii_alphanumeric()));
    /// ```
    pub fn random_alphanumeric(&mut self, len: usize) -> String {
        const CHARS: &[u8] = b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
        (0..len)
            .map(|_| CHARS[self.random_range_u32(0, CHARS.len() as u32) as usize] as char)
            .collect()
    }

    /// Return a reference to a randomly chosen element of `choices`, or `None` if empty.
    pub fn choose<'a, T>(&mut self, choices: &'a [T]) -> Option<&'a T> {
        if choices.is_empty() {
            return None;
        }
        Some(&choices[self.random_range_u32(0, choices.len() as u32) as usize])
    }
}

/// Registry of named [`DeterministicRng`] instances, all seeded from a shared base.
#[derive(Debug)]
pub struct RngRegistry {
    rngs: HashMap<String, DeterministicRng>,
    base_seed: u64,
}

impl RngRegistry {
    /// Create a new registry with the given base seed.
    #[must_use]
    pub fn new(base_seed: u64) -> Self {
        Self {
            rngs: HashMap::new(),
            base_seed,
        }
    }

    /// Return the [`DeterministicRng`] for `name`, inserting one on first access.
    ///
    /// Each name receives a stable seed derived by hashing the name against the base seed.
    pub fn get_rng(&mut self, name: &str) -> &mut DeterministicRng {
        self.rngs.entry(name.to_owned()).or_insert_with(|| {
            let name_hash = name
                .bytes()
                .fold(0u64, |acc, b| acc.wrapping_mul(31).wrapping_add(b as u64));
            DeterministicRng::new(self.base_seed.wrapping_add(name_hash))
        })
    }

    /// Reset every RNG in the registry to its initial state.
    pub fn reset_all(&mut self) {
        for rng in self.rngs.values_mut() {
            rng.reset();
        }
    }

    /// Return the number of named RNGs in this registry.
    #[must_use]
    pub fn len(&self) -> usize {
        self.rngs.len()
    }

    /// Return `true` if no named RNGs have been created yet.
    #[must_use]
    pub fn is_empty(&self) -> bool {
        self.rngs.is_empty()
    }
}