many_components/
many_components.rs1use bevy::{
16 diagnostic::{
17 DiagnosticPath, DiagnosticsPlugin, FrameTimeDiagnosticsPlugin, LogDiagnosticsPlugin,
18 },
19 ecs::{
20 component::{ComponentCloneBehavior, ComponentDescriptor, ComponentId, StorageType},
21 system::QueryParamBuilder,
22 world::FilteredEntityMut,
23 },
24 log::LogPlugin,
25 platform::collections::HashSet,
26 prelude::{App, In, IntoSystem, Query, Schedule, SystemParamBuilder, Update},
27 ptr::{OwningPtr, PtrMut},
28 MinimalPlugins,
29};
30
31use chacha20::ChaCha8Rng;
32use rand::prelude::{IndexedRandom, RngExt, SeedableRng};
33use std::{alloc::Layout, mem::ManuallyDrop, num::Wrapping};
34
35#[expect(unsafe_code, reason = "Reading dynamic components requires unsafe")]
36fn base_system(access_components: In<Vec<ComponentId>>, mut query: Query<FilteredEntityMut>) {
39 #[cfg(feature = "trace")]
40 let _span = tracing::info_span!("base_system", components = ?access_components.0, count = query.iter().len()).entered();
41
42 for mut filtered_entity in &mut query {
43 let mut total: Wrapping<u8> = Wrapping(0);
48 for (exponent, component_id) in (1_u32..).zip(access_components.0.iter()) {
49 let ptr = filtered_entity.get_by_id(*component_id).unwrap();
51
52 let value: u8 = unsafe { *ptr.deref::<u8>() };
54
55 for i in 0..=value {
56 let mut product = Wrapping(1);
57 for _ in 1..=exponent {
58 product *= Wrapping(i);
59 }
60 total += product;
61 }
62 }
63
64 for component_id in &access_components.0 {
66 if let Some(ptr) = filtered_entity.get_mut_by_id(*component_id) {
67 unsafe {
69 let mut value = ptr.with_type::<u8>();
70 *value = total.0;
71 }
72 }
73 }
74 }
75}
76
77#[expect(unsafe_code, reason = "Using dynamic components requires unsafe")]
78fn stress_test(num_entities: u32, num_components: u32, num_systems: u32) {
79 let mut rng = ChaCha8Rng::seed_from_u64(42);
80 let mut app = App::default();
81 let world = app.world_mut();
82
83 let component_ids: Vec<ComponentId> = (1..=num_components)
85 .map(|i| {
86 world.register_component_with_descriptor(
87 unsafe {
91 ComponentDescriptor::new_with_layout(
92 format!("Component{i}").to_string(),
93 StorageType::Table,
94 Layout::new::<u8>(),
95 None,
96 true, ComponentCloneBehavior::Default,
98 None,
99 )
100 },
101 )
102 })
103 .collect();
104
105 let mut schedule = Schedule::new(Update);
107 for _ in 1..=num_systems {
108 let num_access_components = rng.random_range(1..10);
109 let access_components: Vec<ComponentId> = component_ids
110 .sample(&mut rng, num_access_components)
111 .copied()
112 .collect();
113 let system = (QueryParamBuilder::new(|builder| {
114 for &access_component in &access_components {
115 if rand::random::<bool>() {
116 builder.mut_id(access_component);
117 } else {
118 builder.ref_id(access_component);
119 }
120 }
121 }),)
122 .build_state(world)
123 .build_any_system(base_system);
124 schedule.add_systems((move || access_components.clone()).pipe(system));
125 }
126
127 for _ in 1..=num_entities {
129 let num_components = rng.random_range(1..10);
130 let components: Vec<ComponentId> = component_ids
131 .sample(&mut rng, num_components)
132 .copied()
133 .collect();
134
135 let mut entity = world.spawn_empty();
136 let mut values: Vec<ManuallyDrop<u8>> = components
140 .iter()
141 .map(|_id| ManuallyDrop::new(rng.random_range(0..255)))
142 .collect();
143 let ptrs: Vec<OwningPtr> = values
144 .iter_mut()
145 .map(|value| {
146 unsafe { PtrMut::from(value).promote() }
150 })
151 .collect();
152 unsafe {
156 entity.insert_by_ids(&components, ptrs.into_iter());
157 }
158 }
159
160 app.add_schedule(schedule);
162 app.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([
167 DiagnosticPath::new("fps"),
168 ])));
169 app.run();
170}
171
172fn main() {
173 const DEFAULT_NUM_ENTITIES: u32 = 50000;
174 const DEFAULT_NUM_COMPONENTS: u32 = 1000;
175 const DEFAULT_NUM_SYSTEMS: u32 = 800;
176
177 let num_entities = std::env::args()
179 .nth(1)
180 .and_then(|string| string.parse::<u32>().ok())
181 .unwrap_or_else(|| {
182 println!("No valid number of entities provided, using default {DEFAULT_NUM_ENTITIES}");
183 DEFAULT_NUM_ENTITIES
184 });
185 let 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(|| {
190 println!(
191 "No valid number of components provided (>= 10), using default {DEFAULT_NUM_COMPONENTS}"
192 );
193 DEFAULT_NUM_COMPONENTS
194 });
195 let num_systems = std::env::args()
196 .nth(3)
197 .and_then(|string| string.parse::<u32>().ok())
198 .unwrap_or_else(|| {
199 println!("No valid number of systems provided, using default {DEFAULT_NUM_SYSTEMS}");
200 DEFAULT_NUM_SYSTEMS
201 });
202
203 stress_test(num_entities, num_components, num_systems);
204}