Skip to main content

cranpose_app_shell/
shell_debug.rs

1use super::*;
2
3impl<R> AppShell<R>
4where
5    R: Renderer,
6    R::Error: Debug,
7{
8    pub fn debug_info_report(&mut self) -> String {
9        let mut report = String::new();
10        writeln!(report, "=== DEBUG: CURRENT SCREEN STATE ===").ok();
11        if let Some(layout_tree) = self.layout_tree() {
12            let renderer = HeadlessRenderer::new();
13            let render_scene = renderer.render(layout_tree);
14            writeln!(report, "{}", format_layout_tree(layout_tree)).ok();
15            writeln!(report, "{}", format_render_scene(&render_scene)).ok();
16            writeln!(
17                report,
18                "{}",
19                format_screen_summary(layout_tree, &render_scene)
20            )
21            .ok();
22        } else {
23            writeln!(report, "No layout available").ok();
24        }
25        report
26    }
27
28    pub fn log_debug_info(&mut self) -> String {
29        let report = self.debug_info_report();
30        log::info!(target: "cranpose::debug::screen", "\n{report}");
31        report
32    }
33
34    /// Get the current layout tree (for robot/testing)
35    pub fn layout_tree(&mut self) -> Option<&LayoutTree> {
36        self.layout_tree.as_ref()
37    }
38
39    /// Get the current semantics tree (for robot/testing)
40    pub fn semantics_tree(&self) -> Option<&SemanticsTree> {
41        self.semantics_tree.as_ref()
42    }
43
44    pub fn root_layout_size(&mut self) -> Option<(f32, f32)> {
45        self.layout_tree().map(|tree| {
46            let root = tree.root();
47            (root.rect.width, root.rect.height)
48        })
49    }
50
51    pub fn node_layout_bounds(&mut self, target: NodeId) -> Option<(f32, f32, f32, f32)> {
52        self.layout_tree()
53            .and_then(|tree| find_layout_box(tree.root(), target))
54            .map(layout_box_bounds)
55    }
56
57    #[cfg(any(test, feature = "test-support"))]
58    #[doc(hidden)]
59    pub fn debug_runtime_leak_stats(&mut self) -> RuntimeLeakDebugStats {
60        let runtime = self.composition.runtime_handle();
61        let (applier_stats, live_node_heap_bytes, recycled_node_heap_bytes) = {
62            let applier = self.composition.applier_mut();
63            (
64                applier.debug_stats(),
65                applier.debug_live_node_heap_bytes(),
66                applier.debug_recycled_node_heap_bytes(),
67            )
68        };
69        RuntimeLeakDebugStats {
70            applier_stats,
71            live_node_heap_bytes,
72            recycled_node_heap_bytes,
73            slot_table_heap_bytes: self.composition.slot_table_heap_bytes(),
74            pass_stats: self.composition.debug_last_pass_stats(),
75            slot_stats: self.composition.debug_slot_table_stats(),
76            observer_stats: self.composition.debug_observer_stats(),
77            runtime_stats: runtime.debug_stats(),
78            state_arena_stats: runtime.state_arena_debug_stats(),
79            recompose_scope_stats: debug_recompose_scope_registry_stats(),
80            snapshot_v2_stats: debug_snapshot_v2_stats(),
81            snapshot_pinning_stats: debug_snapshot_pinning_stats(),
82        }
83    }
84
85    #[cfg(any(test, feature = "test-support"))]
86    #[doc(hidden)]
87    pub fn debug_slot_table_groups(&self) -> Vec<(usize, Key, Option<usize>, usize)> {
88        self.composition.debug_dump_slot_table_groups()
89    }
90
91    #[cfg(any(test, feature = "test-support"))]
92    #[doc(hidden)]
93    pub fn debug_all_slots(&self) -> Vec<(usize, String)> {
94        self.composition.debug_dump_all_slots()
95    }
96
97    #[cfg(any(test, feature = "test-support"))]
98    #[doc(hidden)]
99    pub fn runtime_handle(&self) -> cranpose_core::RuntimeHandle {
100        self.composition.runtime_handle()
101    }
102
103    #[cfg(any(test, feature = "test-support"))]
104    #[doc(hidden)]
105    pub fn debug_live_subcompose_scope_ids(&mut self) -> Vec<(NodeId, Vec<(u64, Vec<usize>)>)> {
106        fn collect_node_ids(layout: &LayoutBox, out: &mut Vec<NodeId>) {
107            out.push(layout.node_id);
108            for child in &layout.children {
109                collect_node_ids(child, out);
110            }
111        }
112
113        let mut node_ids = Vec::new();
114        if let Some(tree) = self.layout_tree() {
115            collect_node_ids(tree.root(), &mut node_ids);
116        }
117
118        let mut applier = self.composition.applier_mut();
119        let mut result = Vec::new();
120        for node_id in node_ids {
121            if let Ok(scope_ids) = applier.with_node::<SubcomposeLayoutNode, _>(node_id, |node| {
122                node.debug_scope_ids_by_slot()
123            }) {
124                result.push((node_id, scope_ids));
125            }
126        }
127        result
128    }
129
130    #[cfg(any(test, feature = "test-support"))]
131    #[doc(hidden)]
132    pub fn debug_subcompose_slot_table(
133        &mut self,
134        node_id: NodeId,
135        slot_id: u64,
136    ) -> Option<Vec<(usize, String)>> {
137        let mut applier = self.composition.applier_mut();
138        applier
139            .with_node::<SubcomposeLayoutNode, _>(node_id, |node| {
140                node.debug_slot_table_for_slot(SlotId::new(slot_id))
141            })
142            .ok()
143            .flatten()
144    }
145
146    #[cfg(any(test, feature = "test-support"))]
147    #[doc(hidden)]
148    pub fn debug_subcompose_slot_groups(
149        &mut self,
150        node_id: NodeId,
151        slot_id: u64,
152    ) -> Option<Vec<(usize, Key, Option<usize>, usize)>> {
153        let mut applier = self.composition.applier_mut();
154        applier
155            .with_node::<SubcomposeLayoutNode, _>(node_id, |node| {
156                node.debug_slot_table_groups_for_slot(SlotId::new(slot_id))
157            })
158            .ok()
159            .flatten()
160    }
161}
162
163fn find_layout_box(layout_box: &LayoutBox, target: NodeId) -> Option<&LayoutBox> {
164    if layout_box.node_id == target {
165        return Some(layout_box);
166    }
167
168    layout_box
169        .children
170        .iter()
171        .find_map(|child| find_layout_box(child, target))
172}
173
174fn layout_box_bounds(layout_box: &LayoutBox) -> (f32, f32, f32, f32) {
175    (
176        layout_box.rect.x,
177        layout_box.rect.y,
178        layout_box.rect.width,
179        layout_box.rect.height,
180    )
181}