triblespace-core 0.46.4

The triblespace core implementation.
Documentation
use rand::thread_rng;
use rand::RngCore;

use super::ExclusiveId;
use super::Id;

/// Thread-local seeded id source for deterministic simulation testing.
///
/// When the `deterministic` feature is enabled AND [`seed_ids`] has
/// been called on the current thread, [`rngid`] draws from a seeded
/// ChaCha stream instead of the OS entropy source — same seed, same
/// id sequence, reproducible executions. Without the feature this
/// module doesn't exist and `rngid` compiles to exactly the
/// production path.
#[cfg(feature = "deterministic")]
pub mod deterministic {
    use rand::rngs::StdRng;
    use rand::SeedableRng;
    use std::cell::RefCell;

    thread_local! {
        pub(super) static SOURCE: RefCell<Option<StdRng>> = const { RefCell::new(None) };
    }

    /// Seed deterministic id generation for the current thread. Every
    /// subsequent [`super::rngid`]/`genid` call on this thread draws
    /// from the seeded stream. Simulation harnesses (which run all
    /// nodes single-threaded) call this once at startup; ids stay
    /// globally unique within a run because all nodes share the one
    /// stream.
    pub fn seed_ids(seed: u64) {
        SOURCE.with(|s| *s.borrow_mut() = Some(StdRng::seed_from_u64(seed)));
    }

    /// Drop the seeded source — subsequent ids come from OS entropy
    /// again. Mostly useful for tests that want to scope determinism.
    pub fn unseed_ids() {
        SOURCE.with(|s| *s.borrow_mut() = None);
    }

    /// Fill `buf` from the seeded stream if one is installed. Returns
    /// false (buf untouched) when unseeded. Shared by [`super::rngid`]
    /// and [`crate::id::ufoid::ufoid`] so ALL id randomness drains one
    /// deterministic stream under simulation.
    pub fn try_fill(buf: &mut [u8]) -> bool {
        use rand::RngCore;
        SOURCE.with(|s| {
            s.borrow_mut().as_mut().map(|rng| rng.fill_bytes(buf)).is_some()
        })
    }
}

#[cfg(feature = "deterministic")]
pub use deterministic::{seed_ids, unseed_ids};

/// # Random Number Generated ID (RNGID)
/// Are generated by simply taking 128bits from a cryptographic random
/// source. They are easy to implement and provide the maximum possible amount
/// of entropy at the cost of locality and compressability. However UFOIDs are
/// almost universally a better choice, unless the use-case is incompatible with
/// leaking the time at which an id was minted.
pub fn rngid() -> ExclusiveId {
    #[cfg(feature = "deterministic")]
    {
        let seeded = deterministic::SOURCE.with(|s| {
            s.borrow_mut().as_mut().map(|rng| {
                let mut id = [0; 16];
                rng.fill_bytes(&mut id[..]);
                id
            })
        });
        if let Some(id) = seeded {
            return ExclusiveId::force(
                Id::new(id).expect("the probability for a zero id from the seeded stream is negligible"),
            );
        }
    }

    let mut rng = thread_rng();
    let mut id = [0; 16];
    rng.fill_bytes(&mut id[..]);

    ExclusiveId::force(Id::new(id).expect("The probability for rng = 0 should be neglegible."))
}

#[cfg(all(test, feature = "deterministic"))]
mod tests {
    use super::*;

    #[test]
    fn seeded_ids_reproduce() {
        seed_ids(42);
        let a1 = *rngid();
        let a2 = *rngid();
        seed_ids(42);
        let b1 = *rngid();
        let b2 = *rngid();
        unseed_ids();
        assert_eq!(a1, b1);
        assert_eq!(a2, b2);
        assert_ne!(a1, a2);
    }

    #[test]
    fn unseeded_ids_differ_across_reseeds_of_different_seeds() {
        seed_ids(1);
        let a = *rngid();
        seed_ids(2);
        let b = *rngid();
        unseed_ids();
        assert_ne!(a, b);
    }
}