Skip to content

Commit 1186598

Browse files
committed
Add random number generator resources (optionally specified with a seed)
1 parent 71bf07f commit 1186598

File tree

9 files changed

+158
-36
lines changed

9 files changed

+158
-36
lines changed

crates/bevy_core/Cargo.toml

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -15,13 +15,16 @@ keywords = ["bevy"]
1515

1616
[dependencies]
1717
# bevy
18-
bevy_app = { path = "../bevy_app", version = "0.5.0" }
19-
bevy_derive = { path = "../bevy_derive", version = "0.5.0" }
20-
bevy_ecs = { path = "../bevy_ecs", version = "0.5.0" }
21-
bevy_math = { path = "../bevy_math", version = "0.5.0" }
22-
bevy_reflect = { path = "../bevy_reflect", version = "0.5.0", features = ["bevy"] }
23-
bevy_tasks = { path = "../bevy_tasks", version = "0.5.0" }
24-
bevy_utils = { path = "../bevy_utils", version = "0.5.0" }
18+
bevy_app = { path="../bevy_app", version="0.5.0" }
19+
bevy_derive = { path="../bevy_derive", version="0.5.0" }
20+
bevy_ecs = { path="../bevy_ecs", version="0.5.0" }
21+
bevy_math = { path="../bevy_math", version="0.5.0" }
22+
bevy_reflect = { path="../bevy_reflect", version="0.5.0", features=["bevy"] }
23+
bevy_tasks = { path="../bevy_tasks", version="0.5.0" }
24+
bevy_utils = { path="../bevy_utils", version="0.5.0" }
2525

2626
# other
2727
bytemuck = "1.5"
28+
rand = { version="0.8", features=["getrandom", "small_rng", "std_rng"] }
29+
getrandom = { version="0.2", features=["js"] }
30+
rand_chacha = "0.3"

crates/bevy_core/src/lib.rs

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,19 +2,24 @@ mod bytes;
22
mod float_ord;
33
mod label;
44
mod name;
5+
mod rng;
56
mod task_pool_options;
67
mod time;
78

89
pub use bytes::*;
910
pub use float_ord::*;
1011
pub use label::*;
1112
pub use name::*;
13+
pub use rng::*;
1214
pub use task_pool_options::DefaultTaskPoolOptions;
1315
pub use time::*;
1416

1517
pub mod prelude {
1618
#[doc(hidden)]
17-
pub use crate::{DefaultTaskPoolOptions, EntityLabels, Labels, Name, Time, Timer};
19+
pub use crate::{
20+
CryptoRng, DefaultRngOptions, DefaultTaskPoolOptions, EntityLabels, InsecureRng, Labels,
21+
Name, Rng, RngCore, SecureRng, Time, Timer, SliceRandom
22+
};
1823
}
1924

2025
use bevy_app::prelude::*;
@@ -46,6 +51,13 @@ impl Plugin for CorePlugin {
4651
.unwrap_or_else(DefaultTaskPoolOptions::default)
4752
.create_default_pools(app.world_mut());
4853

54+
// Setup the default bevy random number generators
55+
app.world_mut()
56+
.get_resource::<DefaultRngOptions>()
57+
.cloned()
58+
.unwrap_or_else(DefaultRngOptions::default)
59+
.create_default_rngs(app.world_mut());
60+
4961
app.init_resource::<Time>()
5062
.init_resource::<EntityLabels>()
5163
.init_resource::<FixedTimesteps>()

crates/bevy_core/src/rng.rs

Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
use bevy_ecs::world::World;
2+
use bevy_utils::tracing::trace;
3+
use getrandom::getrandom;
4+
pub use rand::{
5+
rngs::{SmallRng, StdRng},
6+
CryptoRng, Rng, RngCore, SeedableRng, seq::SliceRandom,
7+
};
8+
9+
#[derive(Clone, Debug)]
10+
pub struct DefaultRngOptions {
11+
/// The seed to use for secure / cryptographic operations.
12+
pub secure_seed: [u8; 32],
13+
/// The seed to use for insecure / non-cryptographic operations.
14+
pub insecure_seed: [u8; 32],
15+
}
16+
17+
impl Default for DefaultRngOptions {
18+
fn default() -> Self {
19+
let mut seed: [u8; 32] = [0; 32];
20+
getrandom(&mut seed).expect("failed to get seed for crypto rng");
21+
22+
// The default is to use the same secure / cryptographic seed for both Rngs.
23+
DefaultRngOptions {
24+
secure_seed: seed,
25+
insecure_seed: seed,
26+
}
27+
}
28+
}
29+
30+
#[derive(Clone)]
31+
pub struct SecureRng(StdRng);
32+
33+
impl SecureRng {
34+
fn from_seed(seed: [u8; 32]) -> Self {
35+
SecureRng(StdRng::from_seed(seed))
36+
}
37+
}
38+
impl RngCore for SecureRng {
39+
fn next_u32(&mut self) -> u32 {
40+
self.0.next_u32()
41+
}
42+
fn next_u64(&mut self) -> u64 {
43+
self.0.next_u64()
44+
}
45+
fn fill_bytes(&mut self, dest: &mut [u8]) {
46+
self.0.fill_bytes(dest)
47+
}
48+
fn try_fill_bytes(&mut self, dest: &mut [u8]) -> Result<(), rand::Error> {
49+
self.0.try_fill_bytes(dest)
50+
}
51+
}
52+
53+
#[derive(Clone)]
54+
pub struct InsecureRng(SmallRng);
55+
56+
impl InsecureRng {
57+
fn from_seed(seed: [u8; 32]) -> Self {
58+
InsecureRng(SmallRng::from_seed(seed))
59+
}
60+
}
61+
impl RngCore for InsecureRng {
62+
fn next_u32(&mut self) -> u32 {
63+
self.0.next_u32()
64+
}
65+
fn next_u64(&mut self) -> u64 {
66+
self.0.next_u64()
67+
}
68+
fn fill_bytes(&mut self, dest: &mut [u8]) {
69+
self.0.fill_bytes(dest)
70+
}
71+
fn try_fill_bytes(&mut self, dest: &mut [u8]) -> Result<(), rand::Error> {
72+
self.0.try_fill_bytes(dest)
73+
}
74+
}
75+
76+
impl DefaultRngOptions {
77+
/// Create a configuration that forces using a particular secure seed.
78+
pub fn with_secure_seed(seed: [u8; 32]) -> Self {
79+
DefaultRngOptions {
80+
secure_seed: seed,
81+
..Default::default()
82+
}
83+
}
84+
85+
/// Create a configuration that forces using a particular insecure seed.
86+
pub fn with_insecure_seed(seed: [u8; 32]) -> Self {
87+
DefaultRngOptions {
88+
insecure_seed: seed,
89+
..Default::default()
90+
}
91+
}
92+
93+
/// Inserts the default Rngs into the given resource map based on the configured values.
94+
pub fn create_default_rngs(&self, world: &mut World) {
95+
if !world.contains_resource::<SecureRng>() {
96+
trace!("Creating secure RNG with seed: {:x?}", self.secure_seed);
97+
world.insert_resource(SecureRng::from_seed(self.secure_seed));
98+
}
99+
if !world.contains_resource::<InsecureRng>() {
100+
trace!("Creating insecure RNG with seed: {:x?}", self.insecure_seed);
101+
world.insert_resource(InsecureRng::from_seed(self.insecure_seed));
102+
}
103+
}
104+
}

examples/2d/contributors.rs

Lines changed: 8 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
use bevy::prelude::*;
2-
use rand::{prelude::SliceRandom, Rng};
32
use std::{
43
collections::BTreeSet,
54
io::{BufRead, BufReader},
@@ -52,6 +51,7 @@ fn setup(
5251
mut commands: Commands,
5352
asset_server: Res<AssetServer>,
5453
mut materials: ResMut<Assets<ColorMaterial>>,
54+
mut rng: ResMut<InsecureRng>,
5555
) {
5656
let contribs = contributors();
5757

@@ -65,16 +65,14 @@ fn setup(
6565
idx: 0,
6666
};
6767

68-
let mut rnd = rand::thread_rng();
69-
7068
for name in contribs {
71-
let pos = (rnd.gen_range(-400.0..400.0), rnd.gen_range(0.0..400.0));
72-
let dir = rnd.gen_range(-1.0..1.0);
69+
let pos = (rng.gen_range(-400.0..400.0), rng.gen_range(0.0..400.0));
70+
let dir = rng.gen_range(-1.0..1.0);
7371
let velocity = Vec3::new(dir * 500.0, 0.0, 0.0);
74-
let hue = rnd.gen_range(0.0..=360.0);
72+
let hue = rng.gen_range(0.0..=360.0);
7573

7674
// some sprites should be flipped
77-
let flipped = rnd.gen_bool(0.5);
75+
let flipped = rng.gen_bool(0.5);
7876

7977
let transform = Transform::from_xyz(pos.0, pos.1, 0.0);
8078

@@ -106,7 +104,7 @@ fn setup(
106104
sel.order.push((name, e));
107105
}
108106

109-
sel.order.shuffle(&mut rnd);
107+
sel.order.shuffle(&mut *rng);
110108

111109
commands.spawn_bundle((SelectTimer, Timer::from_seconds(SHOWCASE_TIMER_SECS, true)));
112110

@@ -245,10 +243,9 @@ fn velocity_system(time: Res<Time>, mut q: Query<&mut Velocity>) {
245243
/// force.
246244
fn collision_system(
247245
wins: Res<Windows>,
246+
mut rng: ResMut<InsecureRng>,
248247
mut q: Query<(&mut Velocity, &mut Transform), With<Contributor>>,
249248
) {
250-
let mut rnd = rand::thread_rng();
251-
252249
let win = wins.get_primary().unwrap();
253250

254251
let ceiling = win.height() / 2.;
@@ -267,7 +264,7 @@ fn collision_system(
267264
if bottom < ground {
268265
t.translation.y = ground + SPRITE_SIZE / 2.0;
269266
// apply an impulse upwards
270-
v.translation.y = rnd.gen_range(700.0..1000.0);
267+
v.translation.y = rng.gen_range(700.0..1000.0);
271268
}
272269
if top > ceiling {
273270
t.translation.y = ceiling - SPRITE_SIZE / 2.0;

examples/2d/many_sprites.rs

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,6 @@ use bevy::{
55
sprite::SpriteSettings,
66
};
77

8-
use rand::Rng;
9-
108
const CAMERA_SPEED: f32 = 1000.0;
119

1210
pub struct PrintTimer(Timer);
@@ -32,10 +30,9 @@ fn main() {
3230
fn setup(
3331
mut commands: Commands,
3432
assets: Res<AssetServer>,
33+
mut rng: ResMut<InsecureRng>,
3534
mut materials: ResMut<Assets<ColorMaterial>>,
3635
) {
37-
let mut rng = rand::thread_rng();
38-
3936
let tile_size = Vec2::splat(64.0);
4037
let map_size = Vec2::splat(320.0);
4138

examples/ecs/change_detection.rs

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
use bevy::prelude::*;
2-
use rand::Rng;
32

43
// This example illustrates how to react to component change
54
fn main() {
@@ -20,9 +19,13 @@ fn setup(mut commands: Commands) {
2019
commands.spawn().insert(Transform::identity());
2120
}
2221

23-
fn change_component(time: Res<Time>, mut query: Query<(Entity, &mut MyComponent)>) {
22+
fn change_component(
23+
time: Res<Time>,
24+
mut rng: ResMut<InsecureRng>,
25+
mut query: Query<(Entity, &mut MyComponent)>,
26+
) {
2427
for (entity, mut component) in query.iter_mut() {
25-
if rand::thread_rng().gen_bool(0.1) {
28+
if rng.gen_bool(0.1) {
2629
info!("changing component {:?}", entity);
2730
component.0 = time.seconds_since_startup();
2831
}

examples/ecs/iter_combinations.rs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
use bevy::{core::FixedTimestep, prelude::*};
2-
use rand::{thread_rng, Rng};
32

43
#[derive(Debug, Hash, PartialEq, Eq, Clone, StageLabel)]
54
struct FixedUpdateStage;
@@ -44,6 +43,7 @@ struct BodyBundle {
4443

4544
fn generate_bodies(
4645
mut commands: Commands,
46+
mut rng: ResMut<InsecureRng>,
4747
mut meshes: ResMut<Assets<Mesh>>,
4848
mut materials: ResMut<Assets<StandardMaterial>>,
4949
) {
@@ -56,7 +56,6 @@ fn generate_bodies(
5656
let color_range = 0.5..1.0;
5757
let vel_range = -0.5..0.5;
5858

59-
let mut rng = thread_rng();
6059
for _ in 0..NUM_BODIES {
6160
let mass_value_cube_root: f32 = rng.gen_range(0.5..4.0);
6261
let mass_value: f32 = mass_value_cube_root * mass_value_cube_root * mass_value_cube_root;

examples/game/alien_cake_addict.rs

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@ use bevy::{
44
prelude::*,
55
render::{camera::Camera, render_graph::base::camera::CAMERA_3D},
66
};
7-
use rand::Rng;
87

98
#[derive(Clone, Eq, PartialEq, Debug, Hash)]
109
enum GameState {
@@ -98,7 +97,12 @@ fn setup_cameras(mut commands: Commands, mut game: ResMut<Game>) {
9897
commands.spawn_bundle(UiCameraBundle::default());
9998
}
10099

101-
fn setup(mut commands: Commands, asset_server: Res<AssetServer>, mut game: ResMut<Game>) {
100+
fn setup(
101+
mut commands: Commands,
102+
asset_server: Res<AssetServer>,
103+
mut game: ResMut<Game>,
104+
mut rng: ResMut<InsecureRng>,
105+
) {
102106
// reset the game state
103107
game.cake_eaten = 0;
104108
game.score = 0;
@@ -116,7 +120,7 @@ fn setup(mut commands: Commands, asset_server: Res<AssetServer>, mut game: ResMu
116120
.map(|j| {
117121
(0..BOARD_SIZE_I)
118122
.map(|i| {
119-
let height = rand::thread_rng().gen_range(-0.1..0.1);
123+
let height = rng.gen_range(-0.1..0.1);
120124
commands
121125
.spawn_bundle((
122126
Transform::from_xyz(i as f32, height - 0.2, j as f32),
@@ -296,6 +300,7 @@ fn spawn_bonus(
296300
mut state: ResMut<State<GameState>>,
297301
mut commands: Commands,
298302
mut game: ResMut<Game>,
303+
mut rng: ResMut<InsecureRng>,
299304
) {
300305
if *state.current() != GameState::Playing {
301306
return;
@@ -312,8 +317,8 @@ fn spawn_bonus(
312317

313318
// ensure bonus doesn't spawn on the player
314319
loop {
315-
game.bonus.i = rand::thread_rng().gen_range(0..BOARD_SIZE_I);
316-
game.bonus.j = rand::thread_rng().gen_range(0..BOARD_SIZE_J);
320+
game.bonus.i = rng.gen_range(0..BOARD_SIZE_I);
321+
game.bonus.j = rng.gen_range(0..BOARD_SIZE_J);
317322
if game.bonus.i != game.player.i || game.bonus.j != game.player.j {
318323
break;
319324
}

examples/tools/bevymark.rs

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ use bevy::{
22
diagnostic::{Diagnostics, FrameTimeDiagnosticsPlugin},
33
prelude::*,
44
};
5-
use rand::Rng;
65

76
const BIRDS_PER_SECOND: u32 = 1000;
87
const BASE_COLOR: Color = Color::rgb(5.0, 5.0, 5.0);
@@ -116,9 +115,9 @@ fn mouse_handler(
116115
mut bird_material: ResMut<BirdMaterial>,
117116
mut counter: ResMut<BevyCounter>,
118117
mut materials: ResMut<Assets<ColorMaterial>>,
118+
mut rnd: ResMut<InsecureRng>,
119119
) {
120120
if mouse_button_input.just_pressed(MouseButton::Left) {
121-
let mut rnd = rand::thread_rng();
122121
let color = gen_color(&mut rnd);
123122

124123
let texture_handle = asset_server.load("branding/icon.png");
@@ -207,7 +206,10 @@ fn counter_system(
207206
///
208207
/// Because there is no `Mul<Color> for Color` instead `[f32; 3]` is
209208
/// used.
210-
fn gen_color(rng: &mut impl Rng) -> [f32; 3] {
209+
fn gen_color<R>(rng: &mut ResMut<R>) -> [f32; 3]
210+
where
211+
R: Rng + Sync + Send + 'static,
212+
{
211213
let r = rng.gen_range(0.2..1.0);
212214
let g = rng.gen_range(0.2..1.0);
213215
let b = rng.gen_range(0.2..1.0);

0 commit comments

Comments
 (0)