cranpose_app_shell/
shell_debug.rs1use 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 pub fn layout_tree(&mut self) -> Option<&LayoutTree> {
36 self.layout_tree.as_ref()
37 }
38
39 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}