makepad_widgets/
performance_view.rs

1use std::collections::VecDeque;
2
3use makepad_derive_widget::*;
4use crate::{label::*, makepad_draw::*, view::*, widget::*};
5
6live_design! {
7    use link::theme::*;
8    use makepad_draw::shader::std::*;
9    use makepad_widgets::label::LabelBase;
10    use makepad_widgets::view::ViewBase;
11
12    PerformanceLiveGraph = {{PerformanceLiveGraph}} {
13        data_increments: 16.
14        bar_width: 3.
15        graph_label: ""
16        data_y_suffix: ""
17
18        width: 300,
19        height: 65,
20
21        margin: 30,
22        abs_pos: vec2(0, 0)
23
24        show_bg: true,
25        draw_bg: {
26            color: vec4(0.8, 0.8, 0.8, 0.8)
27        }
28
29        view = <ViewBase> {
30            margin: 5,
31            visible: true
32            flow: Right
33            width: Fill,
34            height: Fill,
35
36            graph_label = <LabelBase> {
37                width: Fit
38                align: {x: 0., y: 1.}
39                draw_text: {
40                    text_style: <THEME_FONT_REGULAR>{font_size: 8},
41                    color: #111
42                }
43                text: ""
44            }
45
46            <ViewBase> { width: Fill, height: Fit }
47
48            current_y_entry = <LabelBase> {
49                width: Fit
50                align: {x: 1., y: 1}
51                draw_text: {
52                    text_style: <THEME_FONT_REGULAR>{font_size: 8},
53                    color: #111
54                }
55                text: "-"
56            }
57        }
58    }
59
60    PerformanceView = {{PerformanceView}} {
61        width: Fit,
62        height: Fit,
63
64        abs_pos: vec2(0, 0)
65
66        graph = <PerformanceLiveGraph> {
67            graph_label: "Frame time (max in last second)"
68            data_y_suffix: "ms"
69        }
70
71    }
72}
73
74#[derive(Live, Widget)]
75pub struct PerformanceView {
76    #[deref]
77    view: View,
78    #[rust]
79    next_frame: NextFrame,
80    #[rust]
81    next_refresh_at: f64,
82}
83
84impl LiveHook for PerformanceView {
85    fn after_new_from_doc(&mut self, cx: &mut Cx) {
86        self.next_frame = cx.new_next_frame();
87        self.next_refresh_at = 2.0;
88    }
89}
90
91impl Widget for PerformanceView {
92    fn draw_walk(&mut self, cx: &mut Cx2d, scope:&mut Scope,  walk: Walk) -> DrawStep {
93        let _ = self.view.draw_walk(cx, scope, walk);
94        DrawStep::done()
95    }
96}
97
98impl PerformanceView {
99    pub fn handle_widget(&mut self, cx: &mut Cx, event: &Event) {
100        if let Some(ne) = self.next_frame.is_event(event) {
101            if self.next_refresh_at < ne.time {
102                self.next_refresh_at = ne.time + 0.5;
103
104                if cx.performance_stats.max_frame_times.len() > 0 {
105                    // Data is stored in 100ms buckets, so we take the max of the last 10 buckets
106                    let last_sec_data = cx.performance_stats.max_frame_times.iter().take(10);
107                    let time = (last_sec_data
108                        .max_by(|a, b| a.time_spent.partial_cmp(&b.time_spent).unwrap())
109                        .unwrap()
110                        .time_spent
111                        * 1000.) as i64;
112
113                    self.performance_live_graph(id!(graph))
114                        .add_y_entry(cx, time);
115                }
116            }
117
118            self.next_frame = cx.new_next_frame();
119        }
120    }
121}
122
123#[derive(Live, Widget)]
124pub struct PerformanceLiveGraph {
125    #[redraw] #[deref]
126    view: View,
127    #[redraw] #[live]
128    draw_graph: DrawColor,
129    #[redraw] #[live]
130    draw_bar: DrawColor,
131    #[rust]
132    data: VecDeque<i64>,
133    #[live]
134    bar_width: f64,
135    #[live]
136    data_increments: i64,
137    #[live]
138    data_y_suffix: String,
139    #[live]
140    graph_label: String,
141}
142
143impl LiveHook for PerformanceLiveGraph {
144    fn after_new_from_doc(&mut self, cx: &mut Cx) {
145        self.label(id!(graph_label))
146            .set_text(cx, &format!("{}", self.graph_label));
147    }
148}
149
150impl Widget for PerformanceLiveGraph {
151    fn draw_walk(&mut self, cx: &mut Cx2d, scope:&mut Scope, walk: Walk) -> DrawStep {
152        let _ = self.view.draw_walk(cx, scope, walk);
153        let _ = self.draw_walk(cx, walk);
154        DrawStep::done()
155    }
156}
157
158impl PerformanceLiveGraph {
159    pub fn add_y_entry(&mut self, cx: &mut Cx, y_entry: i64) {
160        let frames_shown_count: usize =
161            (self.walk(cx).width.fixed_or_zero() / self.bar_width) as usize;
162        self.data.push_back(y_entry);
163        while self.data.len() >= frames_shown_count {
164            self.data.pop_front();
165        }
166
167        self.label(id!(current_y_entry))
168            .set_text(cx, &format!("{}{}", y_entry, self.data_y_suffix));
169
170        self.redraw(cx);
171    }
172
173    pub fn draw_walk(&mut self, cx: &mut Cx2d, walk: Walk) {
174        let color_increment_lines = vec4(0.5, 0.5, 0.5, 1.);
175        let color_ok = vec4(0.4, 1.0, 0.4, 1.0);
176        let color_warning = vec4(0.9, 0.9, 0.4, 1.0);
177        let color_fatal = vec4(1.0, 0.2, 0.2, 1.0);
178
179        let graph_width = self.walk(cx).width.fixed_or_zero();
180        let graph_height = self.walk(cx).height.fixed_or_zero();
181        let graph_zero_baseline = graph_height - 20.;
182
183        self.label(id!(graph_label))
184            .set_text(cx, &format!("{}", self.graph_label));
185
186        self.draw_graph.begin(cx, walk, Layout::default());
187
188        // draw increment lines
189        if self.data_increments > 0 {
190            for i in 0..(graph_height as i64 / self.data_increments) {
191                self.draw_bar.color = color_increment_lines;
192                let rect = Rect {
193                    pos: DVec2 {
194                        x: 0.,
195                        // Negative so it draws to the positive side of the y axis.
196                        y: -(i * self.data_increments - graph_zero_baseline as i64) as f64,
197                    },
198                    size: DVec2 {
199                        x: graph_width,
200                        y: 1.,
201                    },
202                };
203                self.draw_bar.draw_rel(cx, rect);
204            }
205        }
206
207        // graphing data
208        for (i, &y_entry) in self.data.iter().enumerate() {
209            self.draw_bar.color = match y_entry {
210                t if t < self.data_increments => color_ok,
211                t if t >= self.data_increments && t < self.data_increments * 2 => color_warning,
212                _ => color_fatal,
213            };
214
215            let rect = Rect {
216                pos: DVec2 {
217                    x: i as f64 * self.bar_width,
218                    y: graph_zero_baseline,
219                },
220                size: DVec2 {
221                    x: self.bar_width,
222                    // Negative so it draws to the positive side of the y axis.
223                    y: -(y_entry as f64) - 1.,
224                },
225            };
226
227            self.draw_bar.draw_rel(cx, rect);
228        }
229
230        self.draw_graph.end(cx);
231    }
232}
233
234impl PerformanceLiveGraphRef {
235    pub fn add_y_entry(&self, cx: &mut Cx, y_entry: i64) {
236        if let Some(mut inner) = self.borrow_mut() {
237            inner.add_y_entry(cx, y_entry);
238        }
239    }
240}