1use super::*;
2
3#[cfg(any(test, feature = "test-support"))]
4type SubcomposeScopeIds = Vec<(NodeId, Vec<(u64, Vec<usize>)>)>;
5#[cfg(any(test, feature = "test-support"))]
6type SlotGroupEntry = (usize, Key, Option<usize>, usize);
7
8impl<R> AppShell<R>
9where
10 R: Renderer,
11 R::Error: Debug,
12{
13 pub fn debug_info_report(&mut self) -> String {
14 debug_info_report(&mut self.app, &mut self.surfaces[0])
15 }
16
17 pub fn log_debug_info(&mut self) -> String {
18 let report = self.debug_info_report();
19 log::info!(target: "cranpose::debug::screen", "\n{report}");
20 report
21 }
22
23 pub fn layout_tree(&mut self) -> Option<&LayoutTree> {
25 let app_context = std::rc::Rc::clone(&self.app.app_context);
26 app_context.enter(|| self.surfaces[0].layout_tree_in_context(&mut self.app))
27 }
28
29 #[doc(hidden)]
30 pub fn with_layout_tree<T>(&mut self, block: impl FnOnce(Option<&LayoutTree>) -> T) -> T {
31 let app_context = std::rc::Rc::clone(&self.app.app_context);
32 app_context.enter(|| {
33 let layout_tree = self.surfaces[0].layout_tree_in_context(&mut self.app);
34 block(layout_tree)
35 })
36 }
37
38 pub fn semantics_active(&self) -> bool {
42 self.app.semantics_enabled
43 }
44
45 pub fn semantics_snapshot_revision(&mut self) -> u64 {
52 if self.app.semantics_enabled
53 || self
54 .surfaces
55 .iter()
56 .any(|surface| surface.inspector.state.open)
57 {
58 let app_context = std::rc::Rc::clone(&self.app.app_context);
59 let semantics_dirty = app_context.enter(|| {
60 self.app.flush_semantics_invalidations();
61 let Some(root) = self.app.composition.root() else {
62 return false;
63 };
64 let mut applier = self.app.composition.applier_mut();
65 cranpose_ui::tree_needs_semantics(&mut *applier, root).unwrap_or(true)
66 });
67 if semantics_dirty {
68 self.app.semantics_snapshot_revision =
69 self.app.semantics_snapshot_revision.wrapping_add(1);
70 }
71 }
72 self.app.semantics_snapshot_revision
73 }
74
75 pub fn semantics_tree(&mut self) -> Option<&SemanticsTree> {
77 let app_context = std::rc::Rc::clone(&self.app.app_context);
78 app_context.enter(|| self.surfaces[0].semantics_tree_in_context(&mut self.app))
79 }
80
81 pub fn root_layout_size(&mut self) -> Option<(f32, f32)> {
82 self.layout_tree().map(|tree| {
83 let root = tree.root();
84 (root.rect.width, root.rect.height)
85 })
86 }
87
88 pub fn node_layout_bounds(&mut self, target: NodeId) -> Option<(f32, f32, f32, f32)> {
89 self.layout_tree()
90 .and_then(|tree| find_layout_box(tree.root(), target))
91 .map(layout_box_bounds)
92 }
93
94 #[cfg(any(test, feature = "test-support"))]
95 #[doc(hidden)]
96 pub fn debug_runtime_leak_stats(&mut self) -> RuntimeLeakDebugStats {
97 let runtime = self.app.composition.runtime_handle();
98 let (applier_stats, live_node_heap_bytes, recycled_node_heap_bytes) = {
99 let applier = self.app.composition.applier_mut();
100 (
101 applier.debug_stats(),
102 applier.debug_live_node_heap_bytes(),
103 applier.debug_recycled_node_heap_bytes(),
104 )
105 };
106 RuntimeLeakDebugStats {
107 applier_stats,
108 live_node_heap_bytes,
109 recycled_node_heap_bytes,
110 slot_table_heap_bytes: self.app.composition.slot_table_heap_bytes(),
111 pass_stats: self.app.composition.debug_last_pass_stats(),
112 slot_stats: self.app.composition.debug_slot_table_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.app.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.app.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.app.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) -> SubcomposeScopeIds {
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.app.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.app.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<SlotGroupEntry>> {
189 let mut applier = self.app.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}
218
219fn debug_info_report<R: Renderer>(app: &mut ShellApp, surface: &mut RootSurface<R>) -> String {
220 let app_context = std::rc::Rc::clone(&app.app_context);
221 app_context.enter(|| {
222 let mut report = String::new();
223 writeln!(report, "=== DEBUG: CURRENT SCREEN STATE ===").ok();
224 if let Some(layout_tree) = surface.layout_tree_in_context(app) {
225 let renderer = HeadlessRenderer::new();
226 let render_scene = renderer.render(layout_tree);
227 writeln!(report, "{}", format_layout_tree(layout_tree)).ok();
228 writeln!(report, "{}", format_render_scene(&render_scene)).ok();
229 writeln!(
230 report,
231 "{}",
232 format_screen_summary(layout_tree, &render_scene)
233 )
234 .ok();
235 } else {
236 writeln!(report, "No layout available").ok();
237 }
238 report
239 })
240}
241
242impl<R> SurfaceMut<'_, R>
243where
244 R: Renderer,
245 R::Error: Debug,
246{
247 pub fn debug_info_report(&mut self) -> String {
249 let (app, surface) = self.parts();
250 debug_info_report(app, surface)
251 }
252
253 pub fn log_debug_info(&mut self) -> String {
255 let report = self.debug_info_report();
256 log::info!(target: "cranpose::debug::screen", "\n{report}");
257 report
258 }
259}