Skip to content

Add random number generator resource (optionally specified with a seed) #2355

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 5 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,7 @@ current changes on git with [previous release tags][git_tag_comparison].
- [Added `set_minimized` and `set_position` to `Window`][1292]
- [Example for 2D Frustum Culling][1503]
- [Add remove resource to commands][1478]
- [Random number generation][2355]

### Changed

Expand Down Expand Up @@ -246,6 +247,7 @@ current changes on git with [previous release tags][git_tag_comparison].
[1703]: https://github.com/bevyengine/bevy/pull/1703
[1728]: https://github.com/bevyengine/bevy/pull/1728
[1762]: https://github.com/bevyengine/bevy/pull/1762
[2355]: https://github.com/bevyengine/bevy/pull/2355

## Version 0.4.0 (2020-12-19)

Expand Down
4 changes: 4 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -181,6 +181,10 @@ path = "examples/3d/z_sort_debug.rs"
name = "custom_loop"
path = "examples/app/custom_loop.rs"

[[example]]
name = "custom_rng_seeds"
path = "examples/app/custom_rng_seeds.rs"

[[example]]
name = "drag_and_drop"
path = "examples/app/drag_and_drop.rs"
Expand Down
5 changes: 5 additions & 0 deletions crates/bevy_core/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,9 @@ repository = "https://github.com/bevyengine/bevy"
license = "MIT"
keywords = ["bevy"]

[features]
default = ["rng"]
rng = ["getrandom", "rand"]

[dependencies]
# bevy
Expand All @@ -25,3 +28,5 @@ bevy_utils = { path = "../bevy_utils", version = "0.5.0" }

# other
bytemuck = "1.5"
getrandom = { version = "0.2", features = ["js"], optional = true }
rand = { version = "0.8", features = ["getrandom", "small_rng", "std_rng"], optional = true }
20 changes: 20 additions & 0 deletions crates/bevy_core/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,27 @@ mod bytes;
mod float_ord;
mod label;
mod name;
#[cfg(feature = "rng")]
mod rng;
mod task_pool_options;
mod time;

pub use bytes::*;
pub use float_ord::*;
pub use label::*;
pub use name::*;
#[cfg(feature = "rng")]
pub use rng::*;
pub use task_pool_options::DefaultTaskPoolOptions;
pub use time::*;

pub mod prelude {
#[cfg(feature = "rng")]
#[doc(hidden)]
pub use crate::{
CryptoRng, DefaultRngOptions, InsecureRng, InsecureSeed, Rng, RngCore, SecureRng,
SecureSeed, SliceRandom,
};
#[doc(hidden)]
pub use crate::{DefaultTaskPoolOptions, EntityLabels, Labels, Name, Time, Timer};
}
Expand Down Expand Up @@ -46,6 +56,16 @@ impl Plugin for CorePlugin {
.unwrap_or_else(DefaultTaskPoolOptions::default)
.create_default_pools(app.world_mut());

#[cfg(feature = "rng")]
{
// Setup the default bevy random number generators
app.world_mut()
.get_resource::<DefaultRngOptions>()
.cloned()
.unwrap_or_else(DefaultRngOptions::default)
.create_default_rngs(app.world_mut());
}

app.init_resource::<Time>()
.init_resource::<EntityLabels>()
.init_resource::<FixedTimesteps>()
Expand Down
55 changes: 55 additions & 0 deletions crates/bevy_core/src/rng/insecure.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
pub use rand::{
rngs::{SmallRng, StdRng},
seq::SliceRandom,
CryptoRng, Rng, RngCore, SeedableRng,
};
use std::ops::Deref;

/// A seed for seeding an [`InsecureRng`].
pub struct InsecureSeed([u8; 32]);

impl From<[u8; 32]> for InsecureSeed {
fn from(item: [u8; 32]) -> Self {
InsecureSeed(item)
}
}

impl Deref for InsecureSeed {
type Target = [u8; 32];
fn deref(&self) -> &Self::Target {
&self.0
}
}

/// A random number generator for use in non-cryptographic operations.
///
/// For cryptographic operations, use [`SecureRng`](super::secure::SecureRng).
#[derive(Clone)]
pub struct InsecureRng(SmallRng);

impl Deref for InsecureRng {
type Target = SmallRng;
fn deref(&self) -> &Self::Target {
&self.0
}
}

impl InsecureRng {
pub(crate) fn from_seed(seed: [u8; 32]) -> Self {
InsecureRng(SmallRng::from_seed(seed))
}
}
impl RngCore for InsecureRng {
fn next_u32(&mut self) -> u32 {
self.0.next_u32()
}
fn next_u64(&mut self) -> u64 {
self.0.next_u64()
}
fn fill_bytes(&mut self, dest: &mut [u8]) {
self.0.fill_bytes(dest)
}
fn try_fill_bytes(&mut self, dest: &mut [u8]) -> Result<(), rand::Error> {
self.0.try_fill_bytes(dest)
}
}
80 changes: 80 additions & 0 deletions crates/bevy_core/src/rng/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
mod insecure;
mod secure;

pub use self::insecure::{InsecureRng, InsecureSeed};
pub use self::secure::{SecureRng, SecureSeed};
use bevy_ecs::world::World;
use bevy_utils::tracing::trace;
use getrandom::getrandom;
#[doc(hidden)]
pub use rand::{
rngs::{SmallRng, StdRng},
seq::SliceRandom,
CryptoRng, Rng, RngCore, SeedableRng,
};

/// Helper for configuring and creating the default random number generators.
/// For end-users who want full control, insert the default random number generators into the resource map manually.
/// If the random number generators are already inserted, this helper will do nothing.
#[derive(Clone, Debug)]
pub struct DefaultRngOptions {
/// The seed to use for secure / cryptographic operations.
secure_seed: [u8; 32],
/// The seed to use for insecure / non-cryptographic operations. If set to `None`,
/// the seed from `secure_seed` will be reused, saving an allocation.
insecure_seed: Option<[u8; 32]>,
}

impl Default for DefaultRngOptions {
fn default() -> Self {
let mut seed: [u8; 32] = [0; 32];
getrandom(&mut seed).expect("failed to get cryptographic seed for rng");

// The default is to use the same secure / cryptographic seed for both rngs, so
// we do not specify an insecure seed.
DefaultRngOptions {
secure_seed: seed,
insecure_seed: None,
}
}
}

impl DefaultRngOptions {
/// Create a configuration that forces using a particular secure seed.
pub fn with_secure_seed(seed: [u8; 32]) -> Self {
DefaultRngOptions {
secure_seed: seed,
..Default::default()
}
}

/// Create a configuration that forces using a particular insecure seed.
pub fn with_insecure_seed(seed: [u8; 32]) -> Self {
DefaultRngOptions {
insecure_seed: Some(seed),
..Default::default()
}
}

/// Create a configuration that forces using particular seeds.
pub fn with_seeds(secure_seed: SecureSeed, insecure_seed: InsecureSeed) -> Self {
DefaultRngOptions {
secure_seed: *secure_seed,
insecure_seed: Some(*insecure_seed),
}
}

/// Inserts the default random number generators into the given resource map based on the configured values.
pub fn create_default_rngs(&self, world: &mut World) {
if !world.contains_resource::<SecureRng>() {
trace!("Creating secure RNG with seed: {:x?}", self.secure_seed);
world.insert_resource(SecureRng::from_seed(self.secure_seed));
}
if !world.contains_resource::<InsecureRng>() {
// Only use the insecure seed if set.
let seed = self.insecure_seed.unwrap_or(self.secure_seed);
trace!("Creating insecure RNG with seed: {:x?}", seed);
world.insert_resource(InsecureRng::from_seed(seed));
}
}
}
53 changes: 53 additions & 0 deletions crates/bevy_core/src/rng/secure.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
pub use rand::{
rngs::{SmallRng, StdRng},
seq::SliceRandom,
CryptoRng, Rng, RngCore, SeedableRng,
};
use std::ops::Deref;

/// A seed for seeding a [`SecureRng`].
pub struct SecureSeed([u8; 32]);
impl From<[u8; 32]> for SecureSeed {
fn from(item: [u8; 32]) -> Self {
SecureSeed(item)
}
}
impl Deref for SecureSeed {
type Target = [u8; 32];
fn deref(&self) -> &Self::Target {
&self.0
}
}

/// A random number generator suitable for use in cryptographic operations.
///
/// For non-cryptographic operations, use [`InsecureRng`](super::insecure::InsecureRng).
#[derive(Clone)]
pub struct SecureRng(StdRng);

impl Deref for SecureRng {
type Target = StdRng;
fn deref(&self) -> &Self::Target {
&self.0
}
}

impl SecureRng {
pub(crate) fn from_seed(seed: [u8; 32]) -> Self {
SecureRng(StdRng::from_seed(seed))
}
}
impl RngCore for SecureRng {
fn next_u32(&mut self) -> u32 {
self.0.next_u32()
}
fn next_u64(&mut self) -> u64 {
self.0.next_u64()
}
fn fill_bytes(&mut self, dest: &mut [u8]) {
self.0.fill_bytes(dest)
}
fn try_fill_bytes(&mut self, dest: &mut [u8]) -> Result<(), rand::Error> {
self.0.try_fill_bytes(dest)
}
}
3 changes: 3 additions & 0 deletions crates/bevy_internal/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,9 @@ wgpu_trace = ["bevy_wgpu/trace"]
trace = [ "bevy_app/trace", "bevy_ecs/trace" ]
trace_chrome = [ "bevy_log/tracing-chrome" ]

# Random number generator support (enabled by default)
rng = ["bevy_core/rng"]

# Image format support for texture loading (PNG and HDR are enabled by default)
hdr = ["bevy_render/hdr"]
png = ["bevy_render/png"]
Expand Down
1 change: 1 addition & 0 deletions docs/cargo_features.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
|bevy_winit|GUI support.|
|bevy_wgpu|Make use of GPU via [WebGPU](https://gpuweb.github.io/gpuweb/) support.|
|render|The render pipeline and all render related plugins.|
|rng|Support for random number generators.|
|png|PNG picture format support.|
|hdr|[HDR](https://en.wikipedia.org/wiki/High_dynamic_range) support.|
|mp3|MP3 audio format support.|
Expand Down
19 changes: 8 additions & 11 deletions examples/2d/contributors.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
use bevy::prelude::*;
use rand::{prelude::SliceRandom, Rng};
use std::{
collections::BTreeSet,
io::{BufRead, BufReader},
Expand Down Expand Up @@ -52,6 +51,7 @@ fn setup(
mut commands: Commands,
asset_server: Res<AssetServer>,
mut materials: ResMut<Assets<ColorMaterial>>,
mut rng: ResMut<InsecureRng>,
) {
let contribs = contributors();

Expand All @@ -65,16 +65,14 @@ fn setup(
idx: 0,
};

let mut rnd = rand::thread_rng();

for name in contribs {
let pos = (rnd.gen_range(-400.0..400.0), rnd.gen_range(0.0..400.0));
let dir = rnd.gen_range(-1.0..1.0);
let pos = (rng.gen_range(-400.0..400.0), rng.gen_range(0.0..400.0));
let dir = rng.gen_range(-1.0..1.0);
let velocity = Vec3::new(dir * 500.0, 0.0, 0.0);
let hue = rnd.gen_range(0.0..=360.0);
let hue = rng.gen_range(0.0..=360.0);

// some sprites should be flipped
let flipped = rnd.gen_bool(0.5);
let flipped = rng.gen_bool(0.5);

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

Expand Down Expand Up @@ -106,7 +104,7 @@ fn setup(
sel.order.push((name, e));
}

sel.order.shuffle(&mut rnd);
sel.order.shuffle(&mut *rng);

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

Expand Down Expand Up @@ -245,10 +243,9 @@ fn velocity_system(time: Res<Time>, mut q: Query<&mut Velocity>) {
/// force.
fn collision_system(
wins: Res<Windows>,
mut rng: ResMut<InsecureRng>,
mut q: Query<(&mut Velocity, &mut Transform), With<Contributor>>,
) {
let mut rnd = rand::thread_rng();

let win = wins.get_primary().unwrap();

let ceiling = win.height() / 2.;
Expand All @@ -267,7 +264,7 @@ fn collision_system(
if bottom < ground {
t.translation.y = ground + SPRITE_SIZE / 2.0;
// apply an impulse upwards
v.translation.y = rnd.gen_range(700.0..1000.0);
v.translation.y = rng.gen_range(700.0..1000.0);
}
if top > ceiling {
t.translation.y = ceiling - SPRITE_SIZE / 2.0;
Expand Down
5 changes: 1 addition & 4 deletions examples/2d/many_sprites.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,6 @@ use bevy::{
sprite::SpriteSettings,
};

use rand::Rng;

const CAMERA_SPEED: f32 = 1000.0;

pub struct PrintTimer(Timer);
Expand All @@ -32,10 +30,9 @@ fn main() {
fn setup(
mut commands: Commands,
assets: Res<AssetServer>,
mut rng: ResMut<InsecureRng>,
mut materials: ResMut<Assets<ColorMaterial>>,
) {
let mut rng = rand::thread_rng();

let tile_size = Vec2::splat(64.0);
let map_size = Vec2::splat(320.0);

Expand Down
16 changes: 16 additions & 0 deletions examples/app/custom_rng_seeds.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
use bevy::prelude::*;

/// This example illustrates how to customize the random number generator seeds.
fn main() {
// These are not very good seeds!
let seed_with_ones: [u8; 32] = [1; 32];
let seed_with_twos: [u8; 32] = [2; 32];

App::build()
.insert_resource(DefaultRngOptions::with_seeds(
SecureSeed::from(seed_with_ones),
InsecureSeed::from(seed_with_twos),
))
.add_plugins(DefaultPlugins)
.run();
}
Loading