diff --git a/Cargo.toml b/Cargo.toml index 8547e43..dfbbfa5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -17,13 +17,13 @@ exclude = ["/.*"] [features] default = ["std"] alloc = [] -std = ["alloc", "instant"] +std = ["alloc"] +js = ["std", "getrandom"] -[target.'cfg(all(target_arch = "wasm32", not(target_os = "wasi")))'.dependencies] -instant = { version = "0.1", optional = true } +[target.'cfg(all(any(target_arch = "wasm32", target_arch = "wasm64"), target_os = "unknown"))'.dependencies] +getrandom = { version = "0.2", features = ["js"], optional = true } -[target.'cfg(all(target_arch = "wasm32", not(target_os = "wasi")))'.dev-dependencies] -instant = { version = "0.1", features = ["wasm-bindgen"] } +[target.'cfg(all(any(target_arch = "wasm32", target_arch = "wasm64"), target_os = "unknown"))'.dev-dependencies] wasm-bindgen-test = "0.3" getrandom = { version = "0.2", features = ["js"] } diff --git a/src/global_rng.rs b/src/global_rng.rs index 6594cc1..f994902 100644 --- a/src/global_rng.rs +++ b/src/global_rng.rs @@ -5,10 +5,8 @@ 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; +// Chosen by fair roll of the dice. +const DEFAULT_RNG_SEED: u64 = 0xef6f79ed30ba75a; impl Default for Rng { /// Initialize the `Rng` from the system's random number generator. @@ -29,17 +27,7 @@ impl Rng { } thread_local! { - static RNG: Cell = 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 - })); + static RNG: Cell = Cell::new(Rng(random_seed().unwrap_or(DEFAULT_RNG_SEED))); } /// Run an operation with the current thread-local generator. @@ -190,3 +178,41 @@ pub fn f64() -> f64 { pub fn choose_multiple(source: T, amount: usize) -> Vec { with_rng(|rng| rng.choose_multiple(source, amount)) } + +#[cfg(not(all( + any(target_arch = "wasm32", target_arch = "wasm64"), + target_os = "unknown" +)))] +fn random_seed() -> Option { + use std::collections::hash_map::DefaultHasher; + use std::hash::{Hash, Hasher}; + use std::thread; + use std::time::Instant; + + let mut hasher = DefaultHasher::new(); + Instant::now().hash(&mut hasher); + thread::current().id().hash(&mut hasher); + let hash = hasher.finish(); + Some((hash << 1) | 1) +} + +#[cfg(all( + any(target_arch = "wasm32", target_arch = "wasm64"), + target_os = "unknown", + feature = "js" +))] +fn random_seed() -> Option { + // TODO(notgull): Failures should be logged somewhere. + let mut seed = [0u8; 8]; + getrandom::getrandom(&mut seed).ok()?; + Some(u64::from_ne_bytes(seed)) +} + +#[cfg(all( + any(target_arch = "wasm32", target_arch = "wasm64"), + target_os = "unknown", + not(feature = "js") +))] +fn random_seed() -> Option { + None +} diff --git a/src/lib.rs b/src/lib.rs index ec4b36d..415aae9 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -78,6 +78,21 @@ //! - `std` (enabled by default): Enables the `std` library. This is required for the global //! generator and global entropy. Without this feature, [`Rng`] can only be instantiated using //! the [`with_seed`](Rng::with_seed) method. +//! - `js`: Assumes that WebAssembly targets are being run in a JavaScript environment. See the +//! [WebAssembly Notes](#webassembly-notes) section for more information. +//! +//! # WebAssembly Notes +//! +//! For non-WASI WASM targets, there is additional sublety to consider when utilizing the global RNG. +//! By default, `std` targets will use entropy sources in the standard library to seed the global RNG. +//! However, these sources are not available by default on WASM targets outside of WASI. +//! +//! If the `js` feature is enabled, this crate will assume that it is running in a JavaScript +//! environment. At this point, the [`getrandom`] crate will be used in order to access the available +//! entropy sources and seed the global RNG. If the `js` feature is not enabled, the global RNG will +//! use a predefined seed. +//! +//! [`getrandom`]: https://crates.io/crates/getrandom #![cfg_attr(not(feature = "std"), no_std)] #![cfg_attr(docsrs, feature(doc_cfg))]