af_core/
random.rs

1// Copyright © 2020 Alexandra Frydl
2//
3// This Source Code Form is subject to the terms of the Mozilla Public
4// License, v. 2.0. If a copy of the MPL was not distributed with this
5// file, You can obtain one at http://mozilla.org/MPL/2.0/.
6
7//! Random number generation.
8
9use crate::prelude::*;
10use parking_lot::Mutex;
11use rand::distributions::uniform::{SampleRange, SampleUniform};
12use rand::distributions::{self, Distribution};
13use rand::seq::SliceRandom;
14use rand::{Rng as _, SeedableRng as _};
15use rand_xoshiro::Xoshiro256StarStar;
16use std::cell::RefCell;
17
18/// Returns `true` with a given probability.
19///
20/// The `probability` is the chance from `0.0` (never) to `1.0` (always) that
21/// this function returns `true`.
22pub fn chance(probability: f64) -> bool {
23  THREAD_RNG.with(|rng| rng.borrow_mut().gen_chance(probability))
24}
25
26/// Fills a slice with random bytes.
27pub fn fill_bytes(bytes: &mut [u8]) {
28  THREAD_RNG.with(|rng| rng.borrow_mut().fill_bytes(bytes))
29}
30
31/// Generates a random value.
32pub fn random<T: Random>() -> T {
33  THREAD_RNG.with(|rng| T::random_with(&mut rng.borrow_mut()))
34}
35
36/// Generates a random number within a range.
37pub fn range<T: SampleUniform>(range: impl SampleRange<T>) -> T {
38  THREAD_RNG.with(|rng| rng.borrow_mut().gen_range(range))
39}
40
41/// Returns `true` with a probability expressed by the ratio between two given
42/// numbers.
43pub fn ratio<T: Number + SampleUniform>(numerator: T, denominator: T) -> bool {
44  THREAD_RNG.with(|rng| rng.borrow_mut().gen_ratio(numerator, denominator))
45}
46
47/// Randomly shuffles a slice in place.
48pub fn shuffle<T>(slice: &mut [T]) {
49  THREAD_RNG.with(|rng| rng.borrow_mut().shuffle(slice))
50}
51
52/// A trait for types that can be created randomly.
53pub trait Random: Sized {
54  /// Returns a random value using the given `Rng`.
55  fn random_with(rng: &mut Rng) -> Self;
56
57  /// Returns a random value.
58  fn random() -> Self {
59    random()
60  }
61}
62
63// Implement `Random` for all types that can be used with `rng.gen()`.
64
65impl<T> Random for T
66where
67  distributions::Standard: Distribution<T>,
68{
69  fn random_with(rng: &mut Rng) -> Self {
70    rng.inner.gen()
71  }
72}
73
74/// A random number generator.
75#[derive(Clone)]
76pub struct Rng {
77  inner: Xoshiro256StarStar,
78}
79
80/// The global RNG to use to create thread-local RNGs.
81static GLOBAL_RNG: Lazy<Mutex<Rng>> =
82  Lazy::new(|| Mutex::new(Rng { inner: Xoshiro256StarStar::from_entropy() }));
83
84thread_local! {
85  /// The thread-local RNG.
86  static THREAD_RNG: RefCell<Rng> = {
87    let mut global_rng = GLOBAL_RNG.lock();
88    let thread_rng = global_rng.clone();
89
90    global_rng.inner.long_jump();
91
92    RefCell::new(thread_rng)
93  };
94}
95
96impl Rng {
97  /// Creates a new `Rng` with a random seed.
98  pub fn new() -> Rng {
99    THREAD_RNG.with(|rng| {
100      let mut thread_rng = rng.borrow_mut();
101      let local_rng = thread_rng.clone();
102
103      thread_rng.inner.jump();
104
105      local_rng
106    })
107  }
108
109  /// Fills a slice with random bytes.
110  pub fn fill_bytes(&mut self, bytes: &mut [u8]) {
111    self.inner.fill(bytes);
112  }
113
114  /// Generates a random value.
115  pub fn gen<T: Random>(&mut self) -> T {
116    T::random_with(self)
117  }
118
119  /// Returns `true` with a given probability.
120  ///
121  /// The probability is the chance from `0.0` (never) to `1.0` (always) that
122  /// this function returns `true`.
123  pub fn gen_chance(&mut self, probability: f64) -> bool {
124    probability > self.gen()
125  }
126
127  /// Generates a random number within a given range.
128  pub fn gen_range<T: SampleUniform>(&mut self, range: impl SampleRange<T>) -> T {
129    self.inner.gen_range(range)
130  }
131
132  /// Returns `true` with a probability expressed by the ratio between two given
133  /// numbers.
134  pub fn gen_ratio<T: Number + SampleUniform>(&mut self, numerator: T, denominator: T) -> bool {
135    debug_assert!(denominator > T::zero(), "The denominator of a ratio must be greater than zero.");
136
137    numerator > self.gen_range(T::zero()..denominator)
138  }
139
140  /// Randomly shuffles a slice in place.
141  pub fn shuffle<T>(&mut self, slice: &mut [T]) {
142    slice.shuffle(&mut self.inner);
143  }
144}
145
146impl Default for Rng {
147  fn default() -> Self {
148    Self::new()
149  }
150}