bevy_diagnostic/
log_diagnostics_plugin.rs1use super::{Diagnostic, DiagnosticPath, DiagnosticsStore};
2use alloc::vec::Vec;
3use bevy_app::prelude::*;
4use bevy_ecs::prelude::*;
5use bevy_time::{Real, Time, Timer, TimerMode};
6use core::time::Duration;
7use log::{debug, info};
8
9pub struct LogDiagnosticsPlugin {
17 pub debug: bool,
18 pub wait_duration: Duration,
19 pub filter: Option<Vec<DiagnosticPath>>,
20}
21
22#[derive(Resource)]
24struct LogDiagnosticsState {
25 timer: Timer,
26 filter: Option<Vec<DiagnosticPath>>,
27}
28
29impl Default for LogDiagnosticsPlugin {
30 fn default() -> Self {
31 LogDiagnosticsPlugin {
32 debug: false,
33 wait_duration: Duration::from_secs(1),
34 filter: None,
35 }
36 }
37}
38
39impl Plugin for LogDiagnosticsPlugin {
40 fn build(&self, app: &mut App) {
41 app.insert_resource(LogDiagnosticsState {
42 timer: Timer::new(self.wait_duration, TimerMode::Repeating),
43 filter: self.filter.clone(),
44 });
45
46 if self.debug {
47 app.add_systems(PostUpdate, Self::log_diagnostics_debug_system);
48 } else {
49 app.add_systems(PostUpdate, Self::log_diagnostics_system);
50 }
51 }
52}
53
54impl LogDiagnosticsPlugin {
55 pub fn filtered(filter: Vec<DiagnosticPath>) -> Self {
56 LogDiagnosticsPlugin {
57 filter: Some(filter),
58 ..Default::default()
59 }
60 }
61
62 fn for_each_diagnostic(
63 state: &LogDiagnosticsState,
64 diagnostics: &DiagnosticsStore,
65 mut callback: impl FnMut(&Diagnostic),
66 ) {
67 if let Some(filter) = &state.filter {
68 for path in filter {
69 if let Some(diagnostic) = diagnostics.get(path) {
70 if diagnostic.is_enabled {
71 callback(diagnostic);
72 }
73 }
74 }
75 } else {
76 for diagnostic in diagnostics.iter() {
77 if diagnostic.is_enabled {
78 callback(diagnostic);
79 }
80 }
81 }
82 }
83
84 fn log_diagnostic(path_width: usize, diagnostic: &Diagnostic) {
85 let Some(value) = diagnostic.smoothed() else {
86 return;
87 };
88
89 if diagnostic.get_max_history_length() > 1 {
90 let Some(average) = diagnostic.average() else {
91 return;
92 };
93
94 info!(
95 target: "bevy_diagnostic",
96 "{path:<path_width$}: {value:>11.6}{suffix:2} (avg {average:>.6}{suffix:})",
101 path = diagnostic.path(),
102 suffix = diagnostic.suffix,
103 );
104 } else {
105 info!(
106 target: "bevy_diagnostic",
107 "{path:<path_width$}: {value:>.6}{suffix:}",
108 path = diagnostic.path(),
109 suffix = diagnostic.suffix,
110 );
111 }
112 }
113
114 fn log_diagnostics(state: &LogDiagnosticsState, diagnostics: &DiagnosticsStore) {
115 let mut path_width = 0;
116 Self::for_each_diagnostic(state, diagnostics, |diagnostic| {
117 let width = diagnostic.path().as_str().len();
118 path_width = path_width.max(width);
119 });
120
121 Self::for_each_diagnostic(state, diagnostics, |diagnostic| {
122 Self::log_diagnostic(path_width, diagnostic);
123 });
124 }
125
126 fn log_diagnostics_system(
127 mut state: ResMut<LogDiagnosticsState>,
128 time: Res<Time<Real>>,
129 diagnostics: Res<DiagnosticsStore>,
130 ) {
131 if state.timer.tick(time.delta()).finished() {
132 Self::log_diagnostics(&state, &diagnostics);
133 }
134 }
135
136 fn log_diagnostics_debug_system(
137 mut state: ResMut<LogDiagnosticsState>,
138 time: Res<Time<Real>>,
139 diagnostics: Res<DiagnosticsStore>,
140 ) {
141 if state.timer.tick(time.delta()).finished() {
142 Self::for_each_diagnostic(&state, &diagnostics, |diagnostic| {
143 debug!("{:#?}\n", diagnostic);
144 });
145 }
146 }
147}