Expose LogDiagnosticsState (#19323)

# Objective

Closes #19175 
Make `LogDiagnosticsState` public to be able to edit its filters

## Solution

Make `LogDiagnosticsState` public and add methods to allow editing the
duration and filter

## Testing

`cargo run -p ci`

## Showcase

Updated `log_diagnostics` example

![image](https://github.com/user-attachments/assets/25bc00f3-40e2-4b4a-b90b-137cc1f307a5)
This commit is contained in:
Lucas Franca 2025-05-23 17:56:36 -03:00 committed by GitHub
parent f95287ca9b
commit d3012df755
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 329 additions and 13 deletions

View File

@ -29,7 +29,7 @@ pub use diagnostic::*;
pub use entity_count_diagnostics_plugin::EntityCountDiagnosticsPlugin; pub use entity_count_diagnostics_plugin::EntityCountDiagnosticsPlugin;
pub use frame_count_diagnostics_plugin::{update_frame_count, FrameCount, FrameCountPlugin}; pub use frame_count_diagnostics_plugin::{update_frame_count, FrameCount, FrameCountPlugin};
pub use frame_time_diagnostics_plugin::FrameTimeDiagnosticsPlugin; pub use frame_time_diagnostics_plugin::FrameTimeDiagnosticsPlugin;
pub use log_diagnostics_plugin::LogDiagnosticsPlugin; pub use log_diagnostics_plugin::{LogDiagnosticsPlugin, LogDiagnosticsState};
#[cfg(feature = "sysinfo_plugin")] #[cfg(feature = "sysinfo_plugin")]
pub use system_information_diagnostics_plugin::{SystemInfo, SystemInformationDiagnosticsPlugin}; pub use system_information_diagnostics_plugin::{SystemInfo, SystemInformationDiagnosticsPlugin};

View File

@ -1,7 +1,8 @@
use super::{Diagnostic, DiagnosticPath, DiagnosticsStore}; use super::{Diagnostic, DiagnosticPath, DiagnosticsStore};
use alloc::vec::Vec;
use bevy_app::prelude::*; use bevy_app::prelude::*;
use bevy_ecs::prelude::*; use bevy_ecs::prelude::*;
use bevy_platform::collections::HashSet;
use bevy_time::{Real, Time, Timer, TimerMode}; use bevy_time::{Real, Time, Timer, TimerMode};
use core::time::Duration; use core::time::Duration;
use log::{debug, info}; use log::{debug, info};
@ -16,14 +17,68 @@ use log::{debug, info};
pub struct LogDiagnosticsPlugin { pub struct LogDiagnosticsPlugin {
pub debug: bool, pub debug: bool,
pub wait_duration: Duration, pub wait_duration: Duration,
pub filter: Option<Vec<DiagnosticPath>>, pub filter: Option<HashSet<DiagnosticPath>>,
} }
/// State used by the [`LogDiagnosticsPlugin`] /// State used by the [`LogDiagnosticsPlugin`]
#[derive(Resource)] #[derive(Resource)]
struct LogDiagnosticsState { pub struct LogDiagnosticsState {
timer: Timer, timer: Timer,
filter: Option<Vec<DiagnosticPath>>, filter: Option<HashSet<DiagnosticPath>>,
}
impl LogDiagnosticsState {
/// Sets a new duration for the log timer
pub fn set_timer_duration(&mut self, duration: Duration) {
self.timer.set_duration(duration);
self.timer.set_elapsed(Duration::ZERO);
}
/// Add a filter to the log state, returning `true` if the [`DiagnosticPath`]
/// was not present
pub fn add_filter(&mut self, diagnostic_path: DiagnosticPath) -> bool {
if let Some(filter) = &mut self.filter {
filter.insert(diagnostic_path)
} else {
self.filter = Some(HashSet::from_iter([diagnostic_path]));
true
}
}
/// Extends the filter of the log state with multiple [`DiagnosticPaths`](DiagnosticPath)
pub fn extend_filter(&mut self, iter: impl IntoIterator<Item = DiagnosticPath>) {
if let Some(filter) = &mut self.filter {
filter.extend(iter);
} else {
self.filter = Some(HashSet::from_iter(iter));
}
}
/// Removes a filter from the log state, returning `true` if it was present
pub fn remove_filter(&mut self, diagnostic_path: &DiagnosticPath) -> bool {
if let Some(filter) = &mut self.filter {
filter.remove(diagnostic_path)
} else {
false
}
}
/// Clears the filters of the log state
pub fn clear_filter(&mut self) {
if let Some(filter) = &mut self.filter {
filter.clear();
}
}
/// Enables filtering with empty filters
pub fn enable_filtering(&mut self) {
self.filter = Some(HashSet::new());
}
/// Disables filtering
pub fn disable_filtering(&mut self) {
self.filter = None;
}
} }
impl Default for LogDiagnosticsPlugin { impl Default for LogDiagnosticsPlugin {
@ -52,7 +107,7 @@ impl Plugin for LogDiagnosticsPlugin {
} }
impl LogDiagnosticsPlugin { impl LogDiagnosticsPlugin {
pub fn filtered(filter: Vec<DiagnosticPath>) -> Self { pub fn filtered(filter: HashSet<DiagnosticPath>) -> Self {
LogDiagnosticsPlugin { LogDiagnosticsPlugin {
filter: Some(filter), filter: Some(filter),
..Default::default() ..Default::default()
@ -65,7 +120,7 @@ impl LogDiagnosticsPlugin {
mut callback: impl FnMut(&Diagnostic), mut callback: impl FnMut(&Diagnostic),
) { ) {
if let Some(filter) = &state.filter { if let Some(filter) = &state.filter {
for path in filter { for path in filter.iter() {
if let Some(diagnostic) = diagnostics.get(path) { if let Some(diagnostic) = diagnostics.get(path) {
if diagnostic.is_enabled { if diagnostic.is_enabled {
callback(diagnostic); callback(diagnostic);

View File

@ -1,10 +1,27 @@
//! Shows different built-in plugins that logs diagnostics, like frames per second (FPS), to the console. //! Shows different built-in plugins that logs diagnostics, like frames per second (FPS), to the console.
use bevy::{ use bevy::{
diagnostic::{FrameTimeDiagnosticsPlugin, LogDiagnosticsPlugin}, color::palettes,
diagnostic::{
DiagnosticPath, EntityCountDiagnosticsPlugin, FrameTimeDiagnosticsPlugin,
LogDiagnosticsPlugin, LogDiagnosticsState, SystemInformationDiagnosticsPlugin,
},
prelude::*, prelude::*,
}; };
const FRAME_TIME_DIAGNOSTICS: [DiagnosticPath; 3] = [
FrameTimeDiagnosticsPlugin::FPS,
FrameTimeDiagnosticsPlugin::FRAME_COUNT,
FrameTimeDiagnosticsPlugin::FRAME_TIME,
];
const ENTITY_COUNT_DIAGNOSTICS: [DiagnosticPath; 1] = [EntityCountDiagnosticsPlugin::ENTITY_COUNT];
const SYSTEM_INFO_DIAGNOSTICS: [DiagnosticPath; 4] = [
SystemInformationDiagnosticsPlugin::PROCESS_CPU_USAGE,
SystemInformationDiagnosticsPlugin::PROCESS_MEM_USAGE,
SystemInformationDiagnosticsPlugin::SYSTEM_CPU_USAGE,
SystemInformationDiagnosticsPlugin::SYSTEM_MEM_USAGE,
];
fn main() { fn main() {
App::new() App::new()
.add_plugins(( .add_plugins((
@ -16,9 +33,9 @@ fn main() {
// Adds frame time, FPS and frame count diagnostics. // Adds frame time, FPS and frame count diagnostics.
FrameTimeDiagnosticsPlugin::default(), FrameTimeDiagnosticsPlugin::default(),
// Adds an entity count diagnostic. // Adds an entity count diagnostic.
bevy::diagnostic::EntityCountDiagnosticsPlugin, EntityCountDiagnosticsPlugin,
// Adds cpu and memory usage diagnostics for systems and the entire game process. // Adds cpu and memory usage diagnostics for systems and the entire game process.
bevy::diagnostic::SystemInformationDiagnosticsPlugin, SystemInformationDiagnosticsPlugin,
// Forwards various diagnostics from the render app to the main app. // Forwards various diagnostics from the render app to the main app.
// These are pretty verbose but can be useful to pinpoint performance issues. // These are pretty verbose but can be useful to pinpoint performance issues.
bevy::render::diagnostic::RenderDiagnosticsPlugin, bevy::render::diagnostic::RenderDiagnosticsPlugin,
@ -26,6 +43,14 @@ fn main() {
// No rendering diagnostics are emitted unless something is drawn to the screen, // No rendering diagnostics are emitted unless something is drawn to the screen,
// so we spawn a small scene. // so we spawn a small scene.
.add_systems(Startup, setup) .add_systems(Startup, setup)
.add_systems(Update, filters_inputs)
.add_systems(
Update,
update_commands.run_if(
resource_exists_and_changed::<LogDiagnosticsStatus>
.or(resource_exists_and_changed::<LogDiagnosticsFilters>),
),
)
.run(); .run();
} }
@ -60,4 +85,223 @@ fn setup(
Camera3d::default(), Camera3d::default(),
Transform::from_xyz(-2.5, 4.5, 9.0).looking_at(Vec3::ZERO, Vec3::Y), Transform::from_xyz(-2.5, 4.5, 9.0).looking_at(Vec3::ZERO, Vec3::Y),
)); ));
commands.init_resource::<LogDiagnosticsFilters>();
commands.init_resource::<LogDiagnosticsStatus>();
commands.spawn((
LogDiagnosticsCommands,
Node {
top: Val::Px(5.),
left: Val::Px(5.),
flex_direction: FlexDirection::Column,
..default()
},
));
} }
fn filters_inputs(
keys: Res<ButtonInput<KeyCode>>,
mut status: ResMut<LogDiagnosticsStatus>,
mut filters: ResMut<LogDiagnosticsFilters>,
mut log_state: ResMut<LogDiagnosticsState>,
) {
if keys.just_pressed(KeyCode::KeyQ) {
*status = match *status {
LogDiagnosticsStatus::Enabled => {
log_state.disable_filtering();
LogDiagnosticsStatus::Disabled
}
LogDiagnosticsStatus::Disabled => {
log_state.enable_filtering();
if filters.frame_time {
enable_filters(&mut log_state, FRAME_TIME_DIAGNOSTICS);
}
if filters.entity_count {
enable_filters(&mut log_state, ENTITY_COUNT_DIAGNOSTICS);
}
if filters.system_info {
enable_filters(&mut log_state, SYSTEM_INFO_DIAGNOSTICS);
}
LogDiagnosticsStatus::Enabled
}
};
}
let enabled = *status == LogDiagnosticsStatus::Enabled;
if keys.just_pressed(KeyCode::Digit1) {
filters.frame_time = !filters.frame_time;
if enabled {
if filters.frame_time {
enable_filters(&mut log_state, FRAME_TIME_DIAGNOSTICS);
} else {
disable_filters(&mut log_state, FRAME_TIME_DIAGNOSTICS);
}
}
}
if keys.just_pressed(KeyCode::Digit2) {
filters.entity_count = !filters.entity_count;
if enabled {
if filters.entity_count {
enable_filters(&mut log_state, ENTITY_COUNT_DIAGNOSTICS);
} else {
disable_filters(&mut log_state, ENTITY_COUNT_DIAGNOSTICS);
}
}
}
if keys.just_pressed(KeyCode::Digit3) {
filters.system_info = !filters.system_info;
if enabled {
if filters.system_info {
enable_filters(&mut log_state, SYSTEM_INFO_DIAGNOSTICS);
} else {
disable_filters(&mut log_state, SYSTEM_INFO_DIAGNOSTICS);
}
}
}
}
fn enable_filters(
log_state: &mut LogDiagnosticsState,
diagnostics: impl IntoIterator<Item = DiagnosticPath>,
) {
log_state.extend_filter(diagnostics);
}
fn disable_filters(
log_state: &mut LogDiagnosticsState,
diagnostics: impl IntoIterator<Item = DiagnosticPath>,
) {
for diagnostic in diagnostics {
log_state.remove_filter(&diagnostic);
}
}
fn update_commands(
mut commands: Commands,
log_commands: Single<Entity, With<LogDiagnosticsCommands>>,
status: Res<LogDiagnosticsStatus>,
filters: Res<LogDiagnosticsFilters>,
) {
let enabled = *status == LogDiagnosticsStatus::Enabled;
let alpha = if enabled { 1. } else { 0.25 };
let enabled_color = |enabled| {
if enabled {
Color::from(palettes::tailwind::GREEN_400)
} else {
Color::from(palettes::tailwind::RED_400)
}
};
commands
.entity(*log_commands)
.despawn_related::<Children>()
.insert(children![
(
Node {
flex_direction: FlexDirection::Row,
column_gap: Val::Px(5.),
..default()
},
children![
Text::new("[Q] Toggle filtering:"),
(
Text::new(format!("{:?}", *status)),
TextColor(enabled_color(enabled))
)
]
),
(
Node {
flex_direction: FlexDirection::Row,
column_gap: Val::Px(5.),
..default()
},
children![
(
Text::new("[1] Frame times:"),
TextColor(Color::WHITE.with_alpha(alpha))
),
(
Text::new(format!("{:?}", filters.frame_time)),
TextColor(enabled_color(filters.frame_time).with_alpha(alpha))
)
]
),
(
Node {
flex_direction: FlexDirection::Row,
column_gap: Val::Px(5.),
..default()
},
children![
(
Text::new("[2] Entity count:"),
TextColor(Color::WHITE.with_alpha(alpha))
),
(
Text::new(format!("{:?}", filters.entity_count)),
TextColor(enabled_color(filters.entity_count).with_alpha(alpha))
)
]
),
(
Node {
flex_direction: FlexDirection::Row,
column_gap: Val::Px(5.),
..default()
},
children![
(
Text::new("[3] System info:"),
TextColor(Color::WHITE.with_alpha(alpha))
),
(
Text::new(format!("{:?}", filters.system_info)),
TextColor(enabled_color(filters.system_info).with_alpha(alpha))
)
]
),
(
Node {
flex_direction: FlexDirection::Row,
column_gap: Val::Px(5.),
..default()
},
children![
(
Text::new("[4] Render diagnostics:"),
TextColor(Color::WHITE.with_alpha(alpha))
),
(
Text::new("Private"),
TextColor(enabled_color(false).with_alpha(alpha))
)
]
),
]);
}
#[derive(Debug, Default, PartialEq, Eq, Resource)]
enum LogDiagnosticsStatus {
/// No filtering, showing all logs
#[default]
Disabled,
/// Filtering enabled, showing only subset of logs
Enabled,
}
#[derive(Default, Resource)]
struct LogDiagnosticsFilters {
frame_time: bool,
entity_count: bool,
system_info: bool,
#[expect(
dead_code,
reason = "Currently the diagnostic paths referent to RenderDiagnosticPlugin are private"
)]
render_diagnostics: bool,
}
#[derive(Component)]
/// Marks the UI node that has instructions on how to change the filtering
struct LogDiagnosticsCommands;

View File

@ -22,6 +22,7 @@ use bevy::{
world::FilteredEntityMut, world::FilteredEntityMut,
}, },
log::LogPlugin, log::LogPlugin,
platform::collections::HashSet,
prelude::{App, In, IntoSystem, Query, Schedule, SystemParamBuilder, Update}, prelude::{App, In, IntoSystem, Query, Schedule, SystemParamBuilder, Update},
ptr::{OwningPtr, PtrMut}, ptr::{OwningPtr, PtrMut},
MinimalPlugins, MinimalPlugins,
@ -168,9 +169,9 @@ fn stress_test(num_entities: u32, num_components: u32, num_systems: u32) {
.add_plugins(DiagnosticsPlugin) .add_plugins(DiagnosticsPlugin)
.add_plugins(LogPlugin::default()) .add_plugins(LogPlugin::default())
.add_plugins(FrameTimeDiagnosticsPlugin::default()) .add_plugins(FrameTimeDiagnosticsPlugin::default())
.add_plugins(LogDiagnosticsPlugin::filtered(vec![DiagnosticPath::new( .add_plugins(LogDiagnosticsPlugin::filtered(HashSet::from_iter([
"fps", DiagnosticPath::new("fps"),
)])); ])));
app.run(); app.run();
} }

View File

@ -0,0 +1,8 @@
---
title: Change filters container of `LogDiagnosticsState` to `HashSet`
authors: ["@hukasu"]
pull_requests: [19323]
---
Make `LogDiagnosticsState`'s filter container and argument of
`LogDiagnosticPlugin::filtered` into `HashSet`.

View File

@ -0,0 +1,8 @@
---
title: LogDiagnosticsState is now public
authors: ["@hukasu"]
pull_requests: [19323]
---
Make `LogDiagnosticsState` public to allow editing its duration and filters during
runtime.