1//! `RenderLog` — a diagnostic panel that records which components re-render
2//! and when, letting you observe GPUI's caching behaviour in real time.
3
4use std::time::Instant;
5
6use gpui::{App, Context, Entity, IntoViewElement, Window, div, hsla, prelude::*, px};
7
8// ---------------------------------------------------------------------------
9// RenderLog entity
10// ---------------------------------------------------------------------------
11
12pub struct RenderLog {
13 entries: Vec<RenderLogEntry>,
14 start_time: Instant,
15}
16
17struct RenderLogEntry {
18 component: &'static str,
19 timestamp: Instant,
20}
21
22impl RenderLog {
23 pub fn new(_cx: &mut Context<Self>) -> Self {
24 Self {
25 entries: Vec::new(),
26 start_time: Instant::now(),
27 }
28 }
29
30 /// Record that `component` rendered. Does **not** call `cx.notify()` — the
31 /// panel updates passively the next time its parent re-renders, which avoids
32 /// an infinite invalidation loop.
33 pub fn log(&mut self, component: &'static str) {
34 self.entries.push(RenderLogEntry {
35 component,
36 timestamp: Instant::now(),
37 });
38 if self.entries.len() > 50 {
39 self.entries.drain(0..self.entries.len() - 50);
40 }
41 }
42}
43
44// ---------------------------------------------------------------------------
45// RenderLogPanel — stateless ComponentView that displays the log
46// ---------------------------------------------------------------------------
47
48#[derive(Hash, IntoViewElement)]
49pub struct RenderLogPanel {
50 log: Entity<RenderLog>,
51}
52
53impl RenderLogPanel {
54 pub fn new(log: Entity<RenderLog>) -> Self {
55 Self { log }
56 }
57}
58
59impl gpui::ComponentView for RenderLogPanel {
60 fn render(self, _window: &mut Window, cx: &mut App) -> impl IntoElement {
61 let log = self.log.read(cx);
62 let start = log.start_time;
63
64 div()
65 .flex()
66 .flex_col()
67 .gap(px(1.))
68 .p(px(8.))
69 .bg(hsla(0., 0., 0.12, 1.))
70 .rounded(px(4.))
71 .max_h(px(180.))
72 .overflow_hidden()
73 .child(
74 div()
75 .text_xs()
76 .text_color(hsla(0., 0., 0.55, 1.))
77 .mb(px(4.))
78 .child("Render log (most recent 20)"),
79 )
80 .children(
81 log.entries
82 .iter()
83 .rev()
84 .take(20)
85 .collect::<Vec<_>>()
86 .into_iter()
87 .rev()
88 .map(|entry| {
89 let elapsed = entry.timestamp.duration_since(start);
90 let secs = elapsed.as_secs_f64();
91 div()
92 .text_xs()
93 .text_color(hsla(120. / 360., 0.7, 0.65, 1.))
94 .child(format!("{:<20} +{:.1}s", entry.component, secs))
95 }),
96 )
97 }
98}