1use core::ops::{Range, RangeInclusive};
29
30use bevy_ecs::prelude::*;
31use serde::{Deserialize, Serialize};
32
33const SPLITMIX_GAMMA: u64 = 0x9E37_79B9_7F4A_7C15;
35const SPLITMIX_MUL1: u64 = 0xBF58_476D_1CE4_E5B9;
37const SPLITMIX_MUL2: u64 = 0x94D0_49BB_1331_11EB;
39const TAG_SALT: u64 = 0xD1B5_4A32_D192_ED03;
41
42const fn finalize(mut z: u64) -> u64 {
44 z = (z ^ (z >> 30)).wrapping_mul(SPLITMIX_MUL1);
45 z = (z ^ (z >> 27)).wrapping_mul(SPLITMIX_MUL2);
46 z ^ (z >> 31)
47}
48
49const fn expand(seed: u64) -> [u64; 4] {
56 let mut counter = seed;
57 let mut state = [0; 4];
58 let mut word = 0;
59 while word < 4 {
60 counter = counter.wrapping_add(SPLITMIX_GAMMA);
61 state[word] = finalize(counter);
62 word += 1;
63 }
64 state
65}
66
67#[derive(Resource, Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
79pub struct SimRng {
80 state: [u64; 4],
81}
82
83impl SimRng {
84 pub const fn seeded(seed: u64) -> Self {
89 Self {
90 state: expand(seed),
91 }
92 }
93
94 pub const fn from_words(mut words: [u64; 4]) -> Self {
100 if words[0] == 0 && words[1] == 0 && words[2] == 0 && words[3] == 0 {
101 words = expand(0);
102 }
103 Self { state: words }
104 }
105
106 pub const fn state_words(&self) -> [u64; 4] {
110 self.state
111 }
112}
113
114impl Rng for SimRng {
115 fn next_u64(&mut self) -> u64 {
117 let result = self.state[1].wrapping_mul(5).rotate_left(7).wrapping_mul(9);
118 let t = self.state[1] << 17;
119 self.state[2] ^= self.state[0];
120 self.state[3] ^= self.state[1];
121 self.state[1] ^= self.state[2];
122 self.state[0] ^= self.state[3];
123 self.state[2] ^= t;
124 self.state[3] = self.state[3].rotate_left(45);
125 result
126 }
127}
128
129#[derive(Resource, Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
144pub struct WorldSeed(u64);
145
146impl WorldSeed {
147 pub const fn new(seed: u64) -> Self {
149 Self(seed)
150 }
151
152 pub const fn get(self) -> u64 {
154 self.0
155 }
156
157 pub fn stream(&self, tag: u64) -> SimRng {
164 SimRng::seeded(finalize(self.0 ^ finalize(tag ^ TAG_SALT)))
165 }
166
167 pub fn named(&self, name: &str) -> SimRng {
172 self.stream(crate::hash::fnv1a64(name.as_bytes()))
173 }
174
175 pub fn stream_for_entity(&self, entity: Entity) -> SimRng {
181 self.stream(entity.to_bits())
182 }
183}
184
185pub trait Rng {
197 fn next_u64(&mut self) -> u64;
199
200 fn next_u32(&mut self) -> u32 {
203 (self.next_u64() >> 32) as u32
204 }
205
206 fn random<T: Sample>(&mut self) -> T {
214 T::sample(self)
215 }
216
217 fn random_range<R: SampleRange>(&mut self, range: R) -> R::Output {
225 range.sample_from(self)
226 }
227
228 fn bool(&mut self) -> bool {
230 (self.next_u64() >> 63) == 1
231 }
232
233 fn random_bool(&mut self, p: f64) -> bool {
239 assert!(!p.is_nan(), "random_bool probability must not be NaN");
240 if p <= 0.0 {
241 return false;
242 }
243 if p >= 1.0 {
244 return true;
245 }
246 self.unit_f64() < p
247 }
248
249 fn unit_f32(&mut self) -> f32 {
254 (self.next_u64() >> 40) as f32 / (1u64 << 24) as f32
255 }
256
257 fn unit_f64(&mut self) -> f64 {
259 (self.next_u64() >> 11) as f64 / (1u64 << 53) as f64
260 }
261
262 fn below(&mut self, bound: u32) -> u32 {
269 self.below_u64(bound as u64) as u32
270 }
271
272 fn below_u64(&mut self, bound: u64) -> u64 {
275 if bound == 0 {
276 return 0;
277 }
278 let mut draw = self.next_u64();
279 let mut product = (draw as u128) * (bound as u128);
280 let mut low = product as u64;
281 if low < bound {
282 let threshold = bound.wrapping_neg() % bound;
283 while low < threshold {
284 draw = self.next_u64();
285 product = (draw as u128) * (bound as u128);
286 low = product as u64;
287 }
288 }
289 (product >> 64) as u64
290 }
291
292 fn pick_index(&mut self, len: usize) -> Option<usize> {
302 if len == 0 {
303 return None;
304 }
305 Some(self.below_u64(len as u64) as usize)
306 }
307
308 fn pick<'a, T>(&mut self, items: &'a [T]) -> Option<&'a T> {
310 self.pick_index(items.len()).map(|index| &items[index])
311 }
312
313 fn weighted_index(&mut self, weights: &[f32]) -> Option<usize> {
321 let total: f32 = weights.iter().copied().map(effective_weight).sum();
322 if !(total > 0.0) {
323 return None;
324 }
325 let target = self.unit_f32() * total;
326 let mut acc = 0.0f32;
327 let mut last = None;
328 for (index, weight) in weights.iter().enumerate() {
329 if effective_weight(*weight) == 0.0 {
330 continue;
331 }
332 last = Some(index);
333 acc += *weight;
334 if acc > target {
335 return Some(index);
336 }
337 }
338 last
339 }
340
341 fn shuffle<T>(&mut self, items: &mut [T]) {
353 for i in (1..items.len()).rev() {
354 let j = self.below_u64(i as u64 + 1) as usize;
355 items.swap(i, j);
356 }
357 }
358}
359
360impl<T: Rng + ?Sized> Rng for &mut T {
361 fn next_u64(&mut self) -> u64 {
362 (**self).next_u64()
363 }
364}
365
366fn effective_weight(weight: f32) -> f32 {
368 if weight.is_finite() && weight > 0.0 {
369 weight
370 } else {
371 0.0
372 }
373}
374
375fn offset_in<R: Rng + ?Sized>(rng: &mut R, span: u128) -> u128 {
380 if span == 1u128 << 64 {
381 rng.next_u64() as u128
382 } else {
383 rng.below_u64(span as u64) as u128
384 }
385}
386
387trait Unit: Sized {
389 fn unit<R: Rng + ?Sized>(rng: &mut R) -> Self;
390}
391
392impl Unit for f32 {
393 fn unit<R: Rng + ?Sized>(rng: &mut R) -> f32 {
394 rng.unit_f32()
395 }
396}
397
398impl Unit for f64 {
399 fn unit<R: Rng + ?Sized>(rng: &mut R) -> f64 {
400 rng.unit_f64()
401 }
402}
403
404pub trait SampleRange: sealed::Sealed {
411 type Output;
413
414 #[doc(hidden)]
416 fn sample_from<R: Rng + ?Sized>(self, rng: &mut R) -> Self::Output;
417}
418
419pub trait Sample: sealed::Sealed {
425 #[doc(hidden)]
427 fn sample<R: Rng + ?Sized>(rng: &mut R) -> Self;
428}
429
430mod sealed {
431 pub trait Sealed {}
434}
435
436macro_rules! impl_uint_range {
437 ($($ty:ty),* $(,)?) => {$(
438 impl sealed::Sealed for Range<$ty> {}
439 impl SampleRange for Range<$ty> {
440 type Output = $ty;
441
442 fn sample_from<R: Rng + ?Sized>(self, rng: &mut R) -> $ty {
443 assert!(
444 self.start < self.end,
445 "random_range needs a non-empty range, got {}..{}",
446 self.start,
447 self.end
448 );
449 let span = self.end as u128 - self.start as u128;
450 self.start.wrapping_add(offset_in(rng, span) as $ty)
451 }
452 }
453
454 impl sealed::Sealed for RangeInclusive<$ty> {}
455 impl SampleRange for RangeInclusive<$ty> {
456 type Output = $ty;
457
458 fn sample_from<R: Rng + ?Sized>(self, rng: &mut R) -> $ty {
459 let start = *self.start();
460 let end = *self.end();
461 assert!(
462 start <= end,
463 "random_range needs a non-empty range, got {start}..={end}"
464 );
465 let span = end as u128 - start as u128 + 1;
466 start.wrapping_add(offset_in(rng, span) as $ty)
467 }
468 }
469 )*};
470}
471
472macro_rules! impl_int_range {
473 ($($ty:ty),* $(,)?) => {$(
474 impl sealed::Sealed for Range<$ty> {}
475 impl SampleRange for Range<$ty> {
476 type Output = $ty;
477
478 fn sample_from<R: Rng + ?Sized>(self, rng: &mut R) -> $ty {
479 assert!(
480 self.start < self.end,
481 "random_range needs a non-empty range, got {}..{}",
482 self.start,
483 self.end
484 );
485 let span = (self.end as i128 - self.start as i128) as u128;
486 self.start.wrapping_add(offset_in(rng, span) as $ty)
487 }
488 }
489
490 impl sealed::Sealed for RangeInclusive<$ty> {}
491 impl SampleRange for RangeInclusive<$ty> {
492 type Output = $ty;
493
494 fn sample_from<R: Rng + ?Sized>(self, rng: &mut R) -> $ty {
495 let start = *self.start();
496 let end = *self.end();
497 assert!(
498 start <= end,
499 "random_range needs a non-empty range, got {start}..={end}"
500 );
501 let span = (end as i128 - start as i128 + 1) as u128;
502 start.wrapping_add(offset_in(rng, span) as $ty)
503 }
504 }
505 )*};
506}
507
508macro_rules! impl_float_range {
509 ($($ty:ty),* $(,)?) => {$(
510 impl sealed::Sealed for Range<$ty> {}
511 impl SampleRange for Range<$ty> {
512 type Output = $ty;
513
514 fn sample_from<R: Rng + ?Sized>(self, rng: &mut R) -> $ty {
515 assert!(
516 self.start < self.end,
517 "random_range needs an ordered, non-empty range, got {}..{}",
518 self.start,
519 self.end
520 );
521 self.start + (self.end - self.start) * <$ty as Unit>::unit(rng)
522 }
523 }
524
525 impl sealed::Sealed for RangeInclusive<$ty> {}
526 impl SampleRange for RangeInclusive<$ty> {
527 type Output = $ty;
528
529 fn sample_from<R: Rng + ?Sized>(self, rng: &mut R) -> $ty {
530 let start = *self.start();
531 let end = *self.end();
532 assert!(
533 start < end,
534 "random_range needs an ordered, non-empty range, got {start}..={end}"
535 );
536 start + (end - start) * <$ty as Unit>::unit(rng)
537 }
538 }
539 )*};
540}
541
542impl_uint_range!(u8, u16, u32, u64, usize);
543impl_int_range!(i8, i16, i32, i64, isize);
544impl_float_range!(f32, f64);
545
546macro_rules! impl_sample_small {
551 ($(($ty:ident, $unsigned:ty)),* $(,)?) => {$(
552 impl sealed::Sealed for $ty {}
553 impl Sample for $ty {
554 fn sample<R: Rng + ?Sized>(rng: &mut R) -> $ty {
555 let bits = rng.next_u64() >> (64 - core::mem::size_of::<$unsigned>() * 8);
556 (bits as $unsigned) as $ty
557 }
558 }
559 )*};
560}
561
562impl sealed::Sealed for u64 {}
563impl Sample for u64 {
564 fn sample<R: Rng + ?Sized>(rng: &mut R) -> u64 {
565 rng.next_u64()
566 }
567}
568
569impl sealed::Sealed for i64 {}
570impl Sample for i64 {
571 fn sample<R: Rng + ?Sized>(rng: &mut R) -> i64 {
572 rng.next_u64() as i64
573 }
574}
575
576impl_sample_small!(
577 (u32, u32),
578 (u16, u16),
579 (u8, u8),
580 (i32, u32),
581 (i16, u16),
582 (i8, u8)
583);
584
585impl sealed::Sealed for bool {}
586impl Sample for bool {
587 fn sample<R: Rng + ?Sized>(rng: &mut R) -> bool {
588 rng.bool()
589 }
590}
591
592impl sealed::Sealed for f32 {}
593impl Sample for f32 {
594 fn sample<R: Rng + ?Sized>(rng: &mut R) -> f32 {
595 rng.unit_f32()
596 }
597}
598
599impl sealed::Sealed for f64 {}
600impl Sample for f64 {
601 fn sample<R: Rng + ?Sized>(rng: &mut R) -> f64 {
602 rng.unit_f64()
603 }
604}
605
606#[cfg(test)]
607mod tests {
608 use super::*;
609
610 const SEED: u64 = 0x0123_4567_89AB_CDEF;
611
612 #[test]
613 fn core_matches_the_published_xoshiro256starstar_vectors() {
614 let mut rng = SimRng::from_words([1, 2, 3, 4]);
617 let want = [
618 11520,
619 0,
620 1509978240,
621 1215971899390074240,
622 1216172134540287360,
623 607988272756665600,
624 16172922978634559625,
625 8476171486693032832,
626 10595114339597558777,
627 2904607092377533576,
628 ];
629 for value in want {
630 assert_eq!(rng.next_u64(), value);
631 }
632 }
633
634 #[test]
635 fn seeded_stream_is_pinned_word_for_word() {
636 let mut rng = SimRng::seeded(SEED);
637 let want = [
638 11728116837925579837,
639 431261241542867727,
640 7088239201150201886,
641 1991960781942118182,
642 16071698363884655823,
643 4123588241367215080,
644 13086776817198750337,
645 9243408305086729258,
646 ];
647 for value in want {
648 assert_eq!(rng.next_u64(), value);
649 }
650 assert_eq!(SimRng::seeded(0).state_words()[0], 0xE220_A839_7B1D_CDAF);
651 }
652
653 #[test]
654 fn seeded_zero_is_a_stream_not_a_fixed_point() {
655 let mut rng = SimRng::seeded(0);
656 for _ in 0..8 {
657 assert_ne!(rng.next_u64(), 0);
658 }
659 let mut mostly_zero = SimRng::from_words([0, 1, 2, 3]);
661 assert_ne!(mostly_zero.next_u64(), 0);
662 assert_eq!(
664 SimRng::from_words([0; 4]).state_words(),
665 SimRng::seeded(0).state_words()
666 );
667 }
668
669 #[test]
670 fn sampling_is_pinned_value_for_value() {
671 let mut rng = SimRng::seeded(SEED);
672 assert_eq!(rng.next_u32(), 2730664992);
673 assert_eq!(rng.next_u32(), 100410832);
674
675 let mut rng = SimRng::seeded(SEED);
676 assert_eq!(rng.unit_f32(), 0.63578248_f32);
677
678 let mut rng = SimRng::seeded(SEED);
679 assert_eq!(rng.unit_f64(), 0.6357824877421369_f64);
680 assert!(rng.unit_f64() < 1.0);
681
682 let mut rng = SimRng::seeded(SEED);
683 assert_eq!(
684 [rng.below(6), rng.below(6), rng.below(6), rng.below(6)],
685 [3, 0, 2, 0]
686 );
687 assert_eq!(SimRng::seeded(SEED).below(0), 0);
688 assert_eq!(SimRng::seeded(SEED).below_u64(1 << 40), 699050238008);
689
690 let mut rng = SimRng::seeded(SEED);
691 assert_eq!(
692 [rng.bool(), rng.bool(), rng.bool(), rng.bool()],
693 [true, false, false, false]
694 );
695 assert_eq!(SimRng::seeded(SEED).random::<u8>(), 162);
696 assert_eq!(SimRng::seeded(SEED).random::<i32>(), -1564302304);
697 assert_eq!(SimRng::seeded(SEED).random::<u64>(), 11728116837925579837);
698 }
699
700 #[test]
701 fn ranges_are_pinned_and_agree_with_their_primitives() {
702 let mut rng = SimRng::seeded(SEED);
703 assert_eq!(
704 [
705 rng.random_range(0..6),
706 rng.random_range(0..6),
707 rng.random_range(0..6),
708 rng.random_range(0..6),
709 rng.random_range(0..6),
710 rng.random_range(0..6),
711 rng.random_range(0..6),
712 rng.random_range(0..6),
713 ],
714 [3, 0, 2, 0, 5, 1, 4, 3]
715 );
716 let mut rng = SimRng::seeded(SEED);
717 assert_eq!(
718 [
719 rng.random_range(2..=7),
720 rng.random_range(2..=7),
721 rng.random_range(2..=7),
722 rng.random_range(2..=7),
723 ],
724 [5, 2, 4, 2]
725 );
726 let mut rng = SimRng::seeded(SEED);
727 assert_eq!(
728 rng.random_range(0.0..1.0_f32),
729 SimRng::seeded(SEED).unit_f32()
730 );
731 let mut rng = SimRng::seeded(SEED);
732 assert_eq!(
736 [
737 rng.random_range(-1.0_f32..1.0_f32),
738 rng.random_range(-1.0_f32..1.0_f32)
739 ],
740 [0.27156496047973633, -0.9532426595687866]
741 );
742 let mut rng = SimRng::seeded(SEED);
745 assert_eq!(
746 [
747 rng.random_range(-1.0_f64..1.0_f64),
748 rng.random_range(-1.0_f64..1.0_f64)
749 ],
750 [0.2715649754842737, -0.9532425625010428]
751 );
752
753 assert_eq!(
757 SimRng::seeded(SEED).random_range(u64::MIN..=u64::MAX),
758 11728116837925579837
759 );
760 assert_eq!(
761 SimRng::seeded(SEED).random_range(i64::MIN..=i64::MAX),
762 2504744801070804029
763 );
764 assert_eq!(SimRng::seeded(SEED).random_range(0..1_u32), 0);
766 }
767
768 #[test]
769 fn a_pick_is_one_unbiased_draw_and_a_shuffle_is_pinned() {
770 let mut rng = SimRng::seeded(SEED);
771 let want = [3, 0, 1, 0, 4, 1, 3, 2];
772 for index in want {
773 assert_eq!(rng.pick_index(5), Some(index));
774 }
775 assert_eq!(rng.pick_index(0), None);
776 assert_eq!(rng.pick(&[] as &[u8]), None);
777 assert_eq!(rng.pick(&[10, 11, 12, 13, 14]), Some(&14));
778
779 let mut deck = [0, 1, 2, 3, 4, 5, 6, 7];
780 SimRng::seeded(SEED).shuffle(&mut deck);
781 assert_eq!(deck, [7, 1, 4, 3, 6, 2, 0, 5]);
782
783 let mut empty: [u8; 0] = [];
784 SimRng::seeded(SEED).shuffle(&mut empty);
785 let mut single = [9];
786 SimRng::seeded(SEED).shuffle(&mut single);
787 assert_eq!(single, [9]);
788 }
789
790 #[test]
791 fn below_uses_its_whole_range_without_modulo_bias() {
792 let mut rng = SimRng::seeded(7);
793 let mut faces = [0usize; 6];
794 for _ in 0..60_000 {
795 faces[rng.below(6) as usize] += 1;
796 }
797 for (face, count) in faces.iter().enumerate() {
798 assert!(
799 (9_200..=10_800).contains(count),
800 "face {face} came up {count} times in 60_000 draws"
801 );
802 }
803 }
804
805 #[test]
806 fn weighted_index_follows_its_weights() {
807 let mut rng = SimRng::seeded(2);
808 assert_eq!(rng.weighted_index(&[1.0, 2.0, 5.0]), Some(0));
809 let mut counts = [0usize; 3];
810 let mut rng = SimRng::seeded(11);
811 for _ in 0..30_000 {
812 counts[rng.weighted_index(&[1.0, 2.0, 5.0]).unwrap()] += 1;
813 }
814 for (index, want) in [(0, 3_750), (1, 7_500), (2, 18_750)] {
815 let got = counts[index];
816 assert!(
817 (want * 92 / 100..want * 108 / 100).contains(&got),
818 "index {index} came up {got} times, expected about {want}"
819 );
820 }
821 let mut rng = SimRng::seeded(SEED);
822 let before = rng.state_words();
823 assert_eq!(rng.weighted_index(&[0.0, 0.0]), None);
824 assert_eq!(rng.weighted_index(&[]), None);
825 assert_eq!(
826 rng.state_words(),
827 before,
828 "a refused weight consumes no draw"
829 );
830 assert_eq!(
831 SimRng::seeded(SEED).weighted_index(&[f32::NAN, -1.0, 2.0]),
832 Some(2)
833 );
834 }
835
836 #[test]
837 fn a_save_resumes_the_identical_stream() {
838 let mut original = SimRng::seeded(SEED);
839 for _ in 0..13 {
840 original.next_u64();
841 }
842 let mut restored = SimRng::from_words(original.state_words());
843 assert_eq!(restored.state_words(), original.state_words());
844 assert_eq!(
845 restored.state_words(),
846 [
847 0x22F8_BA83_280F_7AD2,
848 0x5DD0_0A53_80BC_904C,
849 0x54FF_F2A6_2C07_7DDF,
850 0x1C14_3DD2_89E7_2E17
851 ]
852 );
853 for _ in 0..8 {
854 assert_eq!(restored.next_u64(), original.next_u64());
855 }
856 }
857
858 #[test]
859 fn random_bool_clamps_and_consumes_nothing_for_a_certainty() {
860 let mut rng = SimRng::seeded(SEED);
861 assert!(!rng.random_bool(0.0));
862 assert!(rng.random_bool(1.0));
863 assert!(!rng.random_bool(-1.0));
864 assert!(rng.random_bool(2.0));
865 assert_eq!(rng.state_words(), SimRng::seeded(SEED).state_words());
866 assert!(rng.random_bool(0.75), "0.63578.. is below 0.75");
867 }
868
869 #[test]
870 #[should_panic(expected = "must not be NaN")]
871 fn random_bool_rejects_nan() {
872 SimRng::seeded(SEED).random_bool(f64::NAN);
873 }
874
875 #[test]
876 #[should_panic(expected = "non-empty range")]
877 fn an_empty_range_is_a_bug_not_a_default() {
878 SimRng::seeded(SEED).random_range(4..4);
879 }
880
881 #[test]
882 #[should_panic(expected = "non-empty range")]
883 fn a_reversed_float_range_is_a_bug() {
884 SimRng::seeded(SEED).random_range(1.0..=0.0);
885 }
886
887 #[test]
888 fn a_seed_series_is_the_documented_hazard_derivation_avoids() {
889 let a = SimRng::seeded(SEED).state_words();
892 let b = SimRng::seeded(SEED.wrapping_add(SPLITMIX_GAMMA)).state_words();
893 let shared = a.iter().filter(|word| b.contains(word)).count();
894 assert_eq!(shared, 3, "the hazard this module documents is real");
895
896 let seed = WorldSeed::new(0xDEAD_BEEF);
898 for tag in 0..64u64 {
899 let near = seed.stream(tag.wrapping_add(1)).state_words();
900 let far = seed.stream(tag).state_words();
901 assert!(far.iter().all(|word| !near.contains(word)));
902 }
903 }
904
905 #[test]
906 fn world_seed_streams_are_distinct_and_additive_changes_do_not_shift_them() {
907 let seed = WorldSeed::new(0xDEAD_BEEF);
908 let mut born = false;
909 let mut seen = std::collections::HashSet::new();
910 for tag in 0..64 {
911 if tag == 32 {
912 born = true;
913 }
914 let mut rng = seed.stream(tag);
915 for _ in 0..64 {
916 assert!(seen.insert(rng.next_u64()), "two streams collided");
917 }
918 }
919 assert!(born);
920 assert_eq!(
922 seed.stream(7).next_u64(),
923 WorldSeed::new(0xDEAD_BEEF).stream(7).next_u64()
924 );
925 assert_eq!(seed.stream(0).next_u64(), 0xC400_5AD9_14EA_B622);
926 assert_eq!(seed.stream(7).next_u64(), 0x8BBB_8B00_BC67_7185);
927 let base = SimRng::seeded(seed.get()).next_u64();
928 assert_ne!(
929 seed.stream(0).next_u64(),
930 base,
931 "tag 0 must not be the bare seeded stream"
932 );
933 }
934
935 #[test]
936 fn named_and_entity_streams_are_stable_and_apart_from_the_bare_stream() {
937 let seed = WorldSeed::new(0xDEAD_BEEF);
938 assert_eq!(seed.named("loot").next_u64(), 0x68F0_61C5_2FF8_2575);
939 assert_eq!(seed.named("loot").next_u64(), seed.named("loot").next_u64());
940 assert_ne!(seed.named("loot").next_u64(), seed.named("Loot").next_u64());
941
942 let mut world = World::new();
943 let entity = world.spawn_empty().id();
944 assert_eq!(
945 seed.stream_for_entity(entity).next_u64(),
946 seed.stream(entity.to_bits()).next_u64()
947 );
948 assert_eq!(
949 seed.stream_for_entity(entity).next_u64(),
950 seed.stream_for_entity(entity).next_u64()
951 );
952 assert_ne!(
953 seed.stream_for_entity(entity).next_u64(),
954 seed.stream(0).next_u64()
955 );
956 }
957
958 #[test]
959 fn a_resource_can_be_seeded_and_serialised() {
960 fn assert_serde<T: Serialize + serde::de::DeserializeOwned>() {}
961 assert_serde::<SimRng>();
962 assert_serde::<WorldSeed>();
963
964 let mut world = World::new();
965 world.insert_resource(SimRng::seeded(SEED));
966 world.insert_resource(WorldSeed::new(SEED));
967 assert_eq!(
968 world.resource_mut::<SimRng>().random_range(0..6),
969 SimRng::seeded(SEED).random_range(0..6)
970 );
971 assert_eq!(world.resource::<WorldSeed>().get(), SEED);
972 }
973
974 #[test]
975 fn a_mutable_borrow_is_a_stream() {
976 fn draw(rng: &mut impl Rng) -> u64 {
977 rng.next_u64()
978 }
979 let mut rng = SimRng::seeded(SEED);
980 let mut borrow = &mut rng;
981 let through_borrow = draw(&mut borrow);
982 assert_eq!(through_borrow, SimRng::seeded(SEED).next_u64());
983 }
984}