Skip to content

Commit 1c074bb

Browse files
committed
Add bevy_entropy default plugin
1 parent 0aced5f commit 1c074bb

18 files changed

+314
-36
lines changed

Cargo.toml

+5-1
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,7 @@ bevy_internal = {path = "crates/bevy_internal", version = "0.5.0", default-featu
8787

8888
[dev-dependencies]
8989
anyhow = "1.0.4"
90-
rand = "0.8.0"
90+
rand = { version = "0.8.0", features = ["small_rng"] }
9191
ron = "0.6.2"
9292
serde = {version = "1", features = ["derive"]}
9393
# Needed to poll Task examples
@@ -212,6 +212,10 @@ path = "examples/app/plugin.rs"
212212
name = "plugin_group"
213213
path = "examples/app/plugin_group.rs"
214214

215+
[[example]]
216+
name = "random"
217+
path = "examples/app/random.rs"
218+
215219
[[example]]
216220
name = "return_after_run"
217221
path = "examples/app/return_after_run.rs"

crates/bevy_ecs/Cargo.toml

+2
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,9 @@ rand = "0.8"
3232
serde = "1"
3333

3434
[dev-dependencies]
35+
bevy_entropy = { path = "../bevy_entropy", version = "0.5.0" }
3536
parking_lot = "0.11"
37+
rand = { version = "0.8", features = ["small_rng"] }
3638

3739
[[example]]
3840
name = "events"

crates/bevy_ecs/examples/change_detection.rs

+38-8
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
use bevy_ecs::prelude::*;
2-
use rand::Rng;
2+
use bevy_ecs::schedule::RunOnce;
3+
use bevy_entropy::Entropy;
4+
use rand::{rngs::SmallRng, Rng, SeedableRng};
35
use std::ops::Deref;
46

57
// In this example we will simulate a population of entities. In every tick we will:
6-
// 1. spawn a new entity with a certain possibility
8+
// 1. spawn a new entity with a certain deterministic probability
79
// 2. age all entities
810
// 3. despawn entities with age > 2
911
//
@@ -13,17 +15,33 @@ fn main() {
1315
// Create a new empty World to hold our Entities, Components and Resources
1416
let mut world = World::new();
1517

18+
// Add the entropy resource for future random number generators to use.
19+
// This makes execution deterministic.
20+
let world_seed = [1; 32];
21+
world.insert_resource(Entropy::from(world_seed));
22+
1623
// Add the counter resource to remember how many entities where spawned
1724
world.insert_resource(EntityCounter { value: 0 });
1825

19-
// Create a new Schedule, which defines an execution strategy for Systems
26+
// Create a new Schedule, which defines an execution strategy for Systems.
2027
let mut schedule = Schedule::default();
28+
2129
// Create a Stage to add to our Schedule. Each Stage in a schedule runs all of its systems
22-
// before moving on to the next Stage
23-
let mut update = SystemStage::parallel();
30+
// before moving on to the next Stage.
31+
// Here, we are creating a "startup" Stage with a schedule that runs once.
32+
let mut startup = SystemStage::parallel();
33+
startup.add_system(create_rng.system());
34+
schedule.add_stage(
35+
"startup",
36+
Schedule::default()
37+
.with_run_criteria(RunOnce::default())
38+
.with_stage("only_once", startup),
39+
);
2440

25-
// Add systems to the Stage to execute our app logic
41+
// Add systems to another Stage to execute our app logic.
2642
// We can label our systems to force a specific run-order between some of them
43+
// within the Stage.
44+
let mut update = SystemStage::parallel();
2745
update.add_system(spawn_entities.system().label(SimulationSystem::Spawn));
2846
update.add_system(
2947
print_counter_when_changed
@@ -62,11 +80,23 @@ enum SimulationSystem {
6280
Age,
6381
}
6482

83+
// This system creates a random number generator resource from [`Entropy`].
84+
fn create_rng(mut commands: Commands, mut entropy: ResMut<Entropy>) {
85+
let seed = entropy.get();
86+
println!(" seeding rng from entropy: {:?}", seed);
87+
let rng = SmallRng::from_seed(seed);
88+
commands.insert_resource(rng);
89+
}
90+
6591
// This system randomly spawns a new entity in 60% of all frames
6692
// The entity will start with an age of 0 frames
6793
// If an entity gets spawned, we increase the counter in the EntityCounter resource
68-
fn spawn_entities(mut commands: Commands, mut entity_counter: ResMut<EntityCounter>) {
69-
if rand::thread_rng().gen_bool(0.6) {
94+
fn spawn_entities(
95+
mut commands: Commands,
96+
mut entity_counter: ResMut<EntityCounter>,
97+
mut rng: ResMut<SmallRng>,
98+
) {
99+
if rng.gen_bool(0.6) {
70100
let entity_id = commands.spawn().insert(Age::default()).id();
71101
println!(" spawning {:?}", entity_id);
72102
entity_counter.value += 1;

crates/bevy_ecs/examples/resources.rs

+11-3
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
use bevy_ecs::prelude::*;
2-
use rand::Rng;
2+
use bevy_entropy::Entropy;
3+
use rand::{prelude::SmallRng, Rng, SeedableRng};
34
use std::ops::Deref;
45

56
// In this example we add a counter resource and increase it's value in one system,
@@ -8,6 +9,10 @@ fn main() {
89
// Create a world
910
let mut world = World::new();
1011

12+
// Add the entropy resource
13+
let world_seed = [1; 32];
14+
world.insert_resource(Entropy::from(world_seed));
15+
1116
// Add the counter resource
1217
world.insert_resource(Counter { value: 0 });
1318

@@ -38,8 +43,11 @@ enum CounterSystem {
3843
Increase,
3944
}
4045

41-
fn increase_counter(mut counter: ResMut<Counter>) {
42-
if rand::thread_rng().gen_bool(0.5) {
46+
fn increase_counter(mut counter: ResMut<Counter>, mut entropy: ResMut<Entropy>) {
47+
// Note that in a real system it would be better to create this once
48+
// as a resource.
49+
let mut rng = SmallRng::from_seed(entropy.get());
50+
if rng.gen_bool(0.5) {
4351
counter.value += 1;
4452
println!(" Increased counter value");
4553
}

crates/bevy_entropy/Cargo.toml

+20
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
[package]
2+
name = "bevy_entropy"
3+
version = "0.5.0"
4+
edition = "2018"
5+
authors = [
6+
"Bevy Contributors <[email protected]>",
7+
"Christian Legnitto <[email protected]>",
8+
]
9+
description = "Provides entropy functionality for Bevy Engine"
10+
homepage = "https://bevyengine.org"
11+
repository = "https://github.com/bevyengine/bevy"
12+
license = "MIT"
13+
keywords = ["bevy", "random", "entropy"]
14+
15+
[dependencies]
16+
# bevy
17+
bevy_app = { path = "../bevy_app", version = "0.5.0" }
18+
bevy_utils = { path = "../bevy_utils", version = "0.5.0" }
19+
# other
20+
rand = { version = "0.8", features = ["std_rng"] }

crates/bevy_entropy/src/lib.rs

+59
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
use bevy_app::{AppBuilder, Plugin};
2+
use bevy_utils::tracing::{debug, trace};
3+
use rand::{rngs::StdRng, RngCore, SeedableRng};
4+
5+
pub mod prelude {
6+
#[doc(hidden)]
7+
pub use crate::Entropy;
8+
}
9+
10+
/// Provides a source of entropy.
11+
/// This enables deterministic execution.
12+
#[derive(Default)]
13+
pub struct EntropyPlugin;
14+
15+
impl Plugin for EntropyPlugin {
16+
fn build(&self, app: &mut AppBuilder) {
17+
let world = app.world();
18+
if !world.contains_resource::<Entropy>() {
19+
trace!("Creating entropy");
20+
app.init_resource::<Entropy>();
21+
}
22+
}
23+
}
24+
25+
/// A resource that provides entropy.
26+
pub struct Entropy(StdRng);
27+
28+
impl Default for Entropy {
29+
/// The default entropy source is non-deterministic and seeded from the operating system.
30+
/// For a deterministic source, use [`Entropy::from`].
31+
fn default() -> Self {
32+
debug!("Entropy created via the operating system");
33+
let rng = StdRng::from_entropy();
34+
Entropy(rng)
35+
}
36+
}
37+
38+
impl Entropy {
39+
/// Create a deterministic source of entropy. All random number generators
40+
/// later seeded from an [`Entropy`] created this way will be deterministic.
41+
/// If determinism is not required, use [`Entropy::default`].
42+
pub fn from(seed: [u8; 32]) -> Self {
43+
debug!("Entropy created via seed: {:?} ", seed);
44+
let rng = StdRng::from_seed(seed);
45+
Entropy(rng)
46+
}
47+
48+
/// Fill `dest` with entropy data. For an allocating alternative, see [`Entropy::get`].
49+
pub fn fill_bytes(&mut self, dest: &mut [u8]) {
50+
self.0.fill_bytes(dest)
51+
}
52+
53+
/// Allocate and return entropy data. For a non-allocating alternative, see [`Entropy::fill_bytes`].
54+
pub fn get(&mut self) -> [u8; 32] {
55+
let mut dest = [0; 32];
56+
self.0.fill_bytes(&mut dest);
57+
dest
58+
}
59+
}

crates/bevy_internal/Cargo.toml

+1
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@ bevy_core = { path = "../bevy_core", version = "0.5.0" }
5555
bevy_derive = { path = "../bevy_derive", version = "0.5.0" }
5656
bevy_diagnostic = { path = "../bevy_diagnostic", version = "0.5.0" }
5757
bevy_ecs = { path = "../bevy_ecs", version = "0.5.0" }
58+
bevy_entropy = { path = "../bevy_entropy", version = "0.5.0" }
5859
bevy_input = { path = "../bevy_input", version = "0.5.0" }
5960
bevy_log = { path = "../bevy_log", version = "0.5.0" }
6061
bevy_math = { path = "../bevy_math", version = "0.5.0" }

crates/bevy_internal/src/default_plugins.rs

+3
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ use bevy_asset::AssetPlugin;
66
use bevy_audio::AudioPlugin;
77
use bevy_core::CorePlugin;
88
use bevy_diagnostic::DiagnosticsPlugin;
9+
use bevy_entropy::EntropyPlugin;
910
#[cfg(feature = "bevy_gilrs")]
1011
use bevy_gilrs::GilrsPlugin;
1112
#[cfg(feature = "bevy_gltf")]
@@ -39,6 +40,7 @@ use bevy_winit::WinitPlugin;
3940
/// * [`WindowPlugin`]
4041
/// * [`AssetPlugin`]
4142
/// * [`ScenePlugin`]
43+
/// * [`EntropyPlugin`]
4244
/// * [`RenderPlugin`] - with feature `bevy_render`
4345
/// * [`SpritePlugin`] - with feature `bevy_sprite`
4446
/// * [`PbrPlugin`] - with feature `bevy_pbr`
@@ -61,6 +63,7 @@ impl PluginGroup for DefaultPlugins {
6163
group.add(WindowPlugin::default());
6264
group.add(AssetPlugin::default());
6365
group.add(ScenePlugin::default());
66+
group.add(EntropyPlugin::default());
6467

6568
#[cfg(feature = "bevy_render")]
6669
group.add(RenderPlugin::default());

crates/bevy_internal/src/lib.rs

+5
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,11 @@ pub mod ecs {
2929
pub use bevy_ecs::*;
3030
}
3131

32+
pub mod entropy {
33+
//! Resources for entropy.
34+
pub use bevy_entropy::*;
35+
}
36+
3237
pub mod input {
3338
//! Resources and events for inputs, e.g. mouse/keyboard, touch, gamepads, etc.
3439
pub use bevy_input::*;

crates/bevy_internal/src/prelude.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
#[doc(hidden)]
22
pub use crate::{
3-
app::prelude::*, asset::prelude::*, core::prelude::*, ecs::prelude::*, input::prelude::*,
4-
log::prelude::*, math::prelude::*, reflect::prelude::*, scene::prelude::*,
3+
app::prelude::*, asset::prelude::*, core::prelude::*, ecs::prelude::*, entropy::prelude::*,
4+
input::prelude::*, log::prelude::*, math::prelude::*, reflect::prelude::*, scene::prelude::*,
55
transform::prelude::*, window::prelude::*, DefaultPlugins, MinimalPlugins,
66
};
77

examples/2d/contributors.rs

+8-3
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,16 @@
11
use bevy::prelude::*;
2-
use rand::{prelude::SliceRandom, Rng};
2+
use rand::{prelude::SliceRandom, rngs::SmallRng, Rng, SeedableRng};
33
use std::{
44
collections::BTreeSet,
55
io::{BufRead, BufReader},
66
process::Stdio,
77
};
88

99
fn main() {
10+
let world_seed = [1; 32];
11+
1012
App::build()
13+
.insert_resource(Entropy::from(world_seed))
1114
.add_plugins(DefaultPlugins)
1215
.add_startup_system(setup)
1316
.add_system(velocity_system)
@@ -52,6 +55,7 @@ fn setup(
5255
mut commands: Commands,
5356
asset_server: Res<AssetServer>,
5457
mut materials: ResMut<Assets<ColorMaterial>>,
58+
mut entropy: ResMut<Entropy>,
5559
) {
5660
let contribs = contributors();
5761

@@ -65,7 +69,7 @@ fn setup(
6569
idx: 0,
6670
};
6771

68-
let mut rnd = rand::thread_rng();
72+
let mut rnd = SmallRng::from_seed(entropy.get());
6973

7074
for name in contribs {
7175
let pos = (rnd.gen_range(-400.0..400.0), rnd.gen_range(0.0..400.0));
@@ -246,8 +250,9 @@ fn velocity_system(time: Res<Time>, mut q: Query<&mut Velocity>) {
246250
fn collision_system(
247251
wins: Res<Windows>,
248252
mut q: Query<(&mut Velocity, &mut Transform), With<Contributor>>,
253+
mut entropy: ResMut<Entropy>,
249254
) {
250-
let mut rnd = rand::thread_rng();
255+
let mut rnd = SmallRng::from_seed(entropy.get());
251256

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

examples/2d/many_sprites.rs

+6-2
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ use bevy::{
55
sprite::SpriteSettings,
66
};
77

8-
use rand::Rng;
8+
use rand::{rngs::SmallRng, Rng, SeedableRng};
99

1010
const CAMERA_SPEED: f32 = 1000.0;
1111

@@ -15,7 +15,10 @@ pub struct Position(Transform);
1515
/// This example is for performance testing purposes.
1616
/// See https://github.com/bevyengine/bevy/pull/1492
1717
fn main() {
18+
let world_seed = [1; 32];
19+
1820
App::build()
21+
.insert_resource(Entropy::from(world_seed))
1922
.add_plugin(LogDiagnosticsPlugin::default())
2023
.add_plugin(FrameTimeDiagnosticsPlugin::default())
2124
.insert_resource(SpriteSettings {
@@ -33,8 +36,9 @@ fn setup(
3336
mut commands: Commands,
3437
assets: Res<AssetServer>,
3538
mut materials: ResMut<Assets<ColorMaterial>>,
39+
mut entropy: ResMut<Entropy>,
3640
) {
37-
let mut rng = rand::thread_rng();
41+
let mut rng = SmallRng::from_seed(entropy.get());
3842

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

examples/3d/spawner.rs

+6-2
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ use bevy::{
22
diagnostic::{FrameTimeDiagnosticsPlugin, LogDiagnosticsPlugin},
33
prelude::*,
44
};
5-
use rand::{rngs::StdRng, Rng, SeedableRng};
5+
use rand::{rngs::SmallRng, Rng, SeedableRng};
66

77
/// This example spawns a large number of cubes, each with its own changing position and material
88
/// This is intended to be a stress test of bevy's ability to render many objects with different
@@ -13,7 +13,10 @@ use rand::{rngs::StdRng, Rng, SeedableRng};
1313
/// NOTE: Bevy still has a number of optimizations to do in this area. Expect the
1414
/// performance here to go way up in the future
1515
fn main() {
16+
let world_seed = [1; 32];
17+
1618
App::build()
19+
.insert_resource(Entropy::from(world_seed))
1720
.add_plugins(DefaultPlugins)
1821
.add_plugin(FrameTimeDiagnosticsPlugin::default())
1922
.add_plugin(LogDiagnosticsPlugin::default())
@@ -39,6 +42,7 @@ fn setup(
3942
mut commands: Commands,
4043
mut meshes: ResMut<Assets<Mesh>>,
4144
mut materials: ResMut<Assets<StandardMaterial>>,
45+
mut entropy: ResMut<Entropy>,
4246
) {
4347
// light
4448
commands.spawn_bundle(PointLightBundle {
@@ -51,7 +55,7 @@ fn setup(
5155
..Default::default()
5256
});
5357

54-
let mut rng = StdRng::from_entropy();
58+
let mut rng = SmallRng::from_seed(entropy.get());
5559
let cube_handle = meshes.add(Mesh::from(shape::Cube { size: 1.0 }));
5660
for _ in 0..10000 {
5761
commands.spawn_bundle(PbrBundle {

0 commit comments

Comments
 (0)