Path: blob/main/examples/stress_tests/many_components.rs
6592 views
//! Stress test for large ECS worlds.1//!2//! Running this example:3//!4//! ```5//! cargo run --profile stress-test --example many_components [<num_entities>] [<num_components>] [<num_systems>]6//! ```7//!8//! `num_entities`: The number of entities in the world (must be nonnegative)9//! `num_components`: the number of components in the world (must be at least 10)10//! `num_systems`: the number of systems in the world (must be nonnegative)11//!12//! If no valid number is provided, for each argument there's a reasonable default.1314use bevy::{15diagnostic::{16DiagnosticPath, DiagnosticsPlugin, FrameTimeDiagnosticsPlugin, LogDiagnosticsPlugin,17},18ecs::{19component::{ComponentCloneBehavior, ComponentDescriptor, ComponentId, StorageType},20system::QueryParamBuilder,21world::FilteredEntityMut,22},23log::LogPlugin,24platform::collections::HashSet,25prelude::{App, In, IntoSystem, Query, Schedule, SystemParamBuilder, Update},26ptr::{OwningPtr, PtrMut},27MinimalPlugins,28};2930use rand::prelude::{IndexedRandom, Rng, SeedableRng};31use rand_chacha::ChaCha8Rng;32use std::{alloc::Layout, mem::ManuallyDrop, num::Wrapping};3334#[expect(unsafe_code, reason = "Reading dynamic components requires unsafe")]35// A simple system that matches against several components and does some menial calculation to create36// some non-trivial load.37fn base_system(access_components: In<Vec<ComponentId>>, mut query: Query<FilteredEntityMut>) {38#[cfg(feature = "trace")]39let _span = tracing::info_span!("base_system", components = ?access_components.0, count = query.iter().len()).entered();4041for mut filtered_entity in &mut query {42// We calculate Faulhaber's formula mod 256 with n = value and p = exponent.43// See https://en.wikipedia.org/wiki/Faulhaber%27s_formula44// The time is takes to compute this depends on the number of entities and the values in45// each entity. This is to ensure that each system takes a different amount of time.46let mut total: Wrapping<u8> = Wrapping(0);47let mut exponent: u32 = 1;48for component_id in &access_components.0 {49// find the value of the component50let ptr = filtered_entity.get_by_id(*component_id).unwrap();5152// SAFETY: All components have a u8 layout53let value: u8 = unsafe { *ptr.deref::<u8>() };5455for i in 0..=value {56let mut product = Wrapping(1);57for _ in 1..=exponent {58product *= Wrapping(i);59}60total += product;61}62exponent += 1;63}6465// we assign this value to all the components we can write to66for component_id in &access_components.0 {67if let Some(ptr) = filtered_entity.get_mut_by_id(*component_id) {68// SAFETY: All components have a u8 layout69unsafe {70let mut value = ptr.with_type::<u8>();71*value = total.0;72}73}74}75}76}7778#[expect(unsafe_code, reason = "Using dynamic components requires unsafe")]79fn stress_test(num_entities: u32, num_components: u32, num_systems: u32) {80let mut rng = ChaCha8Rng::seed_from_u64(42);81let mut app = App::default();82let world = app.world_mut();8384// register a bunch of components85let component_ids: Vec<ComponentId> = (1..=num_components)86.map(|i| {87world.register_component_with_descriptor(88// SAFETY:89// * We don't implement a drop function90// * u8 is Sync and Send91unsafe {92ComponentDescriptor::new_with_layout(93format!("Component{i}").to_string(),94StorageType::Table,95Layout::new::<u8>(),96None,97true, // is mutable98ComponentCloneBehavior::Default,99)100},101)102})103.collect();104105// fill the schedule with systems106let mut schedule = Schedule::new(Update);107for _ in 1..=num_systems {108let num_access_components = rng.random_range(1..10);109let access_components: Vec<ComponentId> = component_ids110.choose_multiple(&mut rng, num_access_components)111.copied()112.collect();113let system = (QueryParamBuilder::new(|builder| {114for &access_component in &access_components {115if rand::random::<bool>() {116builder.mut_id(access_component);117} else {118builder.ref_id(access_component);119}120}121}),)122.build_state(world)123.build_any_system(base_system);124schedule.add_systems((move || access_components.clone()).pipe(system));125}126127// spawn a bunch of entities128for _ in 1..=num_entities {129let num_components = rng.random_range(1..10);130let components: Vec<ComponentId> = component_ids131.choose_multiple(&mut rng, num_components)132.copied()133.collect();134135let mut entity = world.spawn_empty();136// We use `ManuallyDrop` here as we need to avoid dropping the u8's when `values` is dropped137// since ownership of the values is passed to the world in `insert_by_ids`.138// But we do want to deallocate the memory when values is dropped.139let mut values: Vec<ManuallyDrop<u8>> = components140.iter()141.map(|_id| ManuallyDrop::new(rng.random_range(0..255)))142.collect();143let ptrs: Vec<OwningPtr> = values144.iter_mut()145.map(|value| {146// SAFETY:147// * We don't read/write `values` binding after this and values are `ManuallyDrop`,148// so we have the right to drop/move the values149unsafe { PtrMut::from(value).promote() }150})151.collect();152// SAFETY:153// * component_id's are from the same world154// * `values` was initialized above, so references are valid155unsafe {156entity.insert_by_ids(&components, ptrs.into_iter());157}158}159160// overwrite Update schedule in the app161app.add_schedule(schedule);162app.add_plugins(MinimalPlugins)163.add_plugins(DiagnosticsPlugin)164.add_plugins(LogPlugin::default())165.add_plugins(FrameTimeDiagnosticsPlugin::default())166.add_plugins(LogDiagnosticsPlugin::filtered(HashSet::from_iter([167DiagnosticPath::new("fps"),168])));169app.run();170}171172fn main() {173const DEFAULT_NUM_ENTITIES: u32 = 50000;174const DEFAULT_NUM_COMPONENTS: u32 = 1000;175const DEFAULT_NUM_SYSTEMS: u32 = 800;176177// take input178let num_entities = std::env::args()179.nth(1)180.and_then(|string| string.parse::<u32>().ok())181.unwrap_or_else(|| {182println!("No valid number of entities provided, using default {DEFAULT_NUM_ENTITIES}");183DEFAULT_NUM_ENTITIES184});185let num_components = std::env::args()186.nth(2)187.and_then(|string| string.parse::<u32>().ok())188.and_then(|n| if n >= 10 { Some(n) } else { None })189.unwrap_or_else(|| {190println!(191"No valid number of components provided (>= 10), using default {DEFAULT_NUM_COMPONENTS}"192);193DEFAULT_NUM_COMPONENTS194});195let num_systems = std::env::args()196.nth(3)197.and_then(|string| string.parse::<u32>().ok())198.unwrap_or_else(|| {199println!("No valid number of systems provided, using default {DEFAULT_NUM_SYSTEMS}");200DEFAULT_NUM_SYSTEMS201});202203stress_test(num_entities, num_components, num_systems);204}205206207