Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
bevyengine
GitHub Repository: bevyengine/bevy
Path: blob/main/crates/bevy_diagnostic/src/log_diagnostics_plugin.rs
6595 views
1
use super::{Diagnostic, DiagnosticPath, DiagnosticsStore};
2
3
use bevy_app::prelude::*;
4
use bevy_ecs::prelude::*;
5
use bevy_platform::collections::HashSet;
6
use bevy_time::{Real, Time, Timer, TimerMode};
7
use core::time::Duration;
8
use log::{debug, info};
9
10
/// An App Plugin that logs diagnostics to the console.
11
///
12
/// Diagnostics are collected by plugins such as
13
/// [`FrameTimeDiagnosticsPlugin`](crate::FrameTimeDiagnosticsPlugin)
14
/// or can be provided by the user.
15
///
16
/// When no diagnostics are provided, this plugin does nothing.
17
pub struct LogDiagnosticsPlugin {
18
/// If `true` then the `Debug` representation of each `Diagnostic` is logged.
19
/// If `false` then a (smoothed) current value and historical average are logged.
20
///
21
/// Defaults to `false`.
22
pub debug: bool,
23
/// Time to wait between logging diagnostics and logging them again.
24
pub wait_duration: Duration,
25
/// If `Some` then only these diagnostics are logged.
26
pub filter: Option<HashSet<DiagnosticPath>>,
27
}
28
29
/// State used by the [`LogDiagnosticsPlugin`]
30
#[derive(Resource)]
31
pub struct LogDiagnosticsState {
32
timer: Timer,
33
filter: Option<HashSet<DiagnosticPath>>,
34
}
35
36
impl LogDiagnosticsState {
37
/// Sets a new duration for the log timer
38
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
/// Add a filter to the log state, returning `true` if the [`DiagnosticPath`]
44
/// was not present
45
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
/// Extends the filter of the log state with multiple [`DiagnosticPaths`](DiagnosticPath)
55
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
/// Removes a filter from the log state, returning `true` if it was present
64
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
/// Clears the filters of the log state
73
pub fn clear_filter(&mut self) {
74
if let Some(filter) = &mut self.filter {
75
filter.clear();
76
}
77
}
78
79
/// Enables filtering with empty filters
80
pub fn enable_filtering(&mut self) {
81
self.filter = Some(HashSet::new());
82
}
83
84
/// Disables filtering
85
pub fn disable_filtering(&mut self) {
86
self.filter = None;
87
}
88
}
89
90
impl 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
100
impl 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
115
impl LogDiagnosticsPlugin {
116
/// Filter logging to only the paths in `filter`.
117
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
// Suffix is only used for 's' or 'ms' currently,
159
// so we reserve two columns for it; however,
160
// Do not reserve columns for the suffix in the average
161
// The ) hugging the value is more aesthetically pleasing
162
"{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
}
210
211