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 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 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 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}