docs/bevy/examples/diagnostics/log_diagnostics.rs 10.2 K · 307 lines · raw · history

1 //! Shows different built-in plugins that logs diagnostics, like frames per second (FPS), to the console.
2
3 use bevy::{
4 color::palettes,
5 diagnostic::{
6 DiagnosticPath, EntityCountDiagnosticsPlugin, FrameTimeDiagnosticsPlugin,
7 LogDiagnosticsPlugin, LogDiagnosticsState, SystemInformationDiagnosticsPlugin,
8 },
9 prelude::*,
10 };
11
12 const FRAME_TIME_DIAGNOSTICS: [DiagnosticPath; 3] = [
13 FrameTimeDiagnosticsPlugin::FPS,
14 FrameTimeDiagnosticsPlugin::FRAME_COUNT,
15 FrameTimeDiagnosticsPlugin::FRAME_TIME,
16 ];
17 const ENTITY_COUNT_DIAGNOSTICS: [DiagnosticPath; 1] = [EntityCountDiagnosticsPlugin::ENTITY_COUNT];
18 const SYSTEM_INFO_DIAGNOSTICS: [DiagnosticPath; 4] = [
19 SystemInformationDiagnosticsPlugin::PROCESS_CPU_USAGE,
20 SystemInformationDiagnosticsPlugin::PROCESS_MEM_USAGE,
21 SystemInformationDiagnosticsPlugin::SYSTEM_CPU_USAGE,
22 SystemInformationDiagnosticsPlugin::SYSTEM_MEM_USAGE,
23 ];
24
25 fn main() {
26 App::new()
27 .add_plugins((
28 // The diagnostics plugins need to be added after DefaultPlugins as they use e.g. the time plugin for timestamps.
29 DefaultPlugins,
30 // Adds a system that prints diagnostics to the console.
31 // The other diagnostics plugins can still be used without this if you want to use them in an ingame overlay for example.
32 LogDiagnosticsPlugin::default(),
33 // Adds frame time, FPS and frame count diagnostics.
34 FrameTimeDiagnosticsPlugin::default(),
35 // Adds an entity count diagnostic.
36 EntityCountDiagnosticsPlugin::default(),
37 // Adds cpu and memory usage diagnostics for systems and the entire game process.
38 SystemInformationDiagnosticsPlugin,
39 // Forwards various diagnostics from the render app to the main app.
40 // These are pretty verbose but can be useful to pinpoint performance issues.
41 bevy::render::diagnostic::RenderDiagnosticsPlugin,
42 ))
43 // No rendering diagnostics are emitted unless something is drawn to the screen,
44 // so we spawn a small scene.
45 .add_systems(Startup, setup)
46 .add_systems(Update, filters_inputs)
47 .add_systems(
48 Update,
49 update_commands.run_if(
50 resource_exists_and_changed::<LogDiagnosticsStatus>
51 .or_eager(resource_exists_and_changed::<LogDiagnosticsFilters>),
52 ),
53 )
54 .run();
55 }
56
57 /// set up a simple 3D scene
58 fn setup(
59 mut commands: Commands,
60 mut meshes: ResMut<Assets<Mesh>>,
61 mut materials: ResMut<Assets<StandardMaterial>>,
62 ) {
63 // circular base
64 commands.spawn((
65 Mesh3d(meshes.add(Circle::new(4.0))),
66 MeshMaterial3d(materials.add(Color::WHITE)),
67 Transform::from_rotation(Quat::from_rotation_x(-std::f32::consts::FRAC_PI_2)),
68 ));
69 // cube
70 commands.spawn((
71 Mesh3d(meshes.add(Cuboid::new(1.0, 1.0, 1.0))),
72 MeshMaterial3d(materials.add(Color::srgb_u8(124, 144, 255))),
73 Transform::from_xyz(0.0, 0.5, 0.0),
74 ));
75 // light
76 commands.spawn((
77 PointLight {
78 shadow_maps_enabled: true,
79 ..default()
80 },
81 Transform::from_xyz(4.0, 8.0, 4.0),
82 ));
83 // camera
84 commands.spawn((
85 Camera3d::default(),
86 Transform::from_xyz(-2.5, 4.5, 9.0).looking_at(Vec3::ZERO, Vec3::Y),
87 ));
88
89 commands.init_resource::<LogDiagnosticsFilters>();
90 commands.init_resource::<LogDiagnosticsStatus>();
91
92 commands.spawn((
93 LogDiagnosticsCommands,
94 Node {
95 top: px(5),
96 left: px(5),
97 flex_direction: FlexDirection::Column,
98 ..default()
99 },
100 ));
101 }
102
103 fn filters_inputs(
104 keys: Res<ButtonInput<KeyCode>>,
105 mut status: ResMut<LogDiagnosticsStatus>,
106 mut filters: ResMut<LogDiagnosticsFilters>,
107 mut log_state: ResMut<LogDiagnosticsState>,
108 ) {
109 if keys.just_pressed(KeyCode::KeyQ) {
110 *status = match *status {
111 LogDiagnosticsStatus::Enabled => {
112 log_state.disable_filtering();
113 LogDiagnosticsStatus::Disabled
114 }
115 LogDiagnosticsStatus::Disabled => {
116 log_state.enable_filtering();
117 if filters.frame_time {
118 enable_filters(&mut log_state, FRAME_TIME_DIAGNOSTICS);
119 }
120 if filters.entity_count {
121 enable_filters(&mut log_state, ENTITY_COUNT_DIAGNOSTICS);
122 }
123 if filters.system_info {
124 enable_filters(&mut log_state, SYSTEM_INFO_DIAGNOSTICS);
125 }
126 LogDiagnosticsStatus::Enabled
127 }
128 };
129 }
130
131 let enabled = *status == LogDiagnosticsStatus::Enabled;
132 if keys.just_pressed(KeyCode::Digit1) {
133 filters.frame_time = !filters.frame_time;
134 if enabled {
135 if filters.frame_time {
136 enable_filters(&mut log_state, FRAME_TIME_DIAGNOSTICS);
137 } else {
138 disable_filters(&mut log_state, FRAME_TIME_DIAGNOSTICS);
139 }
140 }
141 }
142 if keys.just_pressed(KeyCode::Digit2) {
143 filters.entity_count = !filters.entity_count;
144 if enabled {
145 if filters.entity_count {
146 enable_filters(&mut log_state, ENTITY_COUNT_DIAGNOSTICS);
147 } else {
148 disable_filters(&mut log_state, ENTITY_COUNT_DIAGNOSTICS);
149 }
150 }
151 }
152 if keys.just_pressed(KeyCode::Digit3) {
153 filters.system_info = !filters.system_info;
154 if enabled {
155 if filters.system_info {
156 enable_filters(&mut log_state, SYSTEM_INFO_DIAGNOSTICS);
157 } else {
158 disable_filters(&mut log_state, SYSTEM_INFO_DIAGNOSTICS);
159 }
160 }
161 }
162 }
163
164 fn enable_filters(
165 log_state: &mut LogDiagnosticsState,
166 diagnostics: impl IntoIterator<Item = DiagnosticPath>,
167 ) {
168 log_state.extend_filter(diagnostics);
169 }
170
171 fn disable_filters(
172 log_state: &mut LogDiagnosticsState,
173 diagnostics: impl IntoIterator<Item = DiagnosticPath>,
174 ) {
175 for diagnostic in diagnostics {
176 log_state.remove_filter(&diagnostic);
177 }
178 }
179
180 fn update_commands(
181 mut commands: Commands,
182 log_commands: Single<Entity, With<LogDiagnosticsCommands>>,
183 status: Res<LogDiagnosticsStatus>,
184 filters: Res<LogDiagnosticsFilters>,
185 ) {
186 let enabled = *status == LogDiagnosticsStatus::Enabled;
187 let alpha = if enabled { 1. } else { 0.25 };
188 let enabled_color = |enabled| {
189 if enabled {
190 Color::from(palettes::tailwind::GREEN_400)
191 } else {
192 Color::from(palettes::tailwind::RED_400)
193 }
194 };
195 commands
196 .entity(*log_commands)
197 .despawn_related::<Children>()
198 .insert(children![
199 (
200 Node {
201 flex_direction: FlexDirection::Row,
202 column_gap: px(5),
203 ..default()
204 },
205 children![
206 Text::new("[Q] Toggle filtering:"),
207 (
208 Text::new(format!("{:?}", *status)),
209 TextColor(enabled_color(enabled))
210 )
211 ]
212 ),
213 (
214 Node {
215 flex_direction: FlexDirection::Row,
216 column_gap: px(5),
217 ..default()
218 },
219 children![
220 (
221 Text::new("[1] Frame times:"),
222 TextColor(Color::WHITE.with_alpha(alpha))
223 ),
224 (
225 Text::new(format!("{:?}", filters.frame_time)),
226 TextColor(enabled_color(filters.frame_time).with_alpha(alpha))
227 )
228 ]
229 ),
230 (
231 Node {
232 flex_direction: FlexDirection::Row,
233 column_gap: px(5),
234 ..default()
235 },
236 children![
237 (
238 Text::new("[2] Entity count:"),
239 TextColor(Color::WHITE.with_alpha(alpha))
240 ),
241 (
242 Text::new(format!("{:?}", filters.entity_count)),
243 TextColor(enabled_color(filters.entity_count).with_alpha(alpha))
244 )
245 ]
246 ),
247 (
248 Node {
249 flex_direction: FlexDirection::Row,
250 column_gap: px(5),
251 ..default()
252 },
253 children![
254 (
255 Text::new("[3] System info:"),
256 TextColor(Color::WHITE.with_alpha(alpha))
257 ),
258 (
259 Text::new(format!("{:?}", filters.system_info)),
260 TextColor(enabled_color(filters.system_info).with_alpha(alpha))
261 )
262 ]
263 ),
264 (
265 Node {
266 flex_direction: FlexDirection::Row,
267 column_gap: px(5),
268 ..default()
269 },
270 children![
271 (
272 Text::new("[4] Render diagnostics:"),
273 TextColor(Color::WHITE.with_alpha(alpha))
274 ),
275 (
276 Text::new("Private"),
277 TextColor(enabled_color(false).with_alpha(alpha))
278 )
279 ]
280 ),
281 ]);
282 }
283
284 #[derive(Debug, Default, PartialEq, Eq, Resource)]
285 enum LogDiagnosticsStatus {
286 /// No filtering, showing all logs
287 #[default]
288 Disabled,
289 /// Filtering enabled, showing only subset of logs
290 Enabled,
291 }
292
293 #[derive(Default, Resource)]
294 struct LogDiagnosticsFilters {
295 frame_time: bool,
296 entity_count: bool,
297 system_info: bool,
298 #[expect(
299 dead_code,
300 reason = "Currently the diagnostic paths referent to RenderDiagnosticPlugin are private"
301 )]
302 render_diagnostics: bool,
303 }
304
305 #[derive(Component)]
306 /// Marks the UI node that has instructions on how to change the filtering
307 struct LogDiagnosticsCommands;