diff --git a/crates/rand/src/global/number.rs b/crates/rand/src/global/number.rs index 5336161..1b10b07 100644 --- a/crates/rand/src/global/number.rs +++ b/crates/rand/src/global/number.rs @@ -1,5 +1,6 @@ // SPDX-License-Identifier: MPL-2.0 use super::global; +use crate::shared; use rand::Rng; #[byond_fn] @@ -23,17 +24,11 @@ pub fn random_int_signed(secure: Option) -> i32 { } #[byond_fn] -pub fn random_range_int_unsigned(mut min: u32, mut max: u32, secure: Option) -> u32 { - if min > max { - std::mem::swap(&mut min, &mut max); - } - global(secure).gen_range(min..=max) +pub fn random_range_int_unsigned(min: u32, max: u32, secure: Option) -> u32 { + shared::random_range_int_unsigned(&mut global(secure), min, max) } #[byond_fn] -pub fn random_range_int_signed(mut min: i32, mut max: i32, secure: Option) -> i32 { - if min > max { - std::mem::swap(&mut min, &mut max); - } - global(secure).gen_range(min..=max) +pub fn random_range_int_signed(min: i32, max: i32, secure: Option) -> i32 { + shared::random_range_int_signed(&mut global(secure), min, max) } diff --git a/crates/rand/src/global/pick.rs b/crates/rand/src/global/pick.rs index cef82c9..30090f9 100644 --- a/crates/rand/src/global/pick.rs +++ b/crates/rand/src/global/pick.rs @@ -1,53 +1,14 @@ // SPDX-License-Identifier: MPL-2.0 use super::global; +use crate::shared; use meowtonin::{ByondResult, ByondValue}; -use rand::{ - distributions::{Distribution, WeightedIndex}, - Rng, -}; #[byond_fn] -pub fn pick(options: ByondValue, secure: Option) -> ByondResult> { - if !options.is_list() { - return Ok(None); - } - let length = options.length()?; - match length { - 0 => return Ok(None), - 1 => return options.read_list_index(&1).map(Some), - _ => {} - } - let idx = global(secure).gen_range::(1..=length); - options.read_list_index(&idx).map(Some) +pub fn pick(options: ByondValue, secure: Option) -> ByondResult { + shared::pick(&mut global(secure), options) } #[byond_fn] -pub fn pick_weighted(options: ByondValue, secure: Option) -> ByondResult> { - if !options.is_list() { - return Ok(None); - } - let length = options.length::()?; - match length { - 0 => return Ok(None), - 1 => return options.read_list_index(&1).map(Some), - _ => {} - } - let options = options.read_assoc_list()?; - let weights = options - .iter() - .map(|[_, weight]| weight.get_number()) - .filter_map(|weight| weight.ok()) - .filter(|weight| weight.is_normal() && weight.is_sign_positive()) - .collect::>(); - match weights.len() { - 0 => return Ok(None), - 1 => return Ok(options.get(1).and_then(|entry| entry.get(1)).cloned()), - _ => {} - } - let dist = match WeightedIndex::new(weights) { - Ok(dist) => dist, - Err(_) => return Ok(None), - }; - let idx = dist.sample(&mut global(secure)); - Ok(options.get(idx).and_then(|entry| entry.get(2)).cloned()) +pub fn pick_weighted(options: ByondValue, secure: Option) -> ByondResult { + shared::pick_weighted(&mut global(secure), options) } diff --git a/crates/rand/src/global/prob.rs b/crates/rand/src/global/prob.rs index d00737a..211a322 100644 --- a/crates/rand/src/global/prob.rs +++ b/crates/rand/src/global/prob.rs @@ -1,17 +1,13 @@ // SPDX-License-Identifier: MPL-2.0 use super::global; -use rand::Rng; +use crate::shared; #[byond_fn] pub fn prob(probability: f64, secure: Option) -> bool { - if !probability.is_finite() { - return true; - } - let probability = (probability / 100.0).clamp(0.0, 1.0); - global(secure).gen_bool(probability) + shared::prob(&mut global(secure), probability) } #[byond_fn] pub fn prob_ratio(numerator: u32, denominator: u32, secure: Option) -> bool { - global(secure).gen_ratio(numerator, denominator) + shared::prob_ratio(&mut global(secure), numerator, denominator) } diff --git a/crates/rand/src/global/string.rs b/crates/rand/src/global/string.rs index 885f4ef..9397554 100644 --- a/crates/rand/src/global/string.rs +++ b/crates/rand/src/global/string.rs @@ -1,16 +1,10 @@ // SPDX-License-Identifier: MPL-2.0 use super::global; -use rand::{ - distributions::{Alphanumeric, Bernoulli, Distribution}, - Rng, -}; +use crate::shared; #[byond_fn] pub fn random_string_alphanumeric(length: usize, secure: Option) -> String { - let mut rng = global(secure); - (0..=length) - .map(|_| rng.sample(Alphanumeric) as char) - .collect() + shared::random_string_alphanumeric(&mut global(secure), length) } #[byond_fn] @@ -21,20 +15,11 @@ pub fn replace_chars_prob( skip_whitespace: Option, secure: Option, ) -> String { - if !prob.is_normal() || !prob.is_sign_positive() { - return input; - } - let skip_whitespace = skip_whitespace.unwrap_or(false); - let mut rng = global(secure); - let distro = - Bernoulli::new((prob as f64 / 100.0).clamp(0.0, 1.0)).expect("invalid probability, wtf???"); - let mut output = String::with_capacity(input.len() * replacement.len()); // Allocate for worst case scenario. - input.chars().for_each(|c| { - if (!skip_whitespace || !c.is_whitespace()) && distro.sample(&mut rng) { - output.push_str(&replacement); - } else { - output.push(c); - } - }); - output + shared::replace_chars_prob( + &mut global(secure), + input, + replacement, + prob, + skip_whitespace, + ) } diff --git a/crates/rand/src/instance/number.rs b/crates/rand/src/instance/number.rs index 7b87855..65ff808 100644 --- a/crates/rand/src/instance/number.rs +++ b/crates/rand/src/instance/number.rs @@ -1,5 +1,6 @@ // SPDX-License-Identifier: MPL-2.0 use super::INSTANCES; +use crate::shared; use aneri_core::ByondSlotKey; use rand::{ distributions::{ @@ -15,18 +16,6 @@ where { INSTANCES.lock().get_mut(src).map(|rng| rng.gen()) } - -fn range_impl(src: ByondSlotKey, range: Range) -> Option -where - Output: SampleUniform, - Range: SampleRange, -{ - INSTANCES - .lock() - .get_mut(src) - .map(|rng| rng.gen_range(range)) -} - #[byond_fn] pub fn instanced_random_byte(src: ByondSlotKey) -> Option { rand_impl(src) @@ -48,25 +37,17 @@ pub fn instanced_random_int_signed(src: ByondSlotKey) -> Option { } #[byond_fn] -pub fn instanced_random_range_int_unsigned( - src: ByondSlotKey, - mut min: u32, - mut max: u32, -) -> Option { - if min > max { - std::mem::swap(&mut min, &mut max); - } - range_impl(src, min..=max) +pub fn instanced_random_range_int_unsigned(src: ByondSlotKey, min: u32, max: u32) -> Option { + INSTANCES + .lock() + .get_mut(src) + .map(|rng| shared::random_range_int_unsigned(rng, min, max)) } #[byond_fn] -pub fn instanced_random_range_int_signed( - src: ByondSlotKey, - mut min: i32, - mut max: i32, -) -> Option { - if min > max { - std::mem::swap(&mut min, &mut max); - } - range_impl(src, min..=max) +pub fn instanced_random_range_int_signed(src: ByondSlotKey, min: i32, max: i32) -> Option { + INSTANCES + .lock() + .get_mut(src) + .map(|rng| shared::random_range_int_signed(rng, min, max)) } diff --git a/crates/rand/src/instance/pick.rs b/crates/rand/src/instance/pick.rs index ba37f7f..b8bb143 100644 --- a/crates/rand/src/instance/pick.rs +++ b/crates/rand/src/instance/pick.rs @@ -1,5 +1,6 @@ // SPDX-License-Identifier: MPL-2.0 use super::INSTANCES; +use crate::shared; use aneri_core::ByondSlotKey; use meowtonin::{ByondResult, ByondValue}; use rand::{ @@ -9,22 +10,11 @@ use rand::{ #[byond_fn] pub fn instanced_pick(src: ByondSlotKey, options: ByondValue) -> ByondResult> { - if !options.is_list() { - return Ok(None); - } - let length = options.length::()?; - match length { - 0 => return Ok(None), - 1 => return options.read_list_index(&1).map(Some), - _ => {} - } - let mut instances = INSTANCES.lock(); - let rng = match instances.get_mut(src) { - Some(rng) => rng, - None => return Ok(None), - }; - let idx = rng.gen_range::(1..=length); - options.read_list_index(&idx).map(Some) + INSTANCES + .lock() + .get_mut(src) + .map(|rng| shared::pick(rng, options)) + .transpose() } #[byond_fn] @@ -32,36 +22,9 @@ pub fn instanced_pick_weighted( src: ByondSlotKey, options: ByondValue, ) -> ByondResult> { - if !options.is_list() { - return Ok(None); - } - let length = options.length::()?; - match length { - 0 => return Ok(None), - 1 => return options.read_list_index(&1).map(Some), - _ => {} - } - let options = options.read_assoc_list()?; - let weights = options - .iter() - .map(|[_, weight]| weight.get_number()) - .filter_map(|weight| weight.ok()) - .filter(|weight| weight.is_normal() && weight.is_sign_positive()) - .collect::>(); - match weights.len() { - 0 => return Ok(None), - 1 => return Ok(options.get(1).and_then(|entry| entry.get(1)).cloned()), - _ => {} - } - let dist = match WeightedIndex::new(weights) { - Ok(dist) => dist, - Err(_) => return Ok(None), - }; - let mut instances = INSTANCES.lock(); - let rng = match instances.get_mut(src) { - Some(rng) => rng, - None => return Ok(None), - }; - let idx = dist.sample(rng); - Ok(options.get(idx).and_then(|entry| entry.get(2)).cloned()) + INSTANCES + .lock() + .get_mut(src) + .map(|rng| shared::pick_weighted(rng, options)) + .transpose() } diff --git a/crates/rand/src/instance/prob.rs b/crates/rand/src/instance/prob.rs index 7508c02..c2b0546 100644 --- a/crates/rand/src/instance/prob.rs +++ b/crates/rand/src/instance/prob.rs @@ -1,18 +1,14 @@ // SPDX-License-Identifier: MPL-2.0 use super::INSTANCES; +use crate::shared; use aneri_core::ByondSlotKey; -use rand::Rng; #[byond_fn] pub fn instanced_prob(src: ByondSlotKey, probability: f64) -> Option { - if !probability.is_finite() { - return Some(true); - } - let probability = (probability / 100.0).clamp(0.0, 1.0); INSTANCES .lock() .get_mut(src) - .map(|rng| rng.gen_bool(probability)) + .map(|rng| shared::prob(rng, probability)) } #[byond_fn] @@ -20,5 +16,5 @@ pub fn instanced_prob_ratio(src: ByondSlotKey, numerator: u32, denominator: u32) INSTANCES .lock() .get_mut(src) - .map(|rng| rng.gen_ratio(numerator, denominator)) + .map(|rng| shared::prob_ratio(rng, numerator, denominator)) } diff --git a/crates/rand/src/instance/string.rs b/crates/rand/src/instance/string.rs index 755c307..545932f 100644 --- a/crates/rand/src/instance/string.rs +++ b/crates/rand/src/instance/string.rs @@ -1,43 +1,26 @@ // SPDX-License-Identifier: MPL-2.0 use super::INSTANCES; +use crate::shared; use aneri_core::ByondSlotKey; -use rand::{ - distributions::{Alphanumeric, Bernoulli, Distribution}, - Rng, -}; #[byond_fn] pub fn instanced_random_string_alphanumeric(src: ByondSlotKey, length: usize) -> Option { - INSTANCES.lock().get_mut(src).map(|rng| { - (0..=length) - .map(|_| rng.sample(Alphanumeric) as char) - .collect() - }) + INSTANCES + .lock() + .get_mut(src) + .map(|rng| shared::random_string_alphanumeric(rng, length)) } #[byond_fn] -pub fn instnaced_replace_chars_prob( +pub fn instanced_replace_chars_prob( src: ByondSlotKey, input: String, replacement: String, prob: f32, skip_whitespace: Option, ) -> Option { - if !prob.is_normal() || !prob.is_sign_positive() { - return Some(input); - } - let skip_whitespace = skip_whitespace.unwrap_or(false); - INSTANCES.lock().get_mut(src).map(|rng| { - let distro = Bernoulli::new((prob as f64 / 100.0).clamp(0.0, 1.0)) - .expect("invalid probability, wtf???"); - let mut output = String::with_capacity(input.len() * replacement.len()); // Allocate for worst case scenario. - input.chars().for_each(|c| { - if (!skip_whitespace || !c.is_whitespace()) && distro.sample(rng) { - output.push_str(&replacement); - } else { - output.push(c); - } - }); - output - }) + INSTANCES + .lock() + .get_mut(src) + .map(|rng| shared::replace_chars_prob(rng, input, replacement, prob, skip_whitespace)) } diff --git a/crates/rand/src/lib.rs b/crates/rand/src/lib.rs index e863f0e..36d294f 100644 --- a/crates/rand/src/lib.rs +++ b/crates/rand/src/lib.rs @@ -11,6 +11,9 @@ extern crate meowtonin; pub mod global; pub mod instance; +pub mod shared; + +pub(crate) use instance::dispatcher::RngDispatcher; pub fn cleanup() { global::reseed_global_rng(); diff --git a/crates/rand/src/shared.rs b/crates/rand/src/shared.rs new file mode 100644 index 0000000..32df3a6 --- /dev/null +++ b/crates/rand/src/shared.rs @@ -0,0 +1,11 @@ +// SPDX-License-Identifier: MPL-2.0 + +mod number; +mod pick; +mod prob; +mod string; + +pub(crate) use number::*; +pub(crate) use pick::*; +pub(crate) use prob::*; +pub(crate) use string::*; diff --git a/crates/rand/src/shared/number.rs b/crates/rand/src/shared/number.rs new file mode 100644 index 0000000..c258389 --- /dev/null +++ b/crates/rand/src/shared/number.rs @@ -0,0 +1,23 @@ +// SPDX-License-Identifier: MPL-2.0 +use crate::RngDispatcher; +use rand::Rng; + +pub(crate) fn random_range_int_unsigned(rng: &mut Gen, mut min: u32, mut max: u32) -> u32 +where + Gen: Rng, +{ + if min > max { + std::mem::swap(&mut min, &mut max); + } + rng.gen_range(min..=max) +} + +pub(crate) fn random_range_int_signed(rng: &mut Gen, mut min: i32, mut max: i32) -> i32 +where + Gen: Rng, +{ + if min > max { + std::mem::swap(&mut min, &mut max); + } + rng.gen_range(min..=max) +} diff --git a/crates/rand/src/shared/pick.rs b/crates/rand/src/shared/pick.rs new file mode 100644 index 0000000..c5bc705 --- /dev/null +++ b/crates/rand/src/shared/pick.rs @@ -0,0 +1,67 @@ +// SPDX-License-Identifier: MPL-2.0 +use crate::RngDispatcher; +use meowtonin::{ByondResult, ByondValue}; +use rand::{ + distributions::{Distribution, WeightedIndex}, + Rng, +}; + +pub(crate) fn pick(rng: &mut Gen, options: ByondValue) -> ByondResult +where + Gen: Rng, +{ + if !options.is_list() { + return Ok(ByondValue::null()); + } + let length = options.length::()?; + match length { + 0 => return Ok(ByondValue::null()), + 1 => return options.read_list_index(&1), + _ => {} + } + let idx = rng.gen_range::(1..=length); + options.read_list_index(&idx) +} + +pub(crate) fn pick_weighted(rng: &mut Gen, options: ByondValue) -> ByondResult +where + Gen: Rng, +{ + if !options.is_list() { + return Ok(ByondValue::null()); + } + let length = options.length::()?; + match length { + 0 => return Ok(ByondValue::null()), + 1 => return options.read_list_index(&1), + _ => {} + } + let options = options.read_assoc_list()?; + let weights = options + .iter() + .map(|[_, weight]| weight.get_number()) + .filter_map(|weight| weight.ok()) + .filter(|weight| weight.is_normal() && weight.is_sign_positive()) + .collect::>(); + match weights.len() { + 0 => return Ok(ByondValue::null()), + 1 => { + return Ok(options + .get(1) + .and_then(|entry| entry.get(1)) + .cloned() + .unwrap_or_default()) + } + _ => {} + } + let dist = match WeightedIndex::new(weights) { + Ok(dist) => dist, + Err(_) => return Ok(ByondValue::null()), + }; + let idx = dist.sample(rng); + Ok(options + .get(idx) + .and_then(|entry| entry.get(2)) + .cloned() + .unwrap_or_default()) +} diff --git a/crates/rand/src/shared/prob.rs b/crates/rand/src/shared/prob.rs new file mode 100644 index 0000000..63ffec1 --- /dev/null +++ b/crates/rand/src/shared/prob.rs @@ -0,0 +1,23 @@ +// SPDX-License-Identifier: MPL-2.0 +use crate::RngDispatcher; +use rand::Rng; + +pub(crate) fn prob(rng: &mut Gen, probability: f64) -> bool +where + Gen: Rng, +{ + if !probability.is_finite() { + return true; + } else if probability <= 0.0 { + return false; + } + let probability = (probability / 100.0).clamp(0.0, 1.0); + rng.gen_bool(probability) +} + +pub(crate) fn prob_ratio(rng: &mut Gen, numerator: u32, denominator: u32) -> bool +where + Gen: Rng, +{ + rng.gen_ratio(numerator, denominator) +} diff --git a/crates/rand/src/shared/string.rs b/crates/rand/src/shared/string.rs new file mode 100644 index 0000000..feda31a --- /dev/null +++ b/crates/rand/src/shared/string.rs @@ -0,0 +1,41 @@ +// SPDX-License-Identifier: MPL-2.0 +use rand::{ + distributions::{Alphanumeric, Bernoulli, Distribution}, + Rng, +}; + +pub(crate) fn random_string_alphanumeric(rng: &mut Gen, length: usize) -> String +where + Gen: Rng, +{ + (0..=length) + .map(|_| rng.sample(Alphanumeric) as char) + .collect() +} + +pub(crate) fn replace_chars_prob( + rng: &mut Gen, + input: String, + replacement: String, + prob: f32, + skip_whitespace: Option, +) -> String +where + Gen: Rng, +{ + if !prob.is_normal() || !prob.is_sign_positive() { + return input; + } + let skip_whitespace = skip_whitespace.unwrap_or(false); + let distro = + Bernoulli::new((prob as f64 / 100.0).clamp(0.0, 1.0)).expect("invalid probability, wtf???"); + let mut output = String::with_capacity(input.len() * replacement.len()); // Allocate for worst case scenario. + input.chars().for_each(|c| { + if (!skip_whitespace || !c.is_whitespace()) && distro.sample(rng) { + output.push_str(&replacement); + } else { + output.push(c); + } + }); + output +}