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 if self.layout_tree.is_none() {
37 let root = self.composition.root()?;
38 let mut applier = self.composition.applier_mut();
39 match cranpose_ui::build_layout_tree_from_applier(&mut applier, root) {
40 Ok(layout_tree) => {
41 self.layout_tree = layout_tree;
42 }
43 Err(err) => {
44 log::debug!("failed to build layout snapshot: {err}");
45 return None;
46 }
47 }
48 }
49 self.layout_tree.as_ref()
50 }
51
52 pub fn semantics_tree(&mut self) -> Option<&SemanticsTree> {
54 if !self.semantics_enabled {
55 return None;
56 }
57 let root = self.composition.root()?;
58 let semantics_dirty = {
59 let mut applier = self.composition.applier_mut();
60 cranpose_ui::tree_needs_semantics(&mut *applier, root).unwrap_or_else(|err| {
61 log::debug!("failed to check semantics dirty status for root #{root}: {err}");
62 true
63 })
64 };
65 if self.semantics_tree.is_none() || semantics_dirty {
66 let mut applier = self.composition.applier_mut();
67 match cranpose_ui::build_semantics_tree_from_applier(&mut applier, root) {
68 Ok(semantics_tree) => {
69 self.semantics_tree = semantics_tree;
70 }
71 Err(err) => {
72 log::debug!("failed to build semantics snapshot: {err}");
73 return None;
74 }
75 }
76 }
77 self.semantics_tree.as_ref()
78 }
79
80 pub fn root_layout_size(&mut self) -> Option<(f32, f32)> {
81 self.layout_tree().map(|tree| {
82 let root = tree.root();
83 (root.rect.width, root.rect.height)
84 })
85 }
86
87 pub fn node_layout_bounds(&mut self, target: NodeId) -> Option<(f32, f32, f32, f32)> {
88 self.layout_tree()
89 .and_then(|tree| find_layout_box(tree.root(), target))
90 .map(layout_box_bounds)
91 }
92
93 #[cfg(any(test, feature = "test-support"))]
94 #[doc(hidden)]
95 pub fn debug_runtime_leak_stats(&mut self) -> RuntimeLeakDebugStats {
96 let runtime = self.composition.runtime_handle();
97 let (applier_stats, live_node_heap_bytes, recycled_node_heap_bytes) = {
98 let applier = self.composition.applier_mut();
99 (
100 applier.debug_stats(),
101 applier.debug_live_node_heap_bytes(),
102 applier.debug_recycled_node_heap_bytes(),
103 )
104 };
105 RuntimeLeakDebugStats {
106 applier_stats,
107 live_node_heap_bytes,
108 recycled_node_heap_bytes,
109 slot_table_heap_bytes: self.composition.slot_table_heap_bytes(),
110 pass_stats: self.composition.debug_last_pass_stats(),
111 slot_stats: self.composition.debug_slot_table_stats(),
112 observer_stats: self.composition.debug_observer_stats(),
113 runtime_stats: runtime.debug_stats(),
114 state_arena_stats: runtime.state_arena_debug_stats(),
115 recompose_scope_stats: debug_recompose_scope_registry_stats(),
116 snapshot_v2_stats: debug_snapshot_v2_stats(),
117 snapshot_pinning_stats: debug_snapshot_pinning_stats(),
118 }
119 }
120
121 #[cfg(any(test, feature = "test-support"))]
122 #[doc(hidden)]
123 pub fn debug_slot_table_groups(&self) -> Vec<(usize, Key, Option<usize>, usize)> {
124 self.composition.debug_dump_slot_table_groups()
125 }
126
127 #[cfg(any(test, feature = "test-support"))]
128 #[doc(hidden)]
129 pub fn debug_slot_entries(&self) -> Vec<cranpose_core::SlotDebugEntry> {
130 self.composition.debug_dump_slot_entries()
131 }
132
133 #[cfg(any(test, feature = "test-support"))]
134 #[doc(hidden)]
135 pub fn runtime_handle(&self) -> cranpose_core::RuntimeHandle {
136 self.composition.runtime_handle()
137 }
138
139 #[cfg(any(test, feature = "test-support"))]
140 #[doc(hidden)]
141 pub fn debug_live_subcompose_scope_ids(&mut self) -> Vec<(NodeId, Vec<(u64, Vec<usize>)>)> {
142 fn collect_node_ids(layout: &LayoutBox, out: &mut Vec<NodeId>) {
143 out.push(layout.node_id);
144 for child in &layout.children {
145 collect_node_ids(child, out);
146 }
147 }
148
149 let mut node_ids = Vec::new();
150 if let Some(tree) = self.layout_tree() {
151 collect_node_ids(tree.root(), &mut node_ids);
152 }
153
154 let mut applier = self.composition.applier_mut();
155 let mut result = Vec::new();
156 for node_id in node_ids {
157 if let Ok(scope_ids) = applier.with_node::<SubcomposeLayoutNode, _>(node_id, |node| {
158 node.debug_scope_ids_by_slot()
159 }) {
160 result.push((node_id, scope_ids));
161 }
162 }
163 result
164 }
165
166 #[cfg(any(test, feature = "test-support"))]
167 #[doc(hidden)]
168 pub fn debug_subcompose_slot_table(
169 &mut self,
170 node_id: NodeId,
171 slot_id: u64,
172 ) -> Option<Vec<cranpose_core::SlotDebugEntry>> {
173 let mut applier = self.composition.applier_mut();
174 applier
175 .with_node::<SubcomposeLayoutNode, _>(node_id, |node| {
176 node.debug_slot_table_for_slot(SlotId::new(slot_id))
177 })
178 .ok()
179 .flatten()
180 }
181
182 #[cfg(any(test, feature = "test-support"))]
183 #[doc(hidden)]
184 pub fn debug_subcompose_slot_groups(
185 &mut self,
186 node_id: NodeId,
187 slot_id: u64,
188 ) -> Option<Vec<(usize, Key, Option<usize>, usize)>> {
189 let mut applier = self.composition.applier_mut();
190 applier
191 .with_node::<SubcomposeLayoutNode, _>(node_id, |node| {
192 node.debug_slot_table_groups_for_slot(SlotId::new(slot_id))
193 })
194 .ok()
195 .flatten()
196 }
197}
198
199fn find_layout_box(layout_box: &LayoutBox, target: NodeId) -> Option<&LayoutBox> {
200 if layout_box.node_id == target {
201 return Some(layout_box);
202 }
203
204 layout_box
205 .children
206 .iter()
207 .find_map(|child| find_layout_box(child, target))
208}
209
210fn layout_box_bounds(layout_box: &LayoutBox) -> (f32, f32, f32, f32) {
211 (
212 layout_box.rect.x,
213 layout_box.rect.y,
214 layout_box.rect.width,
215 layout_box.rect.height,
216 )
217}