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