1use std::{collections::{HashMap, HashSet}, hash::Hash};
60
61use num::{ Float, Integer };
62use paste::paste;
63use serde::{Deserialize, Serialize, de::{Error, MapAccess, SeqAccess, Visitor}, ser::{SerializeSeq, SerializeStruct}};
64
65pub type DiceT = (i32,i32);
66
67#[derive(Debug, Clone, Copy, Serialize, PartialEq, Eq)]
69pub enum DiceRollMatrixMod {
70 Add(u8),
71 Div(u8),
73 DivUp(u8),
75 Mul(u8),
76 Sub(u8),
77}
78
79impl <'de> Deserialize<'de> for DiceRollMatrixMod {
80 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
81 where D: serde::Deserializer<'de>
82 {
83 struct ModVis;
84 impl<'de> Visitor<'de> for ModVis {
85 type Value = DiceRollMatrixMod;
86
87 fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
88 formatter.write_str("a case-insensitive dice modifier thingy")
89 }
90
91 fn visit_map<A>(self, mut map: A) -> Result<Self::Value, A::Error>
92 where A: MapAccess<'de>,
93 {
94 if let Some((key, value)) = map.next_entry::<String, u8>()? {
95 match key.to_lowercase().as_str() {
96 "add" => Ok(DiceRollMatrixMod::Add(value)),
97 "div" => Ok(DiceRollMatrixMod::Div(value)),
98 "divup"|"div_up" => Ok(DiceRollMatrixMod::DivUp(value)),
99 "mul" => Ok(DiceRollMatrixMod::Mul(value)),
100 "sub" => Ok(DiceRollMatrixMod::Sub(value)),
101 _ => Err(A::Error::unknown_field(&key, &["add","div","divup","div_up","mul","sub"]))
102 }
103 } else {
104 Err(A::Error::custom("empty modifier thingy"))
105 }
106 }
107 }
108
109 deserializer.deserialize_map(ModVis)
110 }
111}
112
113trait DiceRollMatrixModifier {
114 fn drmm(&self, drmm: DiceRollMatrixMod) -> i32;
115}
116
117impl DiceRollMatrixModifier for i32 {
118 fn drmm(&self, drmm: DiceRollMatrixMod) -> i32 {
119 match drmm {
120 DiceRollMatrixMod::Add(v) => self + v as i32,
121 DiceRollMatrixMod::Div(v) => self / v as i32,
122 DiceRollMatrixMod::DivUp(v) => {
123 let v = v as i32;
124 (self + v - 1) / v
125 }
126 DiceRollMatrixMod::Mul(v) => self * v as i32,
127 DiceRollMatrixMod::Sub(v) => self - v as i32,
128 }
129 }
130}
131
132#[derive(Debug, Clone, PartialEq, Eq)]
134pub enum DiceRollMatrix {
135 Exact { value: i32 },
136 Percentage(u8), Chance(u8, Box<DiceRollMatrix>),
138 Multi(u8, u8),
139 MultiWithMod(u8, u8, DiceRollMatrixMod),
140}
141
142impl DiceRollMatrix {
143 pub fn roll(&self) -> i32 {
144 match self {
145 Self::Exact { value } => *value as i32,
146 Self::Multi(num, sides) => (*num).d(*sides as usize) as i32,
147 Self::MultiWithMod(num, sides, drmm) =>
148 Self::Multi(*num, *sides).roll().drmm(*drmm),
149 Self::Percentage(p) => if 1.d100() <= *p { 1 } else { 0 },
150 Self::Chance(p, drm) =>
151 if 1.d100() > *p { 0 }
152 else { drm.roll() },
153 }
154 }
155}
156
157impl From<i32> for DiceRollMatrix {
158 fn from(value: i32) -> Self {
159 Self::Exact { value }
160 }
161}
162
163impl From<u8> for DiceRollMatrix {
164 fn from(value: u8) -> Self {
165 Self::Exact { value: value as i32 }
166 }
167}
168
169impl <'de> Deserialize<'de> for DiceRollMatrix {
170 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
171 where
172 D: serde::Deserializer<'de>
173 {
174 struct DRMVis;
175 impl <'de> Visitor<'de> for DRMVis {
176 type Value = DiceRollMatrix;
177
178 fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
179 formatter.write_str("dice roll matrix thingy")
180 }
181
182 fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
183 where E: Error,
184 {
185 if let Some(percstr) = v.strip_suffix('%') {
186 let n = percstr.parse::<u8>().map_err(E::custom)?;
187 return Ok(DiceRollMatrix::Percentage(n));
188 }
189 Err(E::custom(format!("'{v}' is an invalid string from DiceRollMatrix")))
190 }
191
192 fn visit_u64<E>(self, v: u64) -> Result<Self::Value, E>
193 where E: Error,
194 {
195 if v <= u8::MAX as u64 {
196 Ok(DiceRollMatrix::Exact { value: v as i32 })
197 } else {
198 Err(E::custom(format!("'{v}' is too large for u8")))
199 }
200 }
201
202 fn visit_map<A>(self, mut map: A) -> Result<Self::Value, A::Error>
203 where A: MapAccess<'de>,
204 {
205 let (key, raw): (String, serde_json::Value) =
206 map.next_entry()?.ok_or_else(|| A::Error::custom("expceted a single-field object"))?;
207
208 match key.as_str() {
209 "value" => {
210 let v: u8 = serde_json::from_value(raw).map_err(A::Error::custom)?;
211 Ok(DiceRollMatrix::Exact { value: v as i32 })
212 }
213
214 "chance" => {
215 let arr: Vec<serde_json::Value> = serde_json::from_value(raw).map_err(A::Error::custom)?;
217 if arr.len() != 2 {
218 return Err(A::Error::custom("chance must be [pct, matrix]"));
219 }
220
221 let pct: u8 = serde_json::from_value(arr[0].clone()).map_err(A::Error::custom)?;
222 let inner: DiceRollMatrix = serde_json::from_value(arr[1].clone()).map_err(A::Error::custom)?;
223 Ok(DiceRollMatrix::Chance(pct, Box::new(inner)))
224 }
225
226 "range" => {
227 let arr: Vec<serde_json::Value> = serde_json::from_value(raw).map_err(A::Error::custom)?;
228 if arr.len() != 2 {
229 return Err(A::Error::custom("range must be [a, b]"));
230 }
231 let a: i32 = serde_json::from_value(arr[0].clone()).map_err(A::Error::custom)?;
232 let b: i32 = serde_json::from_value(arr[1].clone()).map_err(A::Error::custom)?;
233 let (modf, delta) = if a > b {
234 log::warn!("Range values require an U-turn from [{a},{b}] to [{b},{a}]. \
235 See to changing that although we'll let that pass… for now.");
236 let delta = a - b;
237 (a - 1 - delta, delta as u8)
238 } else {
239 let delta = b - a;
240 (b - 1 - delta, delta as u8)
241 };
242 if modf.abs() > u8::MAX as i32 {
243 return Err(A::Error::custom(format!("Modifier {modf} doesn't fit into u8…")));
244 }
245 Ok(if delta == 0 {
246 DiceRollMatrix::Exact { value: 0 }
247 } else {
248 DiceRollMatrix::MultiWithMod(1, delta, if modf < 0 { DiceRollMatrixMod::Sub(modf as u8)} else { DiceRollMatrixMod::Add(modf as u8) })
249 })
250 }
251
252 _ => Err(A::Error::custom(format!("unknown offender '{key}' in DiceRollMatrix")))
253 }
254 }
255
256 fn visit_seq<A>(self, mut seq: A) -> Result<Self::Value, A::Error>
257 where A: SeqAccess<'de>,
258 {
259 let a: u8 = seq.next_element()?.ok_or_else(|| A::Error::custom("missing 1st element"))?;
260 let b: u8 = seq.next_element()?.ok_or_else(|| A::Error::custom("missing 2nd element"))?;
261 if let Some(modf) = seq.next_element::<DiceRollMatrixMod>()? {
262 Ok(DiceRollMatrix::MultiWithMod(a,b,modf))
263 } else {
264 Ok(DiceRollMatrix::Multi(a,b))
265 }
266 }
267 }
268
269 deserializer.deserialize_any(DRMVis)
270 }
271}
272
273impl Serialize for DiceRollMatrix {
274 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
275 where S: serde::Serializer
276 {
277 match self {
278 Self::Exact { value } => {
279 let mut st = serializer.serialize_struct("Exact", 1)?;
280 st.serialize_field("value", value)?;
281 st.end()
282 }
283
284 Self::Multi(a,b) => {
285 let mut seq = serializer.serialize_seq(Some(2))?;
286 seq.serialize_element(a)?;
287 seq.serialize_element(b)?;
288 seq.end()
289 }
290
291 Self::MultiWithMod(a,b,m) => {
292 let mut seq = serializer.serialize_seq(Some(3))?;
293 seq.serialize_element(a)?;
294 seq.serialize_element(b)?;
295 seq.serialize_element(m)?;
296 seq.end()
297 }
298
299 Self::Percentage(p) => serializer.serialize_str(&format!("{p}%")),
300
301 Self::Chance(p, drm) => {
302 let mut st = serializer.serialize_struct("Chance", 1)?;
303 st.serialize_field("chance", &(p, drm))?;
304 st.end()
305 }
306 }
307 }
308}
309
310pub trait DiceExt {
324 fn d(&self, sides: usize) -> Self;
326 fn d2(&self) -> Self;
328 fn d3(&self) -> Self;
330 fn d4(&self) -> Self;
332 fn d5(&self) -> Self;
334 fn d6(&self) -> Self;
336 fn d8(&self) -> Self;
338 fn d10(&self) -> Self;
340 fn d12(&self) -> Self;
342 fn d20(&self) -> Self;
344 fn d100(&self) -> Self;
346}
347
348pub trait HiLo {
350 fn hi(&self) -> bool;
352 fn lo(&self) -> bool;
354}
355
356pub trait PercentageVariance {
358 #[deprecated(since = "0.3.11", note = "Use `jitter_percentage` instead.")]
359 fn delta(&self, percentage: i32) -> Self;
360 fn jitter_percentage(&self, percentage: f64) -> Self;
362}
363
364pub trait FixedNumberVariance<T: Float> {
366 #[deprecated(since="0.3.10", note="Use `jitter_within` insted. This will be removed soon.")]
367 fn upto_delta(&self, upto: T) -> T;
368 fn jitter_within(&self, upto: T) -> T;
370}
371
372pub trait IsOne {
374 fn is_one(&self) -> bool;
378}
379
380macro_rules! implement_isone_prim {
381 ($prim:expr) => {paste!{
382 impl IsOne for [<i $prim>] { #[inline(always)] fn is_one(&self) -> bool {*self == 1 }}
383 impl IsOne for &[<i $prim>] { #[inline(always)] fn is_one(&self) -> bool {**self == 1 }}
384 impl IsOne for [<u $prim>] { #[inline(always)] fn is_one(&self) -> bool {*self == 1 }}
385 impl IsOne for &[<u $prim>] { #[inline(always)] fn is_one(&self) -> bool {**self == 1 }}
386 }};
387}
388implement_isone_prim!(8);
390implement_isone_prim!(16);
391implement_isone_prim!(32);
392implement_isone_prim!(64);
393implement_isone_prim!(128);
394implement_isone_prim!(size);
395
396pub trait InclusiveRandomRange<T> {
397 fn random_of(&self) -> T;
398}
399
400impl InclusiveRandomRange<i32> for std::ops::RangeInclusive<i32> {
401 fn random_of(&self) -> i32 {
409 let (mut start, mut end) = (*self.start(), *self.end());
410 if start > end {
412 std::mem::swap(&mut start, &mut end);
413 }
414 let start = start as i64;
415 let end = end as i64;
416 let sides = end - start + 1;
417 (start + (engine::GLOBAL_REACTOR_U64.roll(sides as u64) - 1) as i64) as i32
419 }
421}
422
423impl InclusiveRandomRange<f64> for std::ops::RangeInclusive<f64> {
424 fn random_of(&self) -> f64 {
425 let (mut start, mut end) = (*self.start(), *self.end());
426 if start > end { std::mem::swap(&mut start, &mut end); }let raw_bits = engine::GLOBAL_REACTOR_U64.roll(u64::MAX);
429 let max_m = (1u64 << 53) - 1; start + ((raw_bits & max_m) as f64 / max_m as f64) * (end - start)
431 }
433}
434
435impl InclusiveRandomRange<f32> for std::ops::RangeInclusive<f32> {
436 fn random_of(&self) -> f32 {
437 let (mut start, mut end) = (*self.start(), *self.end());
438 if start > end { std::mem::swap(&mut start, &mut end); }let raw_bits = engine::GLOBAL_REACTOR_U64.roll(u64::MAX);
441 let max_m = (1u32 << 24) - 1; start + ((raw_bits as u32 & max_m) as f32 / max_m as f32) * (end - start)
443 }
445}
446
447impl InclusiveRandomRange<char> for std::ops::RangeInclusive<char> {
448 fn random_of(&self) -> char {
449 let (mut start, mut end) = (*self.start(), *self.end());
450 if start > end { std::mem::swap(&mut start, &mut end); }let u_start = start as u32;
453 let u_end = end as u32;
454 let range = u_end - u_start + 1;
455 loop {
456 let offt = engine::GLOBAL_REACTOR_U64.roll(range as u64) as u32;
457 let maybe = u_start + offt;
458 if !(0xD800..=0xDFFF).contains(&maybe) {
460 return unsafe { char::from_u32_unchecked(maybe) }
461 }
462 }
463 }
464}
465
466pub trait RandomOf<T> : Clone {
467 type Output;
468 fn random_of(&self) -> Self::Output;
469}
470
471pub trait PlainRandomOf : Clone {
472 type Output;
473 fn random_of() -> Self::Output;
474}
475
476pub trait KeyedRandomOf<K,T> : Clone {
477 type Output;
478 fn random_of(&self) -> Self::Output;
479}
480
481impl<T> RandomOf<T> for Vec<T>
482where T: Clone
483{
484 type Output = T;
485 fn random_of(&self) -> Self::Output {
490 if self.is_empty() { panic!("Empty Vec - can't pick a random from that. Anyway… Ta-ta 'til that's fixed.")}
491 T::clone(&self[1.d(self.len())-1])
492 }
493}
494
495impl<T> RandomOf<T> for HashSet<T>
496where T: Clone
497{
498 type Output = T;
499 fn random_of(&self) -> Self::Output {
504 if self.is_empty() { panic!("Empty HashSet - can't pick a random from that. Anyway… Ta-ta 'til that's fixed.")}
505 let Some(ent) = self.iter().nth((1_usize.d(self.len()) - 1) as usize) else {
506 panic!("For some reason the HashSet has less entries in it than .len() suggests?!");
507 };
508 T::clone(ent)
509 }
510}
511
512impl <T> RandomOf<T> for HashMap<String, T>
513where T: Clone
514{
515 type Output = T;
516 fn random_of(&self) -> Self::Output {
517 if self.is_empty() { panic!("Empty HashMap - can't pick a random from that. Anyway… Ta-ta 'til that's fixed.")}
518 let Some((_,ent)) = self.iter().nth((1_usize.d(self.len()) - 1) as usize) else {
519 panic!("For some reason the HashMap has less entries in it than .len() suggests?!");
520 };
521 T::clone(ent)
522 }
523}
524
525impl <K,T> KeyedRandomOf<K,T> for HashMap<K,T>
526where T: Clone, K: Hash + Clone
527{
528 type Output = T;
529 fn random_of(&self) -> Self::Output {
530 if self.is_empty() { panic!("Empty HashMap - can't pick a random from that. Anyway… Ta-ta 'til that's fixed.")}
531 let Some((_,ent)) = self.iter().nth((1_usize.d(self.len()) - 1) as usize) else {
532 panic!("For some reason the HashMap has less entries in it than .len() suggests?!");
533 };
534 T::clone(ent)
535 }
536}
537
538#[macro_export]
539macro_rules! lo {() => {{ use dicebag::{DiceExt, HiLo}; 1_i32.d2().lo() }}}
541
542#[macro_export]
543macro_rules! hi {() => {{ use dicebag::{HiLo, lo}; !lo!() }}}
545
546#[macro_export]
547macro_rules! percentage_chance_of {
558 ($chance:expr, f $v:expr) => {{
559 use dicebag::DiceExt;
560 if 1_i32.d100() <= $chance { $v } else { 0.0 }
561 }};
562
563 ($chance:expr, $v:expr) => {
564 if 1_i32.d100() <= $chance { $v } else { 0 }
565 };
566}
567
568macro_rules! implement_sign_dependant_diceext {
569 ($t:ty, signed) => {paste! {
570 #[inline(always)] fn [<diceabs _ $t>](num: $t) -> $t {num.abs()}
571 #[inline(always)] fn [<dicerev _ $t>](num: $t) -> $t {-num}
572 #[inline(always)] fn [<dicelt0 _ $t>](num: $t) -> bool { num < 0 }
573 }};
574 ($t:ty, unsigned) => {paste! {
575 #[inline(always)] fn [<diceabs _ $t>](num: $t) -> $t {num}
576 #[inline(always)] fn [<dicerev _ $t>](num: $t) -> $t {num}
577 #[inline(always)] fn [<dicelt0 _ $t>](_: $t) -> bool { false }
578 }};
579}
580
581implement_sign_dependant_diceext!(i8, signed);
582implement_sign_dependant_diceext!(i16, signed);
583implement_sign_dependant_diceext!(i32, signed);
584implement_sign_dependant_diceext!(i64, signed);
585implement_sign_dependant_diceext!(i128, signed);
586implement_sign_dependant_diceext!(isize, signed);
587implement_sign_dependant_diceext!(u8, unsigned);
588implement_sign_dependant_diceext!(u16, unsigned);
589implement_sign_dependant_diceext!(u32, unsigned);
590implement_sign_dependant_diceext!(u64, unsigned);
591implement_sign_dependant_diceext!(u128, unsigned);
592implement_sign_dependant_diceext!(usize, unsigned);
593
594mod engine {
595 use std::{cell::UnsafeCell, sync::atomic::AtomicBool};
596 use paste::paste;
597
598 macro_rules! const_chaos_engine_crng_vals {
599 (for $([$t:ty, $bits:literal bits, $init:literal, $mul:expr, $add:literal]),+ $(,)?) => {$(paste! {
600 const [<CE_CRNG_U $bits _INIT>]: $t = $init;
601 const [<CE_CRNG_U $bits _MUL>]: $t = $mul;
602 const [<CE_CRNG_U $bits _ADD>]: $t = $add;
603 pub(crate) static [<REACTOR_U $bits _WARMED>]: AtomicBool = AtomicBool::new(false);
604 })+};
605 }
606 macro_rules! core_chaos_engine_struct {
607 ($t:ty, $u:ty, $bits:literal) => {paste!{
608 pub(crate) struct [<ChaosEngine $t>] {
609 state: UnsafeCell<$t>,
610 }
611
612 unsafe impl Sync for [<ChaosEngine $t>] {}
613
614 impl [<ChaosEngine $t>] {
615 pub const fn new(seed: $t) -> Self {
616 Self { state: UnsafeCell::new(seed) }
617 }
618
619 pub fn roll(&self, max: $t) -> $t {
620 if max == 0 { return 0; }
621 unsafe {
622 let stack_entropy = &max as *const $t as usize as [<u $bits>];
623 let ptr = self.state.get();
624 let next = (*ptr as [<u $bits>])
625 .wrapping_mul([<CE_CRNG_U $bits _MUL>])
626 .wrapping_add([<CE_CRNG_U $bits _ADD>])
627 ^ (max as [<u $bits>])
628 ^ stack_entropy
629 ^ {
630 let inst = std::time::Instant::now();
631 &inst as *const _ as usize as [<u $bits>]
632 }
633 ;
634 *ptr = next as $t;
635 ((next % (max as [<u $bits>])) + 1) as $t
636 }
637 }
638
639 pub fn core_chaos_engine_struct_clobber_(&self) {
640 unsafe {
641 let ptr = self.state.get();
642 *ptr = (*ptr as [<u $bits>]).wrapping_add([<CE_CRNG_U $bits _MUL>]).wrapping_add(13) as $t;
643 }
644 }
645 }
646
647 pub(crate) static [<GLOBAL_REACTOR_U $bits>]: [<ChaosEngine $t>] = [<ChaosEngine $t>]::new([<CE_CRNG_U $bits _INIT>]);
648 }};
649 }
650 macro_rules! implement_chaos_engine_struct {
651 (for $(($t:ty, $bits:literal bits)),+ $(,)?) => {$(paste! {
656 core_chaos_engine_struct!($t, $t, $bits);
657 })+};
658 }
659
660 const_chaos_engine_crng_vals!(for
661 [u64, 64 bits, 67, 6364136223846793005, 1442695040888963407],
671 [u128, 128 bits, 131, 22695477 as u128, 1],
672 );
674 implement_chaos_engine_struct!(for
684 (u64, 64 bits),
688 (u128, 128 bits),
689 );
690}
691
692macro_rules! implement_diceext {
693 ( for $(($t:ty, $bits:literal bits)),+ $(,)?) => {$(paste! {
694 impl DiceExt for $t {
695 #[inline(always)] fn d(&self, sides: usize) -> Self { [<any _ $t>](*self, sides) }
696 #[inline(always)] fn d2(&self) -> Self { [<any _ $t>](*self, 2)}
697 #[inline(always)] fn d3(&self) -> Self { [<any _ $t>](*self, 3)}
698 #[inline(always)] fn d4(&self) -> Self { [<any _ $t>](*self, 4)}
699 #[inline(always)] fn d5(&self) -> Self { [<any _ $t>](*self, 5)}
700 #[inline(always)] fn d6(&self) -> Self { [<any _ $t>](*self, 6)}
701 #[inline(always)] fn d8(&self) -> Self { [<any _ $t>](*self, 8)}
702 #[inline(always)] fn d10(&self) -> Self { [<any _ $t>](*self, 10)}
703 #[inline(always)] fn d12(&self) -> Self { [<any _ $t>](*self, 12)}
704 #[inline(always)] fn d20(&self) -> Self { [<any _ $t>](*self, 20)}
705 #[inline(always)] fn d100(&self) -> Self { [<any _ $t>](*self, 100)}
706 }
707
708 fn [<any _ $t>](num: $t, sides: usize) -> $t {
710 if engine::[<REACTOR_U $bits _WARMED>].compare_exchange(false, true, std::sync::atomic::Ordering::Relaxed, std::sync::atomic::Ordering::Relaxed).is_ok() {
711 let x = std::time::Instant::now();
713 let ptr = &x as *const _ as u64;
714 let b = std::time::Instant::now().elapsed().as_nanos() as u64;
715 let z = ptr ^ b.rotate_left(7);
716 for _ in 0..(z.rotate_left((ptr & 0xF) as u32)).wrapping_rem(512).max(128) {
717 engine::[<GLOBAL_REACTOR_U $bits>].roll(sides as [<u $bits>]);
718 }
719 std::thread::spawn(|| {
720 let sleep_dur = std::time::Duration::from_micros(25);
721 loop {
722 std::hint::black_box(engine::[<GLOBAL_REACTOR_U $bits>].core_chaos_engine_struct_clobber_());
723 std::thread::sleep(sleep_dur);
724 }
725 });
726 }
727 let mut result: $t = 0;
728 for _ in 0..[<diceabs _ $t>](num) {
729 result += std::hint::black_box(engine::[<GLOBAL_REACTOR_U $bits>].roll(sides as [<u $bits>]) as $t);
730 }
731 if [<dicelt0 _ $t>](num) {[<dicerev _ $t>](result)} else {result}
732 }
733 }
734
735 impl HiLo for $t {
736 #[inline(always)] fn hi(&self) -> bool { self.is_even() }
737 #[inline(always)] fn lo(&self) -> bool { self.is_odd() }
738 }
739 )+};
740}
741
742macro_rules! implement_float_diceext {
743 ( for $($t:ty),+) => {paste!{
744 $(
745 impl FixedNumberVariance<$t> for $t {
746 fn upto_delta(&self, upto: Self) -> Self {self.jitter_within(upto)}
747 fn jitter_within(&self, upto: Self) -> Self {
748 if upto <= 0.0 { return *self; }
749 let raw_bits = engine::GLOBAL_REACTOR_U64.roll(u64::MAX);
750 let scale = (raw_bits & (([<$t>]::MANTISSA_DIGITS as u64) -1)) as $t
751 / (([<$t>]::MANTISSA_DIGITS as u64) - 1) as $t;
752 self + ((scale * 2.0 * upto ) - upto)
753 }
754 }
755
756 impl PercentageVariance for $t {
757 fn delta(&self, percentage: i32) -> Self { self.jitter_percentage(percentage as f64) }
758 fn jitter_percentage(&self, percentage: f64) -> Self {
759 let p = 0.01 * percentage;
760 let upto = self.abs() * p as $t;
761 self.jitter_within(upto)
762 }
763 }
764 )+
765 }};
766}
767
768implement_diceext!(for
769 (i8, 64 bits),
770 (i16, 64 bits),
771 (i32, 64 bits),
772 (i64, 64 bits),
773 (i128, 128 bits),
774 (isize, 64 bits),
775 (u8, 64 bits),
776 (u16, 64 bits),
777 (u32, 64 bits),
778 (u64, 64 bits),
779 (u128, 128 bits),
780 (usize, 64 bits),
781);
782implement_float_diceext!(for f32, f64);#[cfg(test)]
785mod tests {
786 use super::*;
787 use std::thread;
788 use std::time::Duration;
789
790 #[test]
791 fn test_dicebag_is_completely_non_deterministic() {
792 _ = env_logger::try_init();
793 let sample_size = 10_000;
795 let sides = 10_000;
796
797 let mut seq_a = Vec::with_capacity(sample_size);
799 for _ in 0..sample_size {
800 seq_a.push(1_i32.d(sides));
801 thread::sleep(Duration::from_micros(10));
803 }
804
805 let mut seq_b = Vec::with_capacity(sample_size);
807 for _ in 0..sample_size {
808 seq_b.push(1_i32.d(sides));
809 thread::sleep(Duration::from_micros(10));
810 }
811
812 let mut matches = 0;
814 for i in 0..sample_size {
815 if seq_a[i] == seq_b[i] {
816 matches += 1;
817 }
818 }
819
820 log::debug!("Identical rolls at matching positions: {}/{}", matches, sample_size);
823 assert!(
824 matches < (sample_size / 10),
825 "Sequences are too similar! We have a determinist amongst us!"
826 );
827
828 assert_ne!(seq_a, seq_b, "Something gone really, really wrong - seq B perfectly mirrored A!");
829 }
830
831 #[test]
832 fn test_concurrent_clobbering() {
833 let mut handles = vec![];
837
838 for _ in 0..8 {
839 handles.push(thread::spawn(|| {
840 for _ in 0..50 {
841 let roll = 1_i64.d(100);
842 assert!(roll >= 1 && roll <= 100);
843 }
844 }));
845 }
846
847 for handle in handles {
848 handle.join().unwrap();
849 }
850 }
851
852 }