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