#![cfg_attr(not(feature = "std"), no_std)]
use cu29::bundle_resources;
use cu29::prelude::*;
use cu29::resource::{BundleContext, ResourceBundle, ResourceManager};
use rand::Rng;
use rand::rand_core::{Infallible, SeedableRng, TryRng};
use rand_chacha::ChaCha8Rng;
pub mod prelude {
pub use crate::{CuRng, CuRngBundle};
pub use rand::distr::{Bernoulli, Distribution, Uniform};
pub use rand::{Rng, RngExt};
}
pub const SEED_KEY: &str = "seed";
pub const RNG_NAME: &str = "rng";
pub struct CuRng {
inner: ChaCha8Rng,
}
impl CuRng {
pub fn from_seed(seed: u64) -> Self {
Self {
inner: ChaCha8Rng::seed_from_u64(seed),
}
}
pub fn reseed(&mut self, seed: u64) {
self.inner = ChaCha8Rng::seed_from_u64(seed);
}
}
impl TryRng for CuRng {
type Error = Infallible;
fn try_next_u32(&mut self) -> Result<u32, Self::Error> {
Ok(self.inner.next_u32())
}
fn try_next_u64(&mut self) -> Result<u64, Self::Error> {
Ok(self.inner.next_u64())
}
fn try_fill_bytes(&mut self, dest: &mut [u8]) -> Result<(), Self::Error> {
self.inner.fill_bytes(dest);
Ok(())
}
}
pub struct CuRngBundle;
bundle_resources!(CuRngBundle: Rng = "rng");
impl ResourceBundle for CuRngBundle {
fn build(
bundle: BundleContext<Self>,
config: Option<&ComponentConfig>,
manager: &mut ResourceManager,
) -> CuResult<()> {
let seed = config
.and_then(|cfg| cfg.get::<u64>(SEED_KEY).transpose())
.transpose()?
.ok_or_else(|| {
CuError::from("CuRngBundle: missing required config key 'seed' (u64)")
})?;
manager.add_owned(bundle.key(CuRngBundleId::Rng), CuRng::from_seed(seed))?;
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::prelude::*;
use cu29::resource::{NamedResourceBundleDecl, resource_index_by_name};
#[test]
fn same_seed_produces_same_stream() {
let mut a = CuRng::from_seed(42);
let mut b = CuRng::from_seed(42);
for _ in 0..64 {
assert_eq!(a.next_u64(), b.next_u64());
}
}
#[test]
fn different_seeds_diverge() {
let mut a = CuRng::from_seed(1);
let mut b = CuRng::from_seed(2);
let av: [u64; 4] = core::array::from_fn(|_| a.next_u64());
let bv: [u64; 4] = core::array::from_fn(|_| b.next_u64());
assert_ne!(av, bv);
}
#[test]
fn reseed_restarts_stream() {
let mut a = CuRng::from_seed(7);
let first = a.next_u64();
for _ in 0..10 {
let _ = a.next_u64();
}
a.reseed(7);
assert_eq!(a.next_u64(), first);
}
#[test]
fn fill_bytes_is_deterministic() {
let mut a = CuRng::from_seed(0xC0FFEE);
let mut b = CuRng::from_seed(0xC0FFEE);
let mut a_bytes = [0u8; 32];
let mut b_bytes = [0u8; 32];
a.fill_bytes(&mut a_bytes);
b.fill_bytes(&mut b_bytes);
assert_eq!(a_bytes, b_bytes);
}
#[test]
fn supports_typed_sampling_api() {
let mut rng = CuRng::from_seed(0xA11CE);
let unit_f64: f64 = rng.random();
let unit_f32: f32 = rng.random();
let ranged_float: f64 = rng.random_range(-0.25..0.25);
let ranged_int: usize = rng.random_range(0..16);
let coin: bool = rng.random();
let rare_event = rng.random_bool(0.05);
let vector: [f32; 3] = rng.random();
assert!((0.0..1.0).contains(&unit_f64));
assert!((0.0..1.0).contains(&unit_f32));
assert!((-0.25..0.25).contains(&ranged_float));
assert!(ranged_int < 16);
let _ = (coin, rare_event, vector);
}
#[test]
fn supports_prebuilt_distributions() {
let mut rng = CuRng::from_seed(0xB0B);
let noise = Uniform::new(-0.05, 0.05).expect("valid float range");
let dropout = Bernoulli::new(0.02).expect("valid probability");
let sampled_noise = noise.sample(&mut rng);
let sampled_dropout = dropout.sample(&mut rng);
assert!((-0.05..0.05).contains(&sampled_noise));
let _ = sampled_dropout;
}
#[test]
fn bundle_declares_named_rng_slot() {
let names = <CuRngBundle as NamedResourceBundleDecl>::NAMES;
assert_eq!(names, &[RNG_NAME]);
assert_eq!(resource_index_by_name::<CuRngBundle>(RNG_NAME), 0);
}
}