1use bevy::{
4 color::palettes,
5 diagnostic::{
6 DiagnosticPath, EntityCountDiagnosticsPlugin, FrameTimeDiagnosticsPlugin,
7 LogDiagnosticsPlugin, LogDiagnosticsState, SystemInformationDiagnosticsPlugin,
8 },
9 prelude::*,
10};
11
12const FRAME_TIME_DIAGNOSTICS: [DiagnosticPath; 3] = [
13 FrameTimeDiagnosticsPlugin::FPS,
14 FrameTimeDiagnosticsPlugin::FRAME_COUNT,
15 FrameTimeDiagnosticsPlugin::FRAME_TIME,
16];
17const ENTITY_COUNT_DIAGNOSTICS: [DiagnosticPath; 1] = [EntityCountDiagnosticsPlugin::ENTITY_COUNT];
18const 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
25fn main() {
26 App::new()
27 .add_plugins((
28 DefaultPlugins,
30 LogDiagnosticsPlugin::default(),
33 FrameTimeDiagnosticsPlugin::default(),
35 EntityCountDiagnosticsPlugin::default(),
37 SystemInformationDiagnosticsPlugin,
39 bevy::render::diagnostic::RenderDiagnosticsPlugin,
42 ))
43 .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(resource_exists_and_changed::<LogDiagnosticsFilters>),
52 ),
53 )
54 .run();
55}
56
57fn setup(
59 mut commands: Commands,
60 mut meshes: ResMut<Assets<Mesh>>,
61 mut materials: ResMut<Assets<StandardMaterial>>,
62) {
63 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 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 commands.spawn((
77 PointLight {
78 shadows_enabled: true,
79 ..default()
80 },
81 Transform::from_xyz(4.0, 8.0, 4.0),
82 ));
83 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
103fn 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
164fn enable_filters(
165 log_state: &mut LogDiagnosticsState,
166 diagnostics: impl IntoIterator<Item = DiagnosticPath>,
167) {
168 log_state.extend_filter(diagnostics);
169}
170
171fn 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
180fn 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)]
285enum LogDiagnosticsStatus {
286 #[default]
288 Disabled,
289 Enabled,
291}
292
293#[derive(Default, Resource)]
294struct 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)]
306struct LogDiagnosticsCommands;