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 app_context = std::rc::Rc::clone(&self.app_context);
10        app_context.enter(|| {
11            let mut report = String::new();
12            writeln!(report, "=== DEBUG: CURRENT SCREEN STATE ===").ok();
13            if let Some(layout_tree) = self.layout_tree_in_context() {
14                let renderer = HeadlessRenderer::new();
15                let render_scene = renderer.render(layout_tree);
16                writeln!(report, "{}", format_layout_tree(layout_tree)).ok();
17                writeln!(report, "{}", format_render_scene(&render_scene)).ok();
18                writeln!(
19                    report,
20                    "{}",
21                    format_screen_summary(layout_tree, &render_scene)
22                )
23                .ok();
24            } else {
25                writeln!(report, "No layout available").ok();
26            }
27            report
28        })
29    }
30
31    pub fn log_debug_info(&mut self) -> String {
32        let report = self.debug_info_report();
33        log::info!(target: "cranpose::debug::screen", "\n{report}");
34        report
35    }
36
37    /// Get the current layout tree (for robot/testing)
38    pub fn layout_tree(&mut self) -> Option<&LayoutTree> {
39        let app_context = std::rc::Rc::clone(&self.app_context);
40        app_context.enter(|| self.layout_tree_in_context())
41    }
42
43    #[doc(hidden)]
44    pub fn with_layout_tree<T>(&mut self, block: impl FnOnce(Option<&LayoutTree>) -> T) -> T {
45        let app_context = std::rc::Rc::clone(&self.app_context);
46        app_context.enter(|| {
47            let layout_tree = self.layout_tree_in_context();
48            block(layout_tree)
49        })
50    }
51
52    fn layout_tree_in_context(&mut self) -> Option<&LayoutTree> {
53        if self.layout_tree.is_none() {
54            let root = self.composition.root()?;
55            let mut applier = self.composition.applier_mut();
56            match cranpose_ui::build_layout_tree_from_applier(&mut applier, root) {
57                Ok(layout_tree) => {
58                    self.layout_tree = layout_tree;
59                }
60                Err(err) => {
61                    log::debug!("failed to build layout snapshot: {err}");
62                    return None;
63                }
64            }
65        }
66        self.layout_tree.as_ref()
67    }
68
69    /// Get the current semantics tree (for robot/testing)
70    pub fn semantics_tree(&mut self) -> Option<&SemanticsTree> {
71        let app_context = std::rc::Rc::clone(&self.app_context);
72        app_context.enter(|| self.semantics_tree_in_context())
73    }
74
75    fn semantics_tree_in_context(&mut self) -> Option<&SemanticsTree> {
76        if !self.semantics_enabled {
77            return None;
78        }
79        let root = self.composition.root()?;
80        let semantics_dirty = {
81            let mut applier = self.composition.applier_mut();
82            cranpose_ui::tree_needs_semantics(&mut *applier, root).unwrap_or_else(|err| {
83                log::debug!("failed to check semantics dirty status for root #{root}: {err}");
84                true
85            })
86        };
87        if self.semantics_tree.is_none() || semantics_dirty {
88            let mut applier = self.composition.applier_mut();
89            match cranpose_ui::build_semantics_tree_from_applier(&mut applier, root) {
90                Ok(semantics_tree) => {
91                    self.semantics_tree = semantics_tree;
92                }
93                Err(err) => {
94                    log::debug!("failed to build semantics snapshot: {err}");
95                    return None;
96                }
97            }
98        }
99        self.semantics_tree.as_ref()
100    }
101
102    pub fn root_layout_size(&mut self) -> Option<(f32, f32)> {
103        self.layout_tree().map(|tree| {
104            let root = tree.root();
105            (root.rect.width, root.rect.height)
106        })
107    }
108
109    pub fn node_layout_bounds(&mut self, target: NodeId) -> Option<(f32, f32, f32, f32)> {
110        self.layout_tree()
111            .and_then(|tree| find_layout_box(tree.root(), target))
112            .map(layout_box_bounds)
113    }
114
115    #[cfg(any(test, feature = "test-support"))]
116    #[doc(hidden)]
117    pub fn debug_runtime_leak_stats(&mut self) -> RuntimeLeakDebugStats {
118        let runtime = self.composition.runtime_handle();
119        let (applier_stats, live_node_heap_bytes, recycled_node_heap_bytes) = {
120            let applier = self.composition.applier_mut();
121            (
122                applier.debug_stats(),
123                applier.debug_live_node_heap_bytes(),
124                applier.debug_recycled_node_heap_bytes(),
125            )
126        };
127        RuntimeLeakDebugStats {
128            applier_stats,
129            live_node_heap_bytes,
130            recycled_node_heap_bytes,
131            slot_table_heap_bytes: self.composition.slot_table_heap_bytes(),
132            pass_stats: self.composition.debug_last_pass_stats(),
133            slot_stats: self.composition.debug_slot_table_stats(),
134            observer_stats: self.composition.debug_observer_stats(),
135            runtime_stats: runtime.debug_stats(),
136            state_arena_stats: runtime.state_arena_debug_stats(),
137            recompose_scope_stats: debug_recompose_scope_registry_stats(),
138            snapshot_v2_stats: debug_snapshot_v2_stats(),
139            snapshot_pinning_stats: debug_snapshot_pinning_stats(),
140        }
141    }
142
143    #[cfg(any(test, feature = "test-support"))]
144    #[doc(hidden)]
145    pub fn debug_slot_table_groups(&self) -> Vec<(usize, Key, Option<usize>, usize)> {
146        self.composition.debug_dump_slot_table_groups()
147    }
148
149    #[cfg(any(test, feature = "test-support"))]
150    #[doc(hidden)]
151    pub fn debug_slot_entries(&self) -> Vec<cranpose_core::SlotDebugEntry> {
152        self.composition.debug_dump_slot_entries()
153    }
154
155    #[cfg(any(test, feature = "test-support"))]
156    #[doc(hidden)]
157    pub fn runtime_handle(&self) -> cranpose_core::RuntimeHandle {
158        self.composition.runtime_handle()
159    }
160
161    #[cfg(any(test, feature = "test-support"))]
162    #[doc(hidden)]
163    pub fn debug_live_subcompose_scope_ids(&mut self) -> Vec<(NodeId, Vec<(u64, Vec<usize>)>)> {
164        fn collect_node_ids(layout: &LayoutBox, out: &mut Vec<NodeId>) {
165            out.push(layout.node_id);
166            for child in &layout.children {
167                collect_node_ids(child, out);
168            }
169        }
170
171        let mut node_ids = Vec::new();
172        if let Some(tree) = self.layout_tree() {
173            collect_node_ids(tree.root(), &mut node_ids);
174        }
175
176        let mut applier = self.composition.applier_mut();
177        let mut result = Vec::new();
178        for node_id in node_ids {
179            if let Ok(scope_ids) = applier.with_node::<SubcomposeLayoutNode, _>(node_id, |node| {
180                node.debug_scope_ids_by_slot()
181            }) {
182                result.push((node_id, scope_ids));
183            }
184        }
185        result
186    }
187
188    #[cfg(any(test, feature = "test-support"))]
189    #[doc(hidden)]
190    pub fn debug_subcompose_slot_table(
191        &mut self,
192        node_id: NodeId,
193        slot_id: u64,
194    ) -> Option<Vec<cranpose_core::SlotDebugEntry>> {
195        let mut applier = self.composition.applier_mut();
196        applier
197            .with_node::<SubcomposeLayoutNode, _>(node_id, |node| {
198                node.debug_slot_table_for_slot(SlotId::new(slot_id))
199            })
200            .ok()
201            .flatten()
202    }
203
204    #[cfg(any(test, feature = "test-support"))]
205    #[doc(hidden)]
206    pub fn debug_subcompose_slot_groups(
207        &mut self,
208        node_id: NodeId,
209        slot_id: u64,
210    ) -> Option<Vec<(usize, Key, Option<usize>, usize)>> {
211        let mut applier = self.composition.applier_mut();
212        applier
213            .with_node::<SubcomposeLayoutNode, _>(node_id, |node| {
214                node.debug_slot_table_groups_for_slot(SlotId::new(slot_id))
215            })
216            .ok()
217            .flatten()
218    }
219}
220
221fn find_layout_box(layout_box: &LayoutBox, target: NodeId) -> Option<&LayoutBox> {
222    if layout_box.node_id == target {
223        return Some(layout_box);
224    }
225
226    layout_box
227        .children
228        .iter()
229        .find_map(|child| find_layout_box(child, target))
230}
231
232fn layout_box_bounds(layout_box: &LayoutBox) -> (f32, f32, f32, f32) {
233    (
234        layout_box.rect.x,
235        layout_box.rect.y,
236        layout_box.rect.width,
237        layout_box.rect.height,
238    )
239}