1use std::time;
2
3use crate::graphics;
4
5pub struct Debug {
20 font: graphics::Font,
21 enabled: bool,
22 load_start: time::Instant,
23 load_duration: time::Duration,
24 frame_start: time::Instant,
25 frame_durations: TimeBuffer,
26 interact_start: time::Instant,
27 interact_duration: time::Duration,
28 update_start: time::Instant,
29 update_durations: TimeBuffer,
30 draw_start: time::Instant,
31 draw_durations: TimeBuffer,
32 ui_start: time::Instant,
33 ui_durations: TimeBuffer,
34 debug_start: time::Instant,
35 debug_durations: TimeBuffer,
36 text: Vec<(String, String)>,
37 draw_rate: u16,
38 frames_until_refresh: u16,
39}
40
41impl Debug {
42 pub(crate) fn new(gpu: &mut graphics::Gpu) -> Self {
43 let now = time::Instant::now();
44
45 Self {
46 font: graphics::Font::from_bytes(gpu, graphics::Font::DEFAULT)
47 .expect("Load debug font"),
48 enabled: cfg!(feature = "debug"),
49 load_start: now,
50 load_duration: time::Duration::from_secs(0),
51 frame_start: now,
52 frame_durations: TimeBuffer::new(200),
53 interact_start: now,
54 interact_duration: time::Duration::from_secs(0),
55 update_start: now,
56 update_durations: TimeBuffer::new(200),
57 draw_start: now,
58 draw_durations: TimeBuffer::new(200),
59 ui_start: now,
60 ui_durations: TimeBuffer::new(200),
61 debug_start: now,
62 debug_durations: TimeBuffer::new(200),
63 text: Vec::new(),
64 draw_rate: 10,
65 frames_until_refresh: 0,
66 }
67 }
68
69 pub(crate) fn loading_started(&mut self) {
70 self.load_start = time::Instant::now();
71 }
72
73 pub(crate) fn loading_finished(&mut self) {
74 self.load_duration = time::Instant::now() - self.load_start;
75 }
76
77 pub fn load_duration(&self) -> time::Duration {
81 self.load_duration
82 }
83
84 pub(crate) fn frame_started(&mut self) {
85 self.frame_start = time::Instant::now();
86 }
87 pub(crate) fn frame_finished(&mut self) {
88 self.frame_durations
89 .push(time::Instant::now() - self.frame_start);
90 }
91
92 pub fn frame_duration(&self) -> time::Duration {
96 self.frame_durations.average()
97 }
98
99 pub(crate) fn interact_started(&mut self) {
100 self.interact_start = time::Instant::now();
101 }
102
103 pub(crate) fn interact_finished(&mut self) {
104 self.interact_duration = time::Instant::now() - self.interact_start;
105 }
106
107 pub fn interact_duration(&self) -> time::Duration {
112 self.interact_duration
113 }
114
115 pub(crate) fn update_started(&mut self) {
116 self.update_start = time::Instant::now();
117 }
118
119 pub(crate) fn update_finished(&mut self) {
120 self.update_durations
121 .push(time::Instant::now() - self.update_start);
122 }
123
124 pub fn update_duration(&self) -> time::Duration {
128 self.update_durations.average()
129 }
130
131 pub(crate) fn draw_started(&mut self) {
132 self.draw_start = time::Instant::now();
133 }
134
135 pub(crate) fn draw_finished(&mut self) {
136 let duration = time::Instant::now() - self.draw_start;
137
138 if duration.subsec_micros() > 0 {
139 self.draw_durations.push(duration);
140 }
141 }
142
143 pub fn draw_duration(&self) -> time::Duration {
147 self.draw_durations.average()
148 }
149
150 pub(crate) fn ui_started(&mut self) {
151 self.ui_start = time::Instant::now();
152 }
153
154 pub(crate) fn ui_finished(&mut self) {
155 self.ui_durations.push(time::Instant::now() - self.ui_start);
156 }
157
158 pub fn ui_duration(&self) -> time::Duration {
162 self.ui_durations.average()
163 }
164
165 pub(crate) fn toggle(&mut self) {
166 self.enabled = !self.enabled;
167 self.frames_until_refresh = 0;
168 }
169
170 pub(crate) fn debug_started(&mut self) {
171 self.debug_start = time::Instant::now();
172 }
173
174 pub(crate) fn debug_finished(&mut self) {
175 self.debug_durations
176 .push(time::Instant::now() - self.debug_start);
177 }
178
179 pub fn debug_duration(&self) -> time::Duration {
183 self.debug_durations.average()
184 }
185
186 pub(crate) fn is_enabled(&self) -> bool {
187 self.enabled
188 }
189
190 pub fn draw(&mut self, frame: &mut graphics::Frame<'_>) {
194 if self.frames_until_refresh <= 0 {
195 self.text.clear();
196 self.refresh_text();
197 self.frames_until_refresh = self.draw_rate.max(1);
198 }
199
200 self.draw_text(frame);
201 self.frames_until_refresh -= 1;
202 }
203
204 const MARGIN: f32 = 20.0;
205 const ROW_HEIGHT: f32 = 25.0;
206 const TITLE_WIDTH: f32 = 150.0;
207 const SHADOW_OFFSET: f32 = 2.0;
208
209 fn refresh_text(&mut self) {
210 let frame_duration = self.frame_durations.average();
211 let frame_micros = (frame_duration.as_secs() as u32 * 1_000_000
212 + frame_duration.subsec_micros())
213 .max(1);
214
215 let fps = (1_000_000.0 / frame_micros as f32).round() as u32;
216 let rows = [
217 ("Load:", self.load_duration, None),
218 ("Interact:", self.interact_duration, None),
219 ("Update:", self.update_duration(), None),
220 ("Draw:", self.draw_duration(), None),
221 ("UI:", self.ui_duration(), None),
222 ("Debug:", self.debug_duration(), None),
223 ("Frame:", frame_duration, Some(fps.to_string() + " fps")),
224 ];
225
226 for (title, duration, extra) in rows.iter() {
227 let formatted_duration = match extra {
228 Some(string) => format_duration(duration) + " (" + string + ")",
229 None => format_duration(duration),
230 };
231
232 self.text.push((String::from(*title), formatted_duration));
233 }
234 }
235
236 fn draw_text(&mut self, frame: &mut graphics::Frame<'_>) {
237 for (row, (key, value)) in self.text.iter().enumerate() {
238 let y = row as f32 * Self::ROW_HEIGHT;
239
240 self.font.add(graphics::Text {
241 content: key,
242 position: graphics::Point::new(
243 Self::MARGIN + Self::SHADOW_OFFSET,
244 Self::MARGIN + y + Self::SHADOW_OFFSET,
245 ),
246 size: 20.0,
247 color: graphics::Color::BLACK,
248 ..graphics::Text::default()
249 });
250
251 self.font.add(graphics::Text {
252 content: key,
253 position: graphics::Point::new(Self::MARGIN, Self::MARGIN + y),
254 size: 20.0,
255 color: graphics::Color::WHITE,
256 ..graphics::Text::default()
257 });
258
259 self.font.add(graphics::Text {
260 content: value,
261 position: graphics::Point::new(
262 Self::MARGIN + Self::TITLE_WIDTH + Self::SHADOW_OFFSET,
263 Self::MARGIN + y + Self::SHADOW_OFFSET,
264 ),
265 size: 20.0,
266 color: graphics::Color::BLACK,
267 ..graphics::Text::default()
268 });
269
270 self.font.add(graphics::Text {
271 content: value,
272 position: graphics::Point::new(
273 Self::MARGIN + Self::TITLE_WIDTH,
274 Self::MARGIN + y,
275 ),
276 size: 20.0,
277 color: graphics::Color::WHITE,
278 ..graphics::Text::default()
279 });
280 }
281
282 self.font.draw(&mut frame.as_target());
283 }
284}
285
286fn format_duration(duration: &time::Duration) -> String {
287 let seconds = duration.as_secs();
288
289 if seconds > 0 {
290 seconds.to_string()
291 + "."
292 + &format!("{:03}", duration.subsec_millis())
293 + " s"
294 } else {
295 let millis = duration.subsec_millis();
296
297 if millis > 0 {
298 millis.to_string()
299 + "."
300 + &format!("{:03}", (duration.subsec_micros() - millis * 1000))
301 + " ms"
302 } else {
303 duration.subsec_micros().to_string() + " µs"
304 }
305 }
306}
307
308impl std::fmt::Debug for Debug {
309 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
310 write!(
311 f,
312 "Debug {{ load: {:?}, interact: {:?}, update: {:?}, draw: {:?}, frame: {:?} }}",
313 self.load_duration(),
314 self.interact_duration(),
315 self.update_duration(),
316 self.draw_duration(),
317 self.frame_duration(),
318 )
319 }
320}
321
322struct TimeBuffer {
323 head: usize,
324 size: usize,
325 contents: Vec<time::Duration>,
326}
327
328impl TimeBuffer {
329 fn new(capacity: usize) -> TimeBuffer {
330 TimeBuffer {
331 head: 0,
332 size: 0,
333 contents: vec![time::Duration::from_secs(0); capacity],
334 }
335 }
336
337 fn push(&mut self, duration: time::Duration) {
338 self.head = (self.head + 1) % self.contents.len();
339 self.contents[self.head] = duration;
340 self.size = (self.size + 1).min(self.contents.len());
341 }
342
343 fn average(&self) -> time::Duration {
344 let sum: time::Duration = if self.size == self.contents.len() {
345 self.contents[..].iter().sum()
346 } else {
347 self.contents[..self.size].iter().sum()
348 };
349
350 sum / self.size.max(1) as u32
351 }
352}