Skip to main content

ferroui_base/rendering/
renderer_diagnostics.rs

1use std::cell::Cell;
2use std::rc::{Rc, Weak};
3
4use super::{LayoutPassTiming, RendererDebugOverlays};
5use crate::reactive::{Disposable, IDisposable};
6use crate::utilities::HandlerList;
7
8/// Manages configurable diagnostics that can be displayed by a renderer.
9pub struct RendererDiagnostics {
10    weak_self: Weak<RendererDiagnostics>,
11    debug_overlays: Cell<RendererDebugOverlays>,
12    last_layout_pass_timing: Cell<LayoutPassTiming>,
13    property_changed: HandlerList<dyn Fn(&RendererDiagnostics, &str)>,
14}
15
16impl RendererDiagnostics {
17    /// The name [`property_changed`](Self::property_changed) reports for
18    /// [`debug_overlays`](Self::debug_overlays).
19    pub const DEBUG_OVERLAYS_PROPERTY_NAME: &'static str = "DebugOverlays";
20
21    /// The name [`property_changed`](Self::property_changed) reports for
22    /// [`last_layout_pass_timing`](Self::last_layout_pass_timing).
23    pub const LAST_LAYOUT_PASS_TIMING_PROPERTY_NAME: &'static str = "LastLayoutPassTiming";
24
25    pub fn new() -> Rc<RendererDiagnostics> {
26        Rc::new_cyclic(|weak_self| RendererDiagnostics {
27            weak_self: weak_self.clone(),
28            debug_overlays: Cell::new(RendererDebugOverlays::NONE),
29            last_layout_pass_timing: Cell::new(LayoutPassTiming::default()),
30            property_changed: HandlerList::new(),
31        })
32    }
33
34    /// Gets which debug overlays are displayed by the renderer.
35    pub fn debug_overlays(&self) -> RendererDebugOverlays {
36        self.debug_overlays.get()
37    }
38
39    /// Sets which debug overlays are displayed by the renderer.
40    pub fn set_debug_overlays(&self, value: RendererDebugOverlays) {
41        if self.debug_overlays.get() != value {
42            self.debug_overlays.set(value);
43            self.on_property_changed(Self::DEBUG_OVERLAYS_PROPERTY_NAME);
44        }
45    }
46
47    /// Gets the last layout pass timing that the renderer may display.
48    pub fn last_layout_pass_timing(&self) -> LayoutPassTiming {
49        self.last_layout_pass_timing.get()
50    }
51
52    /// Sets the last layout pass timing that the renderer may display.
53    pub fn set_last_layout_pass_timing(&self, value: LayoutPassTiming) {
54        if self.last_layout_pass_timing.get() != value {
55            self.last_layout_pass_timing.set(value);
56            self.on_property_changed(Self::LAST_LAYOUT_PASS_TIMING_PROPERTY_NAME);
57        }
58    }
59
60    /// Occurs when a property value changes. The handler receives the name
61    /// of the property.
62    pub fn property_changed(&self, handler: impl Fn(&RendererDiagnostics, &str) + 'static) -> Rc<dyn IDisposable> {
63        let token = self.property_changed.add(Rc::new(handler));
64        let this = self.weak_self.clone();
65        Disposable::create(move || {
66            if let Some(this) = this.upgrade() {
67                this.property_changed.remove(token);
68            }
69        })
70    }
71
72    /// Called when a property changes on the object.
73    fn on_property_changed(&self, property_name: &str) {
74        for (_, handler) in self.property_changed.snapshot().iter() {
75            handler(self, property_name);
76        }
77    }
78}
79
80#[cfg(test)]
81mod tests {
82    use super::*;
83    use std::cell::RefCell;
84    use std::time::Duration;
85
86    #[test]
87    fn raises_property_changed_only_when_the_value_changes() {
88        let diagnostics = RendererDiagnostics::new();
89        let changes = Rc::new(RefCell::new(Vec::new()));
90        let c = changes.clone();
91        let subscription = diagnostics.property_changed(move |sender, name| {
92            c.borrow_mut().push((name.to_string(), sender.debug_overlays(), sender.last_layout_pass_timing()));
93        });
94
95        assert_eq!(diagnostics.debug_overlays(), RendererDebugOverlays::NONE);
96        diagnostics.set_debug_overlays(RendererDebugOverlays::NONE);
97        assert!(changes.borrow().is_empty());
98
99        let overlays = RendererDebugOverlays::FPS | RendererDebugOverlays::DIRTY_RECTS;
100        diagnostics.set_debug_overlays(overlays);
101        diagnostics.set_debug_overlays(overlays);
102        let timing = LayoutPassTiming::new(3, Duration::from_millis(5));
103        diagnostics.set_last_layout_pass_timing(timing);
104        diagnostics.set_last_layout_pass_timing(timing);
105
106        assert_eq!(
107            *changes.borrow(),
108            [
109                ("DebugOverlays".to_string(), overlays, LayoutPassTiming::default()),
110                ("LastLayoutPassTiming".to_string(), overlays, timing),
111            ]
112        );
113
114        subscription.dispose();
115        diagnostics.set_debug_overlays(RendererDebugOverlays::NONE);
116        assert_eq!(changes.borrow().len(), 2);
117    }
118}