ferroui_base/rendering/
renderer_diagnostics.rs1use std::cell::Cell;
2use std::rc::{Rc, Weak};
3
4use super::{LayoutPassTiming, RendererDebugOverlays};
5use crate::reactive::{Disposable, IDisposable};
6use crate::utilities::HandlerList;
7
8pub 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 pub const DEBUG_OVERLAYS_PROPERTY_NAME: &'static str = "DebugOverlays";
20
21 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 pub fn debug_overlays(&self) -> RendererDebugOverlays {
36 self.debug_overlays.get()
37 }
38
39 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 pub fn last_layout_pass_timing(&self) -> LayoutPassTiming {
49 self.last_layout_pass_timing.get()
50 }
51
52 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 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 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}