bevy_diagnostic/
log_diagnostics_plugin.rs1use super::{Diagnostic, DiagnosticPath, DiagnosticsStore};
2
3use bevy_app::prelude::*;
4use bevy_ecs::prelude::*;
5use bevy_platform::collections::HashSet;
6use bevy_time::{Real, Time, Timer, TimerMode};
7use core::time::Duration;
8use log::{debug, info};
9
10pub struct LogDiagnosticsPlugin {
18 pub debug: bool,
23 pub wait_duration: Duration,
25 pub filter: Option<HashSet<DiagnosticPath>>,
27}
28
29#[derive(Resource)]
31pub struct LogDiagnosticsState {
32 timer: Timer,
33 filter: Option<HashSet<DiagnosticPath>>,
34}
35
36impl LogDiagnosticsState {
37 pub fn set_timer_duration(&mut self, duration: Duration) {
39 self.timer.set_duration(duration);
40 self.timer.set_elapsed(Duration::ZERO);
41 }
42
43 pub fn add_filter(&mut self, diagnostic_path: DiagnosticPath) -> bool {
46 if let Some(filter) = &mut self.filter {
47 filter.insert(diagnostic_path)
48 } else {
49 self.filter = Some(HashSet::from_iter([diagnostic_path]));
50 true
51 }
52 }
53
54 pub fn extend_filter(&mut self, iter: impl IntoIterator<Item = DiagnosticPath>) {
56 if let Some(filter) = &mut self.filter {
57 filter.extend(iter);
58 } else {
59 self.filter = Some(HashSet::from_iter(iter));
60 }
61 }
62
63 pub fn remove_filter(&mut self, diagnostic_path: &DiagnosticPath) -> bool {
65 if let Some(filter) = &mut self.filter {
66 filter.remove(diagnostic_path)
67 } else {
68 false
69 }
70 }
71
72 pub fn clear_filter(&mut self) {
74 if let Some(filter) = &mut self.filter {
75 filter.clear();
76 }
77 }
78
79 pub fn enable_filtering(&mut self) {
81 self.filter = Some(HashSet::new());
82 }
83
84 pub fn disable_filtering(&mut self) {
86 self.filter = None;
87 }
88}
89
90impl Default for LogDiagnosticsPlugin {
91 fn default() -> Self {
92 LogDiagnosticsPlugin {
93 debug: false,
94 wait_duration: Duration::from_secs(1),
95 filter: None,
96 }
97 }
98}
99
100impl Plugin for LogDiagnosticsPlugin {
101 fn build(&self, app: &mut App) {
102 app.insert_resource(LogDiagnosticsState {
103 timer: Timer::new(self.wait_duration, TimerMode::Repeating),
104 filter: self.filter.clone(),
105 });
106
107 if self.debug {
108 app.add_systems(PostUpdate, Self::log_diagnostics_debug_system);
109 } else {
110 app.add_systems(PostUpdate, Self::log_diagnostics_system);
111 }
112 }
113}
114
115impl LogDiagnosticsPlugin {
116 pub fn filtered(filter: HashSet<DiagnosticPath>) -> Self {
118 LogDiagnosticsPlugin {
119 filter: Some(filter),
120 ..Default::default()
121 }
122 }
123
124 fn for_each_diagnostic(
125 state: &LogDiagnosticsState,
126 diagnostics: &DiagnosticsStore,
127 mut callback: impl FnMut(&Diagnostic),
128 ) {
129 if let Some(filter) = &state.filter {
130 for path in filter.iter() {
131 if let Some(diagnostic) = diagnostics.get(path)
132 && diagnostic.is_enabled
133 {
134 callback(diagnostic);
135 }
136 }
137 } else {
138 for diagnostic in diagnostics.iter() {
139 if diagnostic.is_enabled {
140 callback(diagnostic);
141 }
142 }
143 }
144 }
145
146 fn log_diagnostic(path_width: usize, diagnostic: &Diagnostic) {
147 let Some(value) = diagnostic.smoothed() else {
148 return;
149 };
150
151 if diagnostic.get_max_history_length() > 1 {
152 let Some(average) = diagnostic.average() else {
153 return;
154 };
155
156 info!(
157 target: "bevy_diagnostic",
158 "{path:<path_width$}: {value:>11.6}{suffix:2} (avg {average:>.6}{suffix:})",
163 path = diagnostic.path(),
164 suffix = diagnostic.suffix,
165 );
166 } else {
167 info!(
168 target: "bevy_diagnostic",
169 "{path:<path_width$}: {value:>.6}{suffix:}",
170 path = diagnostic.path(),
171 suffix = diagnostic.suffix,
172 );
173 }
174 }
175
176 fn log_diagnostics(state: &LogDiagnosticsState, diagnostics: &DiagnosticsStore) {
177 let mut path_width = 0;
178 Self::for_each_diagnostic(state, diagnostics, |diagnostic| {
179 let width = diagnostic.path().as_str().len();
180 path_width = path_width.max(width);
181 });
182
183 Self::for_each_diagnostic(state, diagnostics, |diagnostic| {
184 Self::log_diagnostic(path_width, diagnostic);
185 });
186 }
187
188 fn log_diagnostics_system(
189 mut state: ResMut<LogDiagnosticsState>,
190 time: Res<Time<Real>>,
191 diagnostics: Res<DiagnosticsStore>,
192 ) {
193 if state.timer.tick(time.delta()).is_finished() {
194 Self::log_diagnostics(&state, &diagnostics);
195 }
196 }
197
198 fn log_diagnostics_debug_system(
199 mut state: ResMut<LogDiagnosticsState>,
200 time: Res<Time<Real>>,
201 diagnostics: Res<DiagnosticsStore>,
202 ) {
203 if state.timer.tick(time.delta()).is_finished() {
204 Self::for_each_diagnostic(&state, &diagnostics, |diagnostic| {
205 debug!("{diagnostic:#?}\n");
206 });
207 }
208 }
209}