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_active(&self) -> bool {
73 self.semantics_enabled
74 }
75
76 pub fn semantics_snapshot_revision(&mut self) -> u64 {
83 if self.semantics_enabled {
84 let app_context = std::rc::Rc::clone(&self.app_context);
85 let semantics_dirty = app_context.enter(|| {
86 let Some(root) = self.composition.root() else {
87 return false;
88 };
89 let mut applier = self.composition.applier_mut();
90 cranpose_ui::tree_needs_semantics(&mut *applier, root).unwrap_or(true)
91 });
92 if semantics_dirty {
93 self.semantics_snapshot_revision = self.semantics_snapshot_revision.wrapping_add(1);
94 }
95 }
96 self.semantics_snapshot_revision
97 }
98
99 pub fn semantics_tree(&mut self) -> Option<&SemanticsTree> {
101 let app_context = std::rc::Rc::clone(&self.app_context);
102 app_context.enter(|| self.semantics_tree_in_context())
103 }
104
105 fn semantics_tree_in_context(&mut self) -> Option<&SemanticsTree> {
106 if !self.semantics_enabled {
107 return None;
108 }
109 let root = self.composition.root()?;
110 let semantics_dirty = {
111 let mut applier = self.composition.applier_mut();
112 cranpose_ui::tree_needs_semantics(&mut *applier, root).unwrap_or_else(|err| {
113 log::debug!("failed to check semantics dirty status for root #{root}: {err}");
114 true
115 })
116 };
117 if self.semantics_tree.is_none() || semantics_dirty {
118 let mut applier = self.composition.applier_mut();
119 match cranpose_ui::build_semantics_tree_from_applier(&mut applier, root) {
120 Ok(semantics_tree) => {
121 self.semantics_tree = semantics_tree;
122 }
123 Err(err) => {
124 log::debug!("failed to build semantics snapshot: {err}");
125 return None;
126 }
127 }
128 }
129 self.semantics_tree.as_ref()
130 }
131
132 pub fn root_layout_size(&mut self) -> Option<(f32, f32)> {
133 self.layout_tree().map(|tree| {
134 let root = tree.root();
135 (root.rect.width, root.rect.height)
136 })
137 }
138
139 pub fn node_layout_bounds(&mut self, target: NodeId) -> Option<(f32, f32, f32, f32)> {
140 self.layout_tree()
141 .and_then(|tree| find_layout_box(tree.root(), target))
142 .map(layout_box_bounds)
143 }
144
145 #[cfg(any(test, feature = "test-support"))]
146 #[doc(hidden)]
147 pub fn debug_runtime_leak_stats(&mut self) -> RuntimeLeakDebugStats {
148 let runtime = self.composition.runtime_handle();
149 let (applier_stats, live_node_heap_bytes, recycled_node_heap_bytes) = {
150 let applier = self.composition.applier_mut();
151 (
152 applier.debug_stats(),
153 applier.debug_live_node_heap_bytes(),
154 applier.debug_recycled_node_heap_bytes(),
155 )
156 };
157 RuntimeLeakDebugStats {
158 applier_stats,
159 live_node_heap_bytes,
160 recycled_node_heap_bytes,
161 slot_table_heap_bytes: self.composition.slot_table_heap_bytes(),
162 pass_stats: self.composition.debug_last_pass_stats(),
163 slot_stats: self.composition.debug_slot_table_stats(),
164 observer_stats: self.composition.debug_observer_stats(),
165 runtime_stats: runtime.debug_stats(),
166 state_arena_stats: runtime.state_arena_debug_stats(),
167 recompose_scope_stats: debug_recompose_scope_registry_stats(),
168 snapshot_v2_stats: debug_snapshot_v2_stats(),
169 snapshot_pinning_stats: debug_snapshot_pinning_stats(),
170 }
171 }
172
173 #[cfg(any(test, feature = "test-support"))]
174 #[doc(hidden)]
175 pub fn debug_slot_table_groups(&self) -> Vec<(usize, Key, Option<usize>, usize)> {
176 self.composition.debug_dump_slot_table_groups()
177 }
178
179 #[cfg(any(test, feature = "test-support"))]
180 #[doc(hidden)]
181 pub fn debug_slot_entries(&self) -> Vec<cranpose_core::SlotDebugEntry> {
182 self.composition.debug_dump_slot_entries()
183 }
184
185 #[cfg(any(test, feature = "test-support"))]
186 #[doc(hidden)]
187 pub fn runtime_handle(&self) -> cranpose_core::RuntimeHandle {
188 self.composition.runtime_handle()
189 }
190
191 #[cfg(any(test, feature = "test-support"))]
192 #[doc(hidden)]
193 pub fn debug_live_subcompose_scope_ids(&mut self) -> Vec<(NodeId, Vec<(u64, Vec<usize>)>)> {
194 fn collect_node_ids(layout: &LayoutBox, out: &mut Vec<NodeId>) {
195 out.push(layout.node_id);
196 for child in &layout.children {
197 collect_node_ids(child, out);
198 }
199 }
200
201 let mut node_ids = Vec::new();
202 if let Some(tree) = self.layout_tree() {
203 collect_node_ids(tree.root(), &mut node_ids);
204 }
205
206 let mut applier = self.composition.applier_mut();
207 let mut result = Vec::new();
208 for node_id in node_ids {
209 if let Ok(scope_ids) = applier.with_node::<SubcomposeLayoutNode, _>(node_id, |node| {
210 node.debug_scope_ids_by_slot()
211 }) {
212 result.push((node_id, scope_ids));
213 }
214 }
215 result
216 }
217
218 #[cfg(any(test, feature = "test-support"))]
219 #[doc(hidden)]
220 pub fn debug_subcompose_slot_table(
221 &mut self,
222 node_id: NodeId,
223 slot_id: u64,
224 ) -> Option<Vec<cranpose_core::SlotDebugEntry>> {
225 let mut applier = self.composition.applier_mut();
226 applier
227 .with_node::<SubcomposeLayoutNode, _>(node_id, |node| {
228 node.debug_slot_table_for_slot(SlotId::new(slot_id))
229 })
230 .ok()
231 .flatten()
232 }
233
234 #[cfg(any(test, feature = "test-support"))]
235 #[doc(hidden)]
236 pub fn debug_subcompose_slot_groups(
237 &mut self,
238 node_id: NodeId,
239 slot_id: u64,
240 ) -> Option<Vec<(usize, Key, Option<usize>, usize)>> {
241 let mut applier = self.composition.applier_mut();
242 applier
243 .with_node::<SubcomposeLayoutNode, _>(node_id, |node| {
244 node.debug_slot_table_groups_for_slot(SlotId::new(slot_id))
245 })
246 .ok()
247 .flatten()
248 }
249}
250
251fn find_layout_box(layout_box: &LayoutBox, target: NodeId) -> Option<&LayoutBox> {
252 if layout_box.node_id == target {
253 return Some(layout_box);
254 }
255
256 layout_box
257 .children
258 .iter()
259 .find_map(|child| find_layout_box(child, target))
260}
261
262fn layout_box_bounds(layout_box: &LayoutBox) -> (f32, f32, f32, f32) {
263 (
264 layout_box.rect.x,
265 layout_box.rect.y,
266 layout_box.rect.width,
267 layout_box.rect.height,
268 )
269}