coffee/debug/
basic.rs

1use std::time;
2
3use crate::graphics;
4
5/// A bunch of performance information about your game. It can be drawn!
6///
7/// ![Debug information][debug]
8///
9/// This is the default debug information that will be shown when the
10/// [`Game::DEBUG_KEY`] is pressed.
11///
12/// Overriding [`Game::debug`] gives you access to this struct, allowing you to
13/// implement your own debug view.
14///
15/// [`Game`]: trait.Game.html
16/// [debug]: https://github.com/hecrj/coffee/blob/50c9a857e476266d8bd37f705266bd66b77c0f2d/images/debug.png?raw=true
17/// [`Game::DEBUG_KEY`]: trait.Game.html#associatedconstant.DEBUG_KEY
18/// [`Game::debug`]: trait.Game.html#method.debug
19pub 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    /// Returns the time spent loading your [`Game`].
78    ///
79    /// [`Game`]: trait.Game.html
80    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    /// Returns the average time spent per frame.
93    ///
94    /// It includes time spent on V-Sync, if enabled.
95    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    /// Returns the average time spent processing events and running
108    /// [`Game::interact`].
109    ///
110    /// [`Game::interact`]: trait.Game.html#method.interact
111    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    /// Returns the average time spent running [`Game::update`].
125    ///
126    /// [`Game::update`]: trait.Game.html#tymethod.update
127    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    /// Returns the average time spent running [`Game::draw`].
144    ///
145    /// [`Game::draw`]: trait.Game.html#tymethod.draw
146    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    /// Returns the average time spent rendering the [`UserInterface`].
159    ///
160    /// [`UserInterface`]: ui/trait.UserInterface.html
161    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    /// Returns the average time spent running [`Game::debug`].
180    ///
181    /// [`Game::debug`]: trait.Game.html#tymethod.debug
182    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    /// Draws the [`Debug`] information.
191    ///
192    /// [`Debug`]: struct.Debug.html
193    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}