Skip to content

Commit

Permalink
feat: Add no_std mode (#50)
Browse files Browse the repository at this point in the history
  • Loading branch information
notgull authored Feb 19, 2023
1 parent 61adc59 commit 12e49ca
Show file tree
Hide file tree
Showing 4 changed files with 199 additions and 171 deletions.
2 changes: 2 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,8 @@ jobs:
if: startsWith(matrix.rust, 'nightly')
run: cargo check -Z features=dev_dep
- run: cargo test
- name: Build with no default features
run: cargo build --no-default-features
- name: Test wasm
run: wasm-pack test --headless --chrome
if: startsWith(matrix.os, 'ubuntu')
Expand Down
4 changes: 4 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,10 @@ keywords = ["simple", "fast", "rand", "random", "wyrand"]
categories = ["algorithms"]
exclude = ["/.*"]

[features]
default = ["std"]
std = []

[target.'cfg(all(target_arch = "wasm32", not(target_os = "wasi")))'.dependencies]
instant = "0.1"

Expand Down
173 changes: 173 additions & 0 deletions src/global_rng.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,173 @@
//! A global, thread-local random number generator.

use crate::Rng;

use std::cell::Cell;
use std::ops::RangeBounds;

#[cfg(all(target_arch = "wasm32", not(target_os = "wasi")))]
use instant::Instant;
#[cfg(not(all(target_arch = "wasm32", not(target_os = "wasi"))))]
use std::time::Instant;

impl Default for Rng {
/// Initialize the `Rng` from the system's random number generator.
///
/// This is equivalent to [`Rng::new()`].
#[inline]
fn default() -> Rng {
Rng::new()
}
}

impl Rng {
/// Creates a new random number generator.
#[inline]
pub fn new() -> Rng {
try_with_rng(Rng::fork).unwrap_or_else(|_| Rng::with_seed(0x4d595df4d0f33173))
}
}

thread_local! {
static RNG: Cell<Rng> = Cell::new(Rng({
use std::collections::hash_map::DefaultHasher;
use std::hash::{Hash, Hasher};
use std::thread;

let mut hasher = DefaultHasher::new();
Instant::now().hash(&mut hasher);
thread::current().id().hash(&mut hasher);
let hash = hasher.finish();
(hash << 1) | 1
}));
}

/// Run an operation with the current thread-local generator.
#[inline]
fn with_rng<R>(f: impl FnOnce(&mut Rng) -> R) -> R {
RNG.with(|rng| {
let current = rng.replace(Rng(0));

let mut restore = RestoreOnDrop { rng, current };

f(&mut restore.current)
})
}

/// Try to run an operation with the current thread-local generator.
#[inline]
fn try_with_rng<R>(f: impl FnOnce(&mut Rng) -> R) -> Result<R, std::thread::AccessError> {
RNG.try_with(|rng| {
let current = rng.replace(Rng(0));

let mut restore = RestoreOnDrop { rng, current };

f(&mut restore.current)
})
}

/// Make sure the original RNG is restored even on panic.
struct RestoreOnDrop<'a> {
rng: &'a Cell<Rng>,
current: Rng,
}

impl Drop for RestoreOnDrop<'_> {
fn drop(&mut self) {
self.rng.set(Rng(self.current.0));
}
}

/// Initializes the thread-local generator with the given seed.
#[inline]
pub fn seed(seed: u64) {
with_rng(|r| r.seed(seed));
}

/// Gives back **current** seed that is being held by the thread-local generator.
#[inline]
pub fn get_seed() -> u64 {
with_rng(|r| r.get_seed())
}

/// Generates a random `bool`.
#[inline]
pub fn bool() -> bool {
with_rng(|r| r.bool())
}

/// Generates a random `char` in ranges a-z and A-Z.
#[inline]
pub fn alphabetic() -> char {
with_rng(|r| r.alphabetic())
}

/// Generates a random `char` in ranges a-z, A-Z and 0-9.
#[inline]
pub fn alphanumeric() -> char {
with_rng(|r| r.alphanumeric())
}

/// Generates a random `char` in range a-z.
#[inline]
pub fn lowercase() -> char {
with_rng(|r| r.lowercase())
}

/// Generates a random `char` in range A-Z.
#[inline]
pub fn uppercase() -> char {
with_rng(|r| r.uppercase())
}

/// Generates a random digit in the given `base`.
///
/// Digits are represented by `char`s in ranges 0-9 and a-z.
///
/// Panics if the base is zero or greater than 36.
#[inline]
pub fn digit(base: u32) -> char {
with_rng(|r| r.digit(base))
}

/// Shuffles a slice randomly.
#[inline]
pub fn shuffle<T>(slice: &mut [T]) {
with_rng(|r| r.shuffle(slice))
}

macro_rules! integer {
($t:tt, $doc:tt) => {
#[doc = $doc]
///
/// Panics if the range is empty.
#[inline]
pub fn $t(range: impl RangeBounds<$t>) -> $t {
with_rng(|r| r.$t(range))
}
};
}

integer!(u8, "Generates a random `u8` in the given range.");
integer!(i8, "Generates a random `i8` in the given range.");
integer!(u16, "Generates a random `u16` in the given range.");
integer!(i16, "Generates a random `i16` in the given range.");
integer!(u32, "Generates a random `u32` in the given range.");
integer!(i32, "Generates a random `i32` in the given range.");
integer!(u64, "Generates a random `u64` in the given range.");
integer!(i64, "Generates a random `i64` in the given range.");
integer!(u128, "Generates a random `u128` in the given range.");
integer!(i128, "Generates a random `i128` in the given range.");
integer!(usize, "Generates a random `usize` in the given range.");
integer!(isize, "Generates a random `isize` in the given range.");
integer!(char, "Generates a random `char` in the given range.");

/// Generates a random `f32` in range `0..1`.
pub fn f32() -> f32 {
with_rng(|r| r.f32())
}

/// Generates a random `f64` in range `0..1`.
pub fn f64() -> f64 {
with_rng(|r| r.f64())
}
Loading

0 comments on commit 12e49ca

Please sign in to comment.