From aff2f56b6757c92e249b2c8378a97982d29137fc Mon Sep 17 00:00:00 2001 From: Trashtalk Date: Sun, 1 Dec 2024 23:55:10 +0100 Subject: [PATCH 01/15] added a stress test --- benches/Cargo.toml | 1 + .../benches/bevy_ecs/scheduling/schedule.rs | 116 ++++++++++++++++++ 2 files changed, 117 insertions(+) diff --git a/benches/Cargo.toml b/benches/Cargo.toml index 25fa79256d914..7a4d0c171a7a3 100644 --- a/benches/Cargo.toml +++ b/benches/Cargo.toml @@ -15,6 +15,7 @@ bevy_ecs = { path = "../crates/bevy_ecs", features = ["multi_threaded"] } bevy_hierarchy = { path = "../crates/bevy_hierarchy" } bevy_math = { path = "../crates/bevy_math" } bevy_picking = { path = "../crates/bevy_picking", features = ["bevy_mesh"] } +bevy_ptr = { path = "../crates/bevy_ptr" } bevy_reflect = { path = "../crates/bevy_reflect", features = ["functions"] } bevy_render = { path = "../crates/bevy_render" } bevy_tasks = { path = "../crates/bevy_tasks" } diff --git a/benches/benches/bevy_ecs/scheduling/schedule.rs b/benches/benches/bevy_ecs/scheduling/schedule.rs index 4571899a9b7b5..8046ba347d239 100644 --- a/benches/benches/bevy_ecs/scheduling/schedule.rs +++ b/benches/benches/bevy_ecs/scheduling/schedule.rs @@ -1,6 +1,16 @@ use bevy_app::{App, Update}; use bevy_ecs::prelude::*; use criterion::Criterion; +use rand::Rng; +use bevy_ecs::system::QueryParamBuilder; +use std::alloc::Layout; +use bevy_ecs::component::StorageType; +use bevy_ecs::component::ComponentDescriptor; +use bevy_ecs::component::ComponentId; +use std::num::Wrapping; +use bevy_ecs::world::FilteredEntityMut; +use rand::prelude::SliceRandom; +use bevy_ptr::OwningPtr; pub fn schedule(c: &mut Criterion) { #[derive(Component)] @@ -143,3 +153,109 @@ pub fn empty_schedule_run(criterion: &mut Criterion) { }); group.finish(); } + +fn base_system(mut query: Query) { + for filtered_entity in &mut query { + // we calculate Faulhaber's formula (https://en.wikipedia.org/wiki/Faulhaber%27s_formula) mod 256 + // with n = value and p = exponent for each entity. + // The time is takes to compute this is dependant on the number of entities in the query and + // the values in each entity. This is to ensure that the running times between systems are varied. + let mut total: Wrapping = Wrapping(0); + let mut exponent: u32 = 1; + for component_id in filtered_entity.access().component_reads_and_writes().0 { + // find the value of the component + let ptr = filtered_entity.get_by_id(component_id).unwrap(); + // SAFETY: All components have a u8 layout. + let value: u8 = unsafe { *ptr.deref::() }; + for i in 0..=value { + let mut product = Wrapping(1); + for _ in 1..=exponent { + product *= Wrapping(i); + } + total += product; + } + exponent += 1; + } + + // we assign this value to all the components we can write to + for component_id in filtered_entity.access().component_reads_and_writes().0 { + let ptr = filtered_entity.get_by_id(component_id).unwrap(); + if filtered_entity.access().has_component_write(component_id) { + // SAFETY: + // We have exclusive access so the pointer is unique + // All components have a u8 layout + unsafe { + let value = ptr.assert_unique().deref_mut::(); + *value = total.0; + } + } + } + } +} + +// A benchmark that tests running many systems with a lot of components. +// This is mostly intended to test how quickly two systems can figure out how +// they are in conflict via Access.get_conflicts(other: Access) +fn many_components_and_systems(criterion: &mut Criterion) { + let num_components = 2000; + let num_systems = 4000; + let num_entities = 10000; + + let mut rng = rand::thread_rng(); + let mut world = World::default(); + + // register a bunch of components + let component_ids: Vec = (1..=num_components) + .map(|i| { + world.register_component_with_descriptor(unsafe { + ComponentDescriptor::new_with_layout( + format!("Component{}", i).to_string(), + StorageType::Table, + Layout::new::(), + None, + ) + }) + }) + .collect(); + + // fill the schedule with systems + let mut schedule = Schedule::default(); + for _ in 1..=num_systems { + let num_access_components = rng.gen_range(1..10); + let access_components = component_ids.choose_multiple(&mut rng, num_access_components); + let system = (QueryParamBuilder::new(|builder| { + for &access_component in access_components { + if rand::random::() { + builder.mut_id(access_component); + } else { + builder.ref_id(access_component); + } + } + }),) + .build_state(&mut world) + .build_system(base_system); + schedule.add_systems(system); + } + + // spawn a bunch of entities + for _ in 1..=num_entities { + let num_components = rng.gen_range(1..10); + let components = component_ids.choose_multiple(&mut rng, num_components); + + let mut entity = world.spawn_empty(); + for &component_id in components { + OwningPtr::make(rng.gen_range(0..255), |ptr| unsafe { + entity.insert_by_id(component_id, ptr); + }); + } + } + + let mut group = criterion.benchmark_group("run_large_schedule"); + group.warm_up_time(core::time::Duration::from_millis(500)); + group.measurement_time(core::time::Duration::from_secs(15)); + group.bench_function("large_schedule", |bencher| { + bencher.iter(|| { + schedule.run(&mut world); + }); + }); +} From 8cdbe6953d3dff73300a4a07aaf5bdcfd966aac8 Mon Sep 17 00:00:00 2001 From: Trashtalk Date: Mon, 2 Dec 2024 00:24:35 +0100 Subject: [PATCH 02/15] use deterministic rng --- .../benches/bevy_ecs/scheduling/schedule.rs | 21 +++++++++++-------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/benches/benches/bevy_ecs/scheduling/schedule.rs b/benches/benches/bevy_ecs/scheduling/schedule.rs index 8046ba347d239..0d0316712c830 100644 --- a/benches/benches/bevy_ecs/scheduling/schedule.rs +++ b/benches/benches/bevy_ecs/scheduling/schedule.rs @@ -1,16 +1,19 @@ use bevy_app::{App, Update}; -use bevy_ecs::prelude::*; + +use bevy_ecs::{ + component::{ComponentDescriptor, ComponentId, StorageType}, + prelude::*, + system::QueryParamBuilder, + world::FilteredEntityMut, +}; +use bevy_ptr::OwningPtr; use criterion::Criterion; +use rand::prelude::SeedableRng; +use rand::prelude::SliceRandom; use rand::Rng; -use bevy_ecs::system::QueryParamBuilder; +use rand_chacha::ChaCha8Rng; use std::alloc::Layout; -use bevy_ecs::component::StorageType; -use bevy_ecs::component::ComponentDescriptor; -use bevy_ecs::component::ComponentId; use std::num::Wrapping; -use bevy_ecs::world::FilteredEntityMut; -use rand::prelude::SliceRandom; -use bevy_ptr::OwningPtr; pub fn schedule(c: &mut Criterion) { #[derive(Component)] @@ -201,7 +204,7 @@ fn many_components_and_systems(criterion: &mut Criterion) { let num_systems = 4000; let num_entities = 10000; - let mut rng = rand::thread_rng(); + let mut rng = ChaCha8Rng::seed_from_u64(42); let mut world = World::default(); // register a bunch of components From c7285bfcd23bbce4fee50abfb7bf5fc82fcbfb3a Mon Sep 17 00:00:00 2001 From: Trashtalk Date: Mon, 2 Dec 2024 00:27:41 +0100 Subject: [PATCH 03/15] added bench to the list --- benches/benches/bevy_ecs/scheduling/mod.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/benches/benches/bevy_ecs/scheduling/mod.rs b/benches/benches/bevy_ecs/scheduling/mod.rs index 60d1620a89f5c..f9f21033ff3f8 100644 --- a/benches/benches/bevy_ecs/scheduling/mod.rs +++ b/benches/benches/bevy_ecs/scheduling/mod.rs @@ -20,4 +20,5 @@ criterion_group!( schedule, build_schedule, empty_schedule_run, + many_components_and_systems ); From c502fe1ca2939a2f0cf6b3c57eedcef56029d589 Mon Sep 17 00:00:00 2001 From: Trashtalk217 Date: Tue, 3 Dec 2024 01:01:31 +0100 Subject: [PATCH 04/15] Update benches/benches/bevy_ecs/scheduling/schedule.rs Co-authored-by: Chris Russell <8494645+chescock@users.noreply.github.com> --- benches/benches/bevy_ecs/scheduling/schedule.rs | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/benches/benches/bevy_ecs/scheduling/schedule.rs b/benches/benches/bevy_ecs/scheduling/schedule.rs index 0d0316712c830..0e52b356c4a65 100644 --- a/benches/benches/bevy_ecs/scheduling/schedule.rs +++ b/benches/benches/bevy_ecs/scheduling/schedule.rs @@ -182,13 +182,11 @@ fn base_system(mut query: Query) { // we assign this value to all the components we can write to for component_id in filtered_entity.access().component_reads_and_writes().0 { - let ptr = filtered_entity.get_by_id(component_id).unwrap(); - if filtered_entity.access().has_component_write(component_id) { + if let Some(ptr) = filtered_entity.get_mut_by_id(component_id) { // SAFETY: - // We have exclusive access so the pointer is unique // All components have a u8 layout unsafe { - let value = ptr.assert_unique().deref_mut::(); + let value = ptr.with_type::(); *value = total.0; } } From 8f7f4a02a534e4f756f82d12f9a1a92cef7d447e Mon Sep 17 00:00:00 2001 From: Trashtalk Date: Sat, 7 Dec 2024 13:43:19 +0100 Subject: [PATCH 05/15] moved bench over to stress-test --- Cargo.toml | 11 ++ benches/benches/bevy_ecs/scheduling/mod.rs | 1 - .../benches/bevy_ecs/scheduling/schedule.rs | 104 ---------- examples/stress_tests/many_components.rs | 187 ++++++++++++++++++ 4 files changed, 198 insertions(+), 105 deletions(-) create mode 100644 examples/stress_tests/many_components.rs diff --git a/Cargo.toml b/Cargo.toml index dc4fb9be494a4..b9e3245fe2ca7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -2728,6 +2728,17 @@ description = "Test rendering of many cameras and lights" category = "Stress Tests" wasm = true +[[example]] +name = "many_components" +path = "examples/stress_tests/many_components.rs" +doc-scrape-examples = true + +[package.metadata.example.many_components] +name = "Many Components (and Entities and Systems)" +description = "Test large ECS systems" +category = "Stress Tests" +wasm = false + [[example]] name = "many_cubes" path = "examples/stress_tests/many_cubes.rs" diff --git a/benches/benches/bevy_ecs/scheduling/mod.rs b/benches/benches/bevy_ecs/scheduling/mod.rs index f9f21033ff3f8..60d1620a89f5c 100644 --- a/benches/benches/bevy_ecs/scheduling/mod.rs +++ b/benches/benches/bevy_ecs/scheduling/mod.rs @@ -20,5 +20,4 @@ criterion_group!( schedule, build_schedule, empty_schedule_run, - many_components_and_systems ); diff --git a/benches/benches/bevy_ecs/scheduling/schedule.rs b/benches/benches/bevy_ecs/scheduling/schedule.rs index 0e52b356c4a65..180701d929254 100644 --- a/benches/benches/bevy_ecs/scheduling/schedule.rs +++ b/benches/benches/bevy_ecs/scheduling/schedule.rs @@ -156,107 +156,3 @@ pub fn empty_schedule_run(criterion: &mut Criterion) { }); group.finish(); } - -fn base_system(mut query: Query) { - for filtered_entity in &mut query { - // we calculate Faulhaber's formula (https://en.wikipedia.org/wiki/Faulhaber%27s_formula) mod 256 - // with n = value and p = exponent for each entity. - // The time is takes to compute this is dependant on the number of entities in the query and - // the values in each entity. This is to ensure that the running times between systems are varied. - let mut total: Wrapping = Wrapping(0); - let mut exponent: u32 = 1; - for component_id in filtered_entity.access().component_reads_and_writes().0 { - // find the value of the component - let ptr = filtered_entity.get_by_id(component_id).unwrap(); - // SAFETY: All components have a u8 layout. - let value: u8 = unsafe { *ptr.deref::() }; - for i in 0..=value { - let mut product = Wrapping(1); - for _ in 1..=exponent { - product *= Wrapping(i); - } - total += product; - } - exponent += 1; - } - - // we assign this value to all the components we can write to - for component_id in filtered_entity.access().component_reads_and_writes().0 { - if let Some(ptr) = filtered_entity.get_mut_by_id(component_id) { - // SAFETY: - // All components have a u8 layout - unsafe { - let value = ptr.with_type::(); - *value = total.0; - } - } - } - } -} - -// A benchmark that tests running many systems with a lot of components. -// This is mostly intended to test how quickly two systems can figure out how -// they are in conflict via Access.get_conflicts(other: Access) -fn many_components_and_systems(criterion: &mut Criterion) { - let num_components = 2000; - let num_systems = 4000; - let num_entities = 10000; - - let mut rng = ChaCha8Rng::seed_from_u64(42); - let mut world = World::default(); - - // register a bunch of components - let component_ids: Vec = (1..=num_components) - .map(|i| { - world.register_component_with_descriptor(unsafe { - ComponentDescriptor::new_with_layout( - format!("Component{}", i).to_string(), - StorageType::Table, - Layout::new::(), - None, - ) - }) - }) - .collect(); - - // fill the schedule with systems - let mut schedule = Schedule::default(); - for _ in 1..=num_systems { - let num_access_components = rng.gen_range(1..10); - let access_components = component_ids.choose_multiple(&mut rng, num_access_components); - let system = (QueryParamBuilder::new(|builder| { - for &access_component in access_components { - if rand::random::() { - builder.mut_id(access_component); - } else { - builder.ref_id(access_component); - } - } - }),) - .build_state(&mut world) - .build_system(base_system); - schedule.add_systems(system); - } - - // spawn a bunch of entities - for _ in 1..=num_entities { - let num_components = rng.gen_range(1..10); - let components = component_ids.choose_multiple(&mut rng, num_components); - - let mut entity = world.spawn_empty(); - for &component_id in components { - OwningPtr::make(rng.gen_range(0..255), |ptr| unsafe { - entity.insert_by_id(component_id, ptr); - }); - } - } - - let mut group = criterion.benchmark_group("run_large_schedule"); - group.warm_up_time(core::time::Duration::from_millis(500)); - group.measurement_time(core::time::Duration::from_secs(15)); - group.bench_function("large_schedule", |bencher| { - bencher.iter(|| { - schedule.run(&mut world); - }); - }); -} diff --git a/examples/stress_tests/many_components.rs b/examples/stress_tests/many_components.rs new file mode 100644 index 0000000000000..95053f0a7107d --- /dev/null +++ b/examples/stress_tests/many_components.rs @@ -0,0 +1,187 @@ +//! Stress test for large ECS worlds. +//! +//! Running this example: +//! +//! ``` +//! cargo run --profile stress-test --example many_components +//! ``` +//! +//! num_entities: The number of entities in the world (must be nonnegative) +//! num_components: the number of components in the world (must be at least 10) +//! num_systems: the number of systems in the world (must be nonnegative) + +use bevy::{ + diagnostic::{DiagnosticPath, FrameTimeDiagnosticsPlugin, LogDiagnosticsPlugin}, + ecs::{ + component::{ComponentDescriptor, ComponentId, StorageType}, + system::QueryParamBuilder, + world::FilteredEntityMut, + }, + prelude::{App, In, IntoSystem, Query, Schedule, SystemParamBuilder, Update}, + ptr::OwningPtr, + DefaultPlugins, +}; + +use rand::prelude::{Rng, SeedableRng, SliceRandom}; +use rand_chacha::ChaCha8Rng; +use std::{alloc::Layout, num::Wrapping}; + +// A simple sytem that matches against several components and does some menial calculation to create +// some non-trivial load. +fn base_system(access_components: In>, mut query: Query) { + for mut filtered_entity in &mut query { + // We calculate Faulhaber's formula mod 256 with n = value and p = exponent. + // See https://en.wikipedia.org/wiki/Faulhaber%27s_formula + // The time is takes to compute this depends on the number of entities and the values in + // each entity. This is to ensure that each system takes a different amount of time. + let mut total: Wrapping = Wrapping(0); + let mut exponent: u32 = 1; + for component_id in &access_components.0 { + // find the value of the component + let ptr = filtered_entity.get_by_id(*component_id).unwrap(); + + #[allow(unsafe_code)] + // SAFETY: All components have a u8 layout + let value: u8 = unsafe { *ptr.deref::() }; + + for i in 0..=value { + let mut product = Wrapping(1); + for _ in 1..=exponent { + product *= Wrapping(i); + } + total += product; + } + exponent += 1; + } + + // we assign this value to all the components we can write to + for component_id in &access_components.0 { + if let Some(ptr) = filtered_entity.get_mut_by_id(*component_id) { + #[allow(unsafe_code)] + // SAFETY: All components have a u8 layout + unsafe { + let mut value = ptr.with_type::(); + *value = total.0; + } + } + } + } +} + +fn stress_test(num_entities: u32, num_components: u32, num_systems: u32) { + let mut rng = ChaCha8Rng::seed_from_u64(42); + let mut app = App::default(); + let mut world = app.world_mut(); + + // register a bunch of components + let component_ids: Vec = (1..=num_components) + .map(|i| { + world.register_component_with_descriptor( + #[allow(unsafe_code)] + // SAFETY: + // we don't implement a drop function + // u32 is Sync and Send + unsafe { + ComponentDescriptor::new_with_layout( + format!("Component{}", i).to_string(), + StorageType::Table, + Layout::new::(), + None, + ) + }, + ) + }) + .collect(); + + // fill the schedule with systems + let mut schedule = Schedule::new(Update); + for _ in 1..=num_systems { + let num_access_components = rng.gen_range(1..10); + let access_components: Vec = component_ids + .choose_multiple(&mut rng, num_access_components) + .copied() + .collect(); + let system = (QueryParamBuilder::new(|builder| { + for &access_component in &access_components { + if rand::random::() { + builder.mut_id(access_component); + } else { + builder.ref_id(access_component); + } + } + }),) + .build_state(&mut world) + .build_any_system(base_system); + schedule.add_systems((move || access_components.clone()).pipe(system)); + } + + // spawn a bunch of entities + for _ in 1..=num_entities { + let num_components = rng.gen_range(1..10); + let components = component_ids.choose_multiple(&mut rng, num_components); + + let mut entity = world.spawn_empty(); + for &component_id in components { + let value: u8 = rng.gen_range(0..255); + OwningPtr::make(value, |ptr| { + #[allow(unsafe_code)] + // SAFETY: + // component_id is from the same world + // value is u8, so ptr is a valid reference for component_id + unsafe { + entity.insert_by_id(component_id, ptr); + } + }); + } + } + + // overwrite Update schedule in the app + app.add_schedule(schedule); + app.add_plugins(DefaultPlugins) + .add_plugins(FrameTimeDiagnosticsPlugin::default()) + .add_plugins(LogDiagnosticsPlugin::filtered(vec![DiagnosticPath::new( + "fps", + )])); + app.run(); +} + +pub fn main() { + let default_num_entities = 10000; + let default_num_components = 2000; + let default_num_systems = 500; + + // take input + let num_entities = std::env::args() + .nth(1) + .and_then(|string| string.parse::().ok()) + .unwrap_or_else(|| { + println!( + "No valid number of entities provided, using default {}", + default_num_entities + ); + default_num_entities + }); + let num_components = std::env::args() + .nth(2) + .and_then(|string| string.parse::().ok()) + .and_then(|n| if n >= 10 { Some(n) } else { None }) + .unwrap_or_else(|| { + println!( + "No valid number of components provided (>= 10), using default {}", + default_num_components + ); + default_num_components + }); + let num_systems = std::env::args() + .nth(3) + .and_then(|string| string.parse::().ok()) + .unwrap_or_else(|| { + println!( + "No valid number of systems provided, using default {}", + default_num_systems + ); + default_num_systems + }); + + stress_test(num_entities, num_components, num_systems); +} From f2794d75faa025fa0e9c1976e1475d2c9d24df36 Mon Sep 17 00:00:00 2001 From: Trashtalk Date: Sat, 7 Dec 2024 13:49:16 +0100 Subject: [PATCH 06/15] restore benches --- benches/benches/bevy_ecs/scheduling/schedule.rs | 15 +-------------- 1 file changed, 1 insertion(+), 14 deletions(-) diff --git a/benches/benches/bevy_ecs/scheduling/schedule.rs b/benches/benches/bevy_ecs/scheduling/schedule.rs index 180701d929254..4571899a9b7b5 100644 --- a/benches/benches/bevy_ecs/scheduling/schedule.rs +++ b/benches/benches/bevy_ecs/scheduling/schedule.rs @@ -1,19 +1,6 @@ use bevy_app::{App, Update}; - -use bevy_ecs::{ - component::{ComponentDescriptor, ComponentId, StorageType}, - prelude::*, - system::QueryParamBuilder, - world::FilteredEntityMut, -}; -use bevy_ptr::OwningPtr; +use bevy_ecs::prelude::*; use criterion::Criterion; -use rand::prelude::SeedableRng; -use rand::prelude::SliceRandom; -use rand::Rng; -use rand_chacha::ChaCha8Rng; -use std::alloc::Layout; -use std::num::Wrapping; pub fn schedule(c: &mut Criterion) { #[derive(Component)] From 9949d125f5e2abfa9ca7a5377e59b8bd3ce1af0f Mon Sep 17 00:00:00 2001 From: Trashtalk Date: Sat, 7 Dec 2024 13:51:30 +0100 Subject: [PATCH 07/15] revert benches/Cargo.toml --- benches/Cargo.toml | 1 - 1 file changed, 1 deletion(-) diff --git a/benches/Cargo.toml b/benches/Cargo.toml index d1c5b95c37116..6375af6d5e198 100644 --- a/benches/Cargo.toml +++ b/benches/Cargo.toml @@ -17,7 +17,6 @@ bevy_math = { path = "../crates/bevy_math" } bevy_picking = { path = "../crates/bevy_picking", features = [ "bevy_mesh_picking_backend", ] } -bevy_ptr = { path = "../crates/bevy_ptr" } bevy_reflect = { path = "../crates/bevy_reflect", features = ["functions"] } bevy_render = { path = "../crates/bevy_render" } bevy_tasks = { path = "../crates/bevy_tasks" } From 6a7faa51ff9a3c63e66aa11b9e31af8a6767e2c0 Mon Sep 17 00:00:00 2001 From: Trashtalk Date: Sat, 7 Dec 2024 13:54:59 +0100 Subject: [PATCH 08/15] fix typo --- examples/stress_tests/many_components.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/stress_tests/many_components.rs b/examples/stress_tests/many_components.rs index 95053f0a7107d..13e88ed31dcfe 100644 --- a/examples/stress_tests/many_components.rs +++ b/examples/stress_tests/many_components.rs @@ -26,7 +26,7 @@ use rand::prelude::{Rng, SeedableRng, SliceRandom}; use rand_chacha::ChaCha8Rng; use std::{alloc::Layout, num::Wrapping}; -// A simple sytem that matches against several components and does some menial calculation to create +// A simple system that matches against several components and does some menial calculation to create // some non-trivial load. fn base_system(access_components: In>, mut query: Query) { for mut filtered_entity in &mut query { From c3cd459a1da6efe19e14116fd2136e6cc6a11a7c Mon Sep 17 00:00:00 2001 From: Trashtalk Date: Sat, 7 Dec 2024 13:59:13 +0100 Subject: [PATCH 09/15] added to examples --- examples/README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/examples/README.md b/examples/README.md index 0027df377af2c..14a507cf1fbc3 100644 --- a/examples/README.md +++ b/examples/README.md @@ -460,6 +460,7 @@ Example | Description [Many Animated Sprites](../examples/stress_tests/many_animated_sprites.rs) | Displays many animated sprites in a grid arrangement with slight offsets to their animation timers. Used for performance testing. [Many Buttons](../examples/stress_tests/many_buttons.rs) | Test rendering of many UI elements [Many Cameras & Lights](../examples/stress_tests/many_cameras_lights.rs) | Test rendering of many cameras and lights +[Many Components (and Entities and Systems)](../examples/stress_tests/many_components.rs) | Test large ECS systems [Many Cubes](../examples/stress_tests/many_cubes.rs) | Simple benchmark to test per-entity draw overhead. Run with the `sphere` argument to test frustum culling [Many Foxes](../examples/stress_tests/many_foxes.rs) | Loads an animated fox model and spawns lots of them. Good for testing skinned mesh performance. Takes an unsigned integer argument for the number of foxes to spawn. Defaults to 1000 [Many Gizmos](../examples/stress_tests/many_gizmos.rs) | Test rendering of many gizmos From 9075794f7484619f58325805279cd272a635bf29 Mon Sep 17 00:00:00 2001 From: Trashtalk Date: Sat, 7 Dec 2024 14:19:30 +0100 Subject: [PATCH 10/15] small change for unmutable components --- examples/stress_tests/many_components.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/examples/stress_tests/many_components.rs b/examples/stress_tests/many_components.rs index 13e88ed31dcfe..526aa6c55db11 100644 --- a/examples/stress_tests/many_components.rs +++ b/examples/stress_tests/many_components.rs @@ -87,6 +87,7 @@ fn stress_test(num_entities: u32, num_components: u32, num_systems: u32) { StorageType::Table, Layout::new::(), None, + true, // is mutable ) }, ) From 809101f04d8fbfa640c06425fc849efb8c9a770a Mon Sep 17 00:00:00 2001 From: Trashtalk Date: Sat, 7 Dec 2024 14:29:52 +0100 Subject: [PATCH 11/15] added CI fixes --- examples/stress_tests/many_components.rs | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/examples/stress_tests/many_components.rs b/examples/stress_tests/many_components.rs index 526aa6c55db11..ea40b46985d32 100644 --- a/examples/stress_tests/many_components.rs +++ b/examples/stress_tests/many_components.rs @@ -6,9 +6,9 @@ //! cargo run --profile stress-test --example many_components //! ``` //! -//! num_entities: The number of entities in the world (must be nonnegative) -//! num_components: the number of components in the world (must be at least 10) -//! num_systems: the number of systems in the world (must be nonnegative) +//! `num_entities`: The number of entities in the world (must be nonnegative) +//! `num_components`: the number of components in the world (must be at least 10) +//! `num_systems`: the number of systems in the world (must be nonnegative) use bevy::{ diagnostic::{DiagnosticPath, FrameTimeDiagnosticsPlugin, LogDiagnosticsPlugin}, @@ -71,7 +71,7 @@ fn base_system(access_components: In>, mut query: Query = (1..=num_components) @@ -111,7 +111,7 @@ fn stress_test(num_entities: u32, num_components: u32, num_systems: u32) { } } }),) - .build_state(&mut world) + .build_state(world) .build_any_system(base_system); schedule.add_systems((move || access_components.clone()).pipe(system)); } @@ -139,13 +139,14 @@ fn stress_test(num_entities: u32, num_components: u32, num_systems: u32) { // overwrite Update schedule in the app app.add_schedule(schedule); app.add_plugins(DefaultPlugins) - .add_plugins(FrameTimeDiagnosticsPlugin::default()) + .add_plugins(FrameTimeDiagnosticsPlugin) .add_plugins(LogDiagnosticsPlugin::filtered(vec![DiagnosticPath::new( "fps", )])); app.run(); } +#[allow(missing_docs)] pub fn main() { let default_num_entities = 10000; let default_num_components = 2000; From 974d3f3a16f4a9e78df5486fb3e921762a882d53 Mon Sep 17 00:00:00 2001 From: Trashtalk Date: Sat, 7 Dec 2024 15:18:03 +0100 Subject: [PATCH 12/15] changed to minimal plugins, changed defaults --- examples/stress_tests/many_components.rs | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/examples/stress_tests/many_components.rs b/examples/stress_tests/many_components.rs index ea40b46985d32..c074ea5147142 100644 --- a/examples/stress_tests/many_components.rs +++ b/examples/stress_tests/many_components.rs @@ -11,15 +11,18 @@ //! `num_systems`: the number of systems in the world (must be nonnegative) use bevy::{ - diagnostic::{DiagnosticPath, FrameTimeDiagnosticsPlugin, LogDiagnosticsPlugin}, + diagnostic::{ + DiagnosticPath, DiagnosticsPlugin, FrameTimeDiagnosticsPlugin, LogDiagnosticsPlugin, + }, ecs::{ component::{ComponentDescriptor, ComponentId, StorageType}, system::QueryParamBuilder, world::FilteredEntityMut, }, + log::LogPlugin, prelude::{App, In, IntoSystem, Query, Schedule, SystemParamBuilder, Update}, ptr::OwningPtr, - DefaultPlugins, + MinimalPlugins, }; use rand::prelude::{Rng, SeedableRng, SliceRandom}; @@ -138,7 +141,9 @@ fn stress_test(num_entities: u32, num_components: u32, num_systems: u32) { // overwrite Update schedule in the app app.add_schedule(schedule); - app.add_plugins(DefaultPlugins) + app.add_plugins(MinimalPlugins) + .add_plugins(DiagnosticsPlugin) + .add_plugins(LogPlugin::default()) .add_plugins(FrameTimeDiagnosticsPlugin) .add_plugins(LogDiagnosticsPlugin::filtered(vec![DiagnosticPath::new( "fps", @@ -148,9 +153,9 @@ fn stress_test(num_entities: u32, num_components: u32, num_systems: u32) { #[allow(missing_docs)] pub fn main() { - let default_num_entities = 10000; - let default_num_components = 2000; - let default_num_systems = 500; + let default_num_entities = 50000; + let default_num_components = 1000; + let default_num_systems = 800; // take input let num_entities = std::env::args() From c6138b10adecd14baaedf308229243641b647d20 Mon Sep 17 00:00:00 2001 From: Trashtalk Date: Sat, 7 Dec 2024 20:05:24 +0100 Subject: [PATCH 13/15] incorporated suggestions --- examples/stress_tests/many_components.rs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/examples/stress_tests/many_components.rs b/examples/stress_tests/many_components.rs index c074ea5147142..437e23a9f4e7f 100644 --- a/examples/stress_tests/many_components.rs +++ b/examples/stress_tests/many_components.rs @@ -3,12 +3,14 @@ //! Running this example: //! //! ``` -//! cargo run --profile stress-test --example many_components +//! cargo run --profile stress-test --example many_components [] [] [] //! ``` //! //! `num_entities`: The number of entities in the world (must be nonnegative) //! `num_components`: the number of components in the world (must be at least 10) //! `num_systems`: the number of systems in the world (must be nonnegative) +//! +//! If no valid number is provided, for each argument there's a reasonable default. use bevy::{ diagnostic::{ @@ -83,7 +85,7 @@ fn stress_test(num_entities: u32, num_components: u32, num_systems: u32) { #[allow(unsafe_code)] // SAFETY: // we don't implement a drop function - // u32 is Sync and Send + // u8 is Sync and Send unsafe { ComponentDescriptor::new_with_layout( format!("Component{}", i).to_string(), From c23e14641244614dfbc302429e19faca05fe3724 Mon Sep 17 00:00:00 2001 From: Trashtalk217 Date: Mon, 9 Dec 2024 23:12:15 +0100 Subject: [PATCH 14/15] Apply suggestions from code review Co-authored-by: BD103 <59022059+BD103@users.noreply.github.com> --- examples/stress_tests/many_components.rs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/examples/stress_tests/many_components.rs b/examples/stress_tests/many_components.rs index 437e23a9f4e7f..450926711be97 100644 --- a/examples/stress_tests/many_components.rs +++ b/examples/stress_tests/many_components.rs @@ -45,7 +45,7 @@ fn base_system(access_components: In>, mut query: Query() }; @@ -62,7 +62,7 @@ fn base_system(access_components: In>, mut query: Query(); @@ -153,11 +153,11 @@ fn stress_test(num_entities: u32, num_components: u32, num_systems: u32) { app.run(); } -#[allow(missing_docs)] +#[expect(missing_docs)] pub fn main() { - let default_num_entities = 50000; - let default_num_components = 1000; - let default_num_systems = 800; + const DEFAULT_NUM_ENTITIES: u32 = 50000; + const DEFAULT_NUM_COMPONENTS: u32 = 1000; + const DEFAULT_NUM_SYSTEMS: u32 = 800; // take input let num_entities = std::env::args() From ab5706d70f76586d8abd17f53c46610fce0453c7 Mon Sep 17 00:00:00 2001 From: Trashtalk217 Date: Mon, 9 Dec 2024 23:31:37 +0100 Subject: [PATCH 15/15] fix variable names --- examples/stress_tests/many_components.rs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/examples/stress_tests/many_components.rs b/examples/stress_tests/many_components.rs index 450926711be97..4bb87d322d5c7 100644 --- a/examples/stress_tests/many_components.rs +++ b/examples/stress_tests/many_components.rs @@ -166,9 +166,9 @@ pub fn main() { .unwrap_or_else(|| { println!( "No valid number of entities provided, using default {}", - default_num_entities + DEFAULT_NUM_ENTITIES ); - default_num_entities + DEFAULT_NUM_ENTITIES }); let num_components = std::env::args() .nth(2) @@ -177,9 +177,9 @@ pub fn main() { .unwrap_or_else(|| { println!( "No valid number of components provided (>= 10), using default {}", - default_num_components + DEFAULT_NUM_COMPONENTS ); - default_num_components + DEFAULT_NUM_COMPONENTS }); let num_systems = std::env::args() .nth(3) @@ -187,9 +187,9 @@ pub fn main() { .unwrap_or_else(|| { println!( "No valid number of systems provided, using default {}", - default_num_systems + DEFAULT_NUM_SYSTEMS ); - default_num_systems + DEFAULT_NUM_SYSTEMS }); stress_test(num_entities, num_components, num_systems);