#![allow(clippy::disallowed_types)]
use core::ops::Bound;
use core::ops::RangeBounds;
#[derive(Clone)]
struct Rng(u128);
const MULTIPLIER: u128 = 0x2360_ED05_1FC6_5DA4_4385_DF64_9FCC_F645;
impl Rng {
#[inline]
#[allow(unused)]
pub const fn from_seed(seed: u128) -> Self {
Self((seed << 1) | 1)
}
#[inline]
fn next_u32(&mut self) -> u32 {
self.next_u64() as u32
}
#[inline]
fn next_u64(&mut self) -> u64 {
self.0 = self.0.wrapping_mul(MULTIPLIER);
const XSHIFT: u32 = 64; const ROTATE: u32 = 122; let rot = (self.0 >> ROTATE) as u32;
let xsl = ((self.0 >> XSHIFT) as u64) ^ (self.0 as u64);
xsl.rotate_right(rot)
}
}
macro_rules! impl_get_bounds {
(float, $fn_name:ident, $t:ty) => {
fn $fn_name<R: RangeBounds<$t>>(bounds: R) -> ($t, $t) {
let start = match bounds.start_bound() {
Bound::Included(&b) | Bound::Excluded(&b) => b,
Bound::Unbounded => <$t>::MIN,
};
let end = match bounds.end_bound() {
Bound::Included(&b) | Bound::Excluded(&b) => b,
Bound::Unbounded => <$t>::MAX,
};
(start, end)
}
};
(int, $fn_name:ident, $t:ty) => {
fn $fn_name<R: RangeBounds<$t>>(bounds: R) -> ($t, $t) {
let start = match bounds.start_bound() {
Bound::Included(&b) => b,
Bound::Excluded(&b) => b.checked_add(1).unwrap(),
Bound::Unbounded => <$t>::MIN,
};
let end = match bounds.end_bound() {
Bound::Included(&b) => b.checked_add(1).unwrap(),
Bound::Excluded(&b) => b,
Bound::Unbounded => <$t>::MAX,
};
(start, end)
}
};
}
impl_get_bounds!(float, get_f32_bounds, f32);
impl_get_bounds!(int, get_usize_bounds, usize);
impl_get_bounds!(int, get_i32_bounds, i32);
#[derive(Clone)]
pub struct PerchanceContext(Rng);
impl PerchanceContext {
pub const fn new(seed: u128) -> Self {
Self(Rng::from_seed(seed))
}
pub fn get_bool(&mut self) -> bool {
self.0.next_u64() & 1 == 1
}
pub fn get_u32(&mut self) -> u32 {
self.0.next_u32()
}
pub fn get_u64(&mut self) -> u64 {
self.0.next_u64()
}
pub fn get_u128(&mut self) -> u128 {
(u128::from(self.0.next_u64()) << 64) | u128::from(self.0.next_u64())
}
pub fn u64_less_than(&mut self, max: u64) -> u64 {
if max == 0 {
return 0;
}
let significant_bits = 64 - max.leading_zeros();
let mask = if significant_bits == 64 {
u64::MAX
} else {
(1 << significant_bits) - 1
};
loop {
let num = self.get_u64() & mask;
if num < max {
return num;
}
}
}
pub fn usize_less_than(&mut self, end: usize) -> usize {
self.u64_less_than(end as u64) as usize
}
pub fn choose<'slice, T>(&mut self, slice: &'slice [T]) -> &'slice T {
&slice[self.usize_less_than(slice.len())]
}
pub fn choose_mut<'slice, T>(&mut self, slice: &'slice mut [T]) -> &'slice mut T {
&mut slice[self.usize_less_than(slice.len())]
}
pub fn choose_it<T>(&mut self, it: impl Iterator<Item = T>) -> Option<T> {
let mut chosen = None;
for (i, value) in it.enumerate() {
if self.usize_less_than(i + 1) == 0 {
chosen = Some(value);
}
}
chosen
}
pub fn shuffle<T>(&mut self, slice: &mut [T]) {
for i in 0..slice.len().saturating_sub(1) {
let j = self.uniform_range_usize(i..slice.len());
slice.swap(i, j);
}
}
pub fn uniform_range_i32<R: RangeBounds<i32>>(&mut self, range: R) -> i32 {
let (start, end) = get_i32_bounds(range);
let start = i64::from(start);
let end = i64::from(end);
let range_size = end - start;
assert!(range_size >= 0);
let value = start + self.u64_less_than(range_size as u64) as i64;
value as i32
}
pub fn uniform_range_usize<R: RangeBounds<usize>>(&mut self, range: R) -> usize {
let (start, end) = get_usize_bounds(range);
assert!(end >= start);
start + self.usize_less_than(end - start)
}
pub fn uniform_f32(&mut self) -> f32 {
f32::from_bits(0x3F80_0000 | (self.get_u32() >> 9)) - 1.0
}
pub fn uniform_f64(&mut self) -> f64 {
let exponent = 1023; let mantissa = self.get_u64() >> 12;
f64::from_bits((exponent << 52) | mantissa) - 1.0
}
pub fn uniform_range_f32<R: RangeBounds<f32>>(&mut self, range: R) -> f32 {
self.uniform_range_f32_raw(get_f32_bounds(range))
}
#[inline]
fn uniform_range_f32_raw(&mut self, (start, end): (f32, f32)) -> f32 {
self.uniform_f32() * (end - start) + start
}
pub fn normal_f32(&mut self) -> f32 {
let mut r;
#[allow(clippy::blocks_in_conditions)]
while {
r = (-2.0 * self.uniform_f64().ln()).sqrt();
!r.is_finite()
} {}
let angle = 2.0 * core::f64::consts::PI * self.uniform_f64();
let s = angle.sin();
(r * s) as f32
}
#[cfg(feature = "macaw")]
pub fn normal_vec2(&mut self) -> macaw::Vec2 {
let mut r;
#[allow(clippy::blocks_in_conditions)]
while {
r = (-2.0 * self.uniform_f64().ln()).sqrt();
!r.is_finite()
} {}
let angle = 2.0 * core::f64::consts::PI * self.uniform_f64();
let (s, c) = angle.sin_cos();
macaw::Vec2::new((r * s) as f32, (r * c) as f32)
}
#[cfg(feature = "macaw")]
pub fn normal_vec3(&mut self) -> macaw::Vec3 {
self.normal_vec2().extend(self.normal_f32())
}
#[cfg(feature = "macaw")]
pub fn uniform_square_vec2<R: RangeBounds<f32>>(&mut self, range: R) -> macaw::Vec2 {
let bounds = get_f32_bounds(range);
macaw::Vec2::new(
self.uniform_range_f32_raw(bounds),
self.uniform_range_f32_raw(bounds),
)
}
#[cfg(feature = "macaw")]
pub fn uniform_cube_vec3<R: RangeBounds<f32>>(&mut self, range: R) -> macaw::Vec3 {
let bounds = get_f32_bounds(range);
macaw::Vec3::new(
self.uniform_range_f32_raw(bounds),
self.uniform_range_f32_raw(bounds),
self.uniform_range_f32_raw(bounds),
)
}
#[cfg(feature = "macaw")]
pub fn uniform_bounds_vec3(&mut self, bounds: macaw::BoundingBox) -> macaw::Vec3 {
macaw::Vec3::new(
self.uniform_range_f32_raw((bounds.min.x, bounds.max.x)),
self.uniform_range_f32_raw((bounds.min.y, bounds.max.y)),
self.uniform_range_f32_raw((bounds.min.z, bounds.max.z)),
)
}
#[cfg(feature = "macaw")]
pub fn uniform_rectangle_vec2(&mut self, min: macaw::Vec2, max: macaw::Vec2) -> macaw::Vec2 {
macaw::Vec2::new(
self.uniform_range_f32_raw((min.x, max.x)),
self.uniform_range_f32_raw((min.y, max.y)),
)
}
#[cfg(feature = "macaw")]
pub fn uniform_circle_edge_vec2(&mut self) -> macaw::Vec2 {
let angle = self.uniform_f32() * core::f32::consts::TAU;
let x = angle.cos();
let y = angle.sin();
macaw::Vec2::new(x, y)
}
#[cfg(feature = "macaw")]
pub fn uniform_circle_area_vec2(&mut self) -> macaw::Vec2 {
let radius = self.uniform_f32();
let edge_point = self.uniform_circle_edge_vec2();
edge_point * radius
}
#[cfg(feature = "macaw")]
#[deprecated = "Deprecated in favor of the equivalent `uniform_circle_area_vec2()`"]
pub fn uniform_disc_vec2(&mut self) -> macaw::Vec2 {
self.uniform_circle_area_vec2()
}
#[cfg(feature = "macaw")]
pub fn uniform_sphere_surface_vec3(&mut self) -> macaw::Vec3 {
let x = self.uniform_f32();
let y = self.uniform_f32();
let z = 1.0f32 - 2.0f32 * x;
let xy = ((0.0f32).max(1.0f32 - z * z)).sqrt();
let sn = (core::f32::consts::TAU * y).sin();
let cs = (core::f32::consts::TAU * y).cos();
macaw::Vec3::new(sn * xy, cs * xy, z)
}
#[cfg(feature = "macaw")]
pub fn uniform_sphere_volume_vec3(&mut self) -> macaw::Vec3 {
let radius = self.uniform_f32();
let surface_point = self.uniform_sphere_surface_vec3();
surface_point * radius
}
pub fn weighted_choice_f32(&mut self, weights: &[f32]) -> usize {
assert!(!weights.is_empty());
let sum: f64 = weights.iter().map(|&w| f64::from(w)).sum();
assert!(sum.is_finite());
if sum == 0.0 {
self.usize_less_than(weights.len())
} else {
let pos = self.uniform_f64() * sum;
let mut cumulative_sum = 0.0;
for (i, w) in weights.iter().take(weights.len() - 1).enumerate() {
cumulative_sum += f64::from(*w);
if pos < cumulative_sum {
return i;
}
}
weights.len() - 1
}
}
pub fn weighted_choices_f32(&mut self, weights: &[f32]) -> WeightedSampler<'_> {
use ordered_float::NotNan;
assert!(!weights.is_empty());
let zero = NotNan::<f64>::default();
let mut sum = zero;
let mut cum = Vec::with_capacity(weights.len());
cum.resize(weights.len(), zero);
for (c, v) in cum.iter_mut().zip(weights) {
sum += NotNan::new(f64::from(*v)).expect("weighted_choices_f32 was given a NaN weight");
*c = sum;
}
WeightedSampler {
context: self,
cumulative_weights: cum,
}
}
}
pub struct WeightedSampler<'a> {
context: &'a mut PerchanceContext,
cumulative_weights: Vec<ordered_float::NotNan<f64>>,
}
impl<'a> WeightedSampler<'a> {
pub fn sample(&mut self) -> usize {
let weight_sum = *self.cumulative_weights.last().unwrap();
let value = ordered_float::NotNan::new(self.context.uniform_f64()).unwrap() * weight_sum;
match self.cumulative_weights.binary_search(&value) {
Ok(index) | Err(index) => index,
}
}
}
mod global_ctx {
use super::*;
use once_cell::sync::Lazy;
use std::sync::Mutex;
#[cfg(not(target_family = "wasm"))]
use std::time::SystemTime;
#[cfg(not(target_family = "wasm"))]
use std::time::UNIX_EPOCH;
#[cfg(not(target_family = "wasm"))]
const TWO_POW_64: u128 = 2u128.pow(64);
#[cfg(not(target_family = "wasm"))]
const TWO_POW_96: u128 = 2u128.pow(96);
static GLOBAL_RNG: Lazy<Mutex<PerchanceContext>> =
Lazy::new(|| Mutex::new(PerchanceContext::new(0)));
static HAS_BEEN_SEEDED: std::sync::atomic::AtomicBool =
std::sync::atomic::AtomicBool::new(false);
pub fn global<'a>() -> std::sync::MutexGuard<'a, PerchanceContext> {
assert!(
global_has_been_seeded(),
"You must call `perchance::seed_global` before using the global perchance instance."
);
GLOBAL_RNG.lock().unwrap()
}
pub fn seed_global(seed: u128) {
HAS_BEEN_SEEDED.store(true, std::sync::atomic::Ordering::SeqCst);
*global() = PerchanceContext::new(seed);
}
#[cfg(not(target_family = "wasm"))]
pub fn gen_time_seed() -> u128 {
SystemTime::now()
.duration_since(UNIX_EPOCH)
.map(|duration| {
u128::from(duration.as_secs()) | (u128::from(duration.subsec_millis()) * TWO_POW_64) | (u128::from(duration.subsec_micros()) * TWO_POW_96) })
.unwrap()
}
pub fn global_has_been_seeded() -> bool {
HAS_BEEN_SEEDED.load(std::sync::atomic::Ordering::SeqCst)
}
}
pub use global_ctx::*;