Make some examples deterministic (#16488)

# Objective

- Improve reproducibility of examples

## Solution

- Use seeded rng when needed
- Use fixed z-ordering when needed

## Testing

```sh
steps=5;
echo "cpu_draw\nparallel_query\nanimated_fox\ntransparency_2d" > test
cargo run -p example-showcase -- run --stop-frame 250 --screenshot-frame 100 --fixed-frame-time 0.05 --example-list test --in-ci;
mv screenshots base;
for prefix in `seq 0 $steps`;
do
  echo step $prefix;
  cargo run -p example-showcase -- run --stop-frame 250 --screenshot-frame 100 --fixed-frame-time 0.05 --example-list test;
  mv screenshots $prefix-screenshots;
done;
mv base screenshots
for prefix in `seq 0 $steps`;
do
  echo check $prefix
  for file in screenshots/*/*;
  do
    echo $file;
    diff $file $prefix-$file;
  done;
done;
```
This commit is contained in:
François Mockers 2024-11-23 19:28:47 +01:00 committed by GitHub
parent 636e99c9fb
commit a9a4b069b6
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 33 additions and 15 deletions

View file

@ -11,7 +11,8 @@ use bevy::render::{
render_asset::RenderAssetUsages,
render_resource::{Extent3d, TextureDimension, TextureFormat},
};
use rand::Rng;
use rand::{Rng, SeedableRng};
use rand_chacha::ChaCha8Rng;
const IMAGE_WIDTH: u32 = 256;
const IMAGE_HEIGHT: u32 = 256;
@ -33,6 +34,9 @@ fn main() {
#[derive(Resource)]
struct MyProcGenImage(Handle<Image>);
#[derive(Resource)]
struct SeededRng(ChaCha8Rng);
fn setup(mut commands: Commands, mut images: ResMut<Assets<Image>>) {
// spawn a camera
commands.spawn(Camera2d);
@ -80,6 +84,11 @@ fn setup(mut commands: Commands, mut images: ResMut<Assets<Image>>) {
// create a sprite entity using our image
commands.spawn(Sprite::from_image(handle.clone()));
commands.insert_resource(MyProcGenImage(handle));
// We're seeding the PRNG here to make this example deterministic for testing purposes.
// This isn't strictly required in practical use unless you need your app to be deterministic.
let seeded_rng = ChaCha8Rng::seed_from_u64(19878367467712);
commands.insert_resource(SeededRng(seeded_rng));
}
/// Every fixed update tick, draw one more pixel to make a spiral pattern
@ -89,12 +98,11 @@ fn draw(
// used to keep track of where we are
mut i: Local<u32>,
mut draw_color: Local<Color>,
mut seeded_rng: ResMut<SeededRng>,
) {
let mut rng = rand::thread_rng();
if *i == 0 {
// Generate a random color on first run.
*draw_color = Color::linear_rgb(rng.gen(), rng.gen(), rng.gen());
*draw_color = Color::linear_rgb(seeded_rng.0.gen(), seeded_rng.0.gen(), seeded_rng.0.gen());
}
// Get the image from Bevy's asset storage.
@ -117,7 +125,7 @@ fn draw(
// If the old color is our current color, change our drawing color.
let tolerance = 1.0 / 255.0;
if old_color.distance(&draw_color) <= tolerance {
*draw_color = Color::linear_rgb(rng.gen(), rng.gen(), rng.gen());
*draw_color = Color::linear_rgb(seeded_rng.0.gen(), seeded_rng.0.gen(), seeded_rng.0.gen());
}
// Set the new color, but keep old alpha value from image.

View file

@ -23,7 +23,7 @@ fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
color: Color::srgba(0.0, 0.0, 1.0, 0.7),
..default()
},
Transform::from_xyz(100.0, 0.0, 0.0),
Transform::from_xyz(100.0, 0.0, 0.1),
));
commands.spawn((
Sprite {
@ -31,6 +31,6 @@ fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
color: Color::srgba(0.0, 1.0, 0.0, 0.3),
..default()
},
Transform::from_xyz(200.0, 0.0, 0.0),
Transform::from_xyz(200.0, 0.0, 0.2),
));
}

View file

@ -8,7 +8,8 @@ use bevy::{
pbr::CascadeShadowConfigBuilder,
prelude::*,
};
use rand::{thread_rng, Rng};
use rand::{Rng, SeedableRng};
use rand_chacha::ChaCha8Rng;
const FOX_PATH: &str = "models/animated/Fox.glb";
@ -28,6 +29,9 @@ fn main() {
.run();
}
#[derive(Resource)]
struct SeededRng(ChaCha8Rng);
#[derive(Resource)]
struct Animations {
animations: Vec<AnimationNodeIndex>,
@ -42,19 +46,19 @@ fn observe_on_step(
particle: Res<ParticleAssets>,
mut commands: Commands,
transforms: Query<&GlobalTransform>,
mut seeded_rng: ResMut<SeededRng>,
) {
let translation = transforms.get(trigger.entity()).unwrap().translation();
let mut rng = thread_rng();
// Spawn a bunch of particles.
for _ in 0..14 {
let horizontal = rng.gen::<Dir2>() * rng.gen_range(8.0..12.0);
let vertical = rng.gen_range(0.0..4.0);
let size = rng.gen_range(0.2..1.0);
let horizontal = seeded_rng.0.gen::<Dir2>() * seeded_rng.0.gen_range(8.0..12.0);
let vertical = seeded_rng.0.gen_range(0.0..4.0);
let size = seeded_rng.0.gen_range(0.2..1.0);
commands.queue(spawn_particle(
particle.mesh.clone(),
particle.material.clone(),
translation.reject_from_normalized(Vec3::Y),
rng.gen_range(0.2..0.6),
seeded_rng.0.gen_range(0.2..0.6),
size,
Vec3::new(horizontal.x, vertical, horizontal.y) * 10.0,
));
@ -121,6 +125,11 @@ fn setup(
println!(" - digit 1 / 3 / 5: play the animation <digit> times");
println!(" - L: loop the animation forever");
println!(" - return: change animation");
// We're seeding the PRNG here to make this example deterministic for testing purposes.
// This isn't strictly required in practical use unless you need your app to be deterministic.
let seeded_rng = ChaCha8Rng::seed_from_u64(19878367467712);
commands.insert_resource(SeededRng(seeded_rng));
}
// An `AnimationPlayer` is automatically added to the scene when it's ready.

View file

@ -14,10 +14,11 @@ fn spawn_system(mut commands: Commands, asset_server: Res<AssetServer>) {
// We're seeding the PRNG here to make this example deterministic for testing purposes.
// This isn't strictly required in practical use unless you need your app to be deterministic.
let mut rng = ChaCha8Rng::seed_from_u64(19878367467713);
for _ in 0..128 {
for z in 0..128 {
commands.spawn((
Sprite::from_image(texture.clone()),
Transform::from_scale(Vec3::splat(0.1)),
Transform::from_scale(Vec3::splat(0.1))
.with_translation(Vec2::splat(0.0).extend(z as f32)),
Velocity(20.0 * Vec2::new(rng.gen::<f32>() - 0.5, rng.gen::<f32>() - 0.5)),
));
}