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 {
16 debug_info_report(&mut self.app, &mut self.surfaces[0])
17 }
18
19 pub fn log_debug_info(&mut self) -> String {
22 let report = self.debug_info_report();
23 log::info!(target: "cranpose::debug::screen", "\n{report}");
24 report
25 }
26
27 pub fn layout_tree(&mut self) -> Option<&LayoutTree> {
29 let app_context = std::rc::Rc::clone(&self.app.app_context);
30 app_context.enter(|| self.surfaces[0].layout_tree_in_context(&mut self.app))
31 }
32
33 #[doc(hidden)]
34 pub fn with_layout_tree<T>(&mut self, block: impl FnOnce(Option<&LayoutTree>) -> T) -> T {
35 let app_context = std::rc::Rc::clone(&self.app.app_context);
36 app_context.enter(|| {
37 let layout_tree = self.surfaces[0].layout_tree_in_context(&mut self.app);
38 block(layout_tree)
39 })
40 }
41
42 pub fn semantics_active(&self) -> bool {
46 self.app.semantics_enabled
47 }
48
49 pub fn semantics_snapshot_revision(&mut self) -> u64 {
56 if self.app.semantics_enabled
57 || self
58 .surfaces
59 .iter()
60 .any(|surface| surface.inspector.state.open)
61 {
62 let app_context = std::rc::Rc::clone(&self.app.app_context);
63 let semantics_dirty = app_context.enter(|| {
64 self.app.flush_semantics_invalidations();
65 let Some(root) = self.app.composition.root() else {
66 return false;
67 };
68 let mut applier = self.app.composition.applier_mut();
69 cranpose_ui::tree_needs_semantics(&mut *applier, root).unwrap_or(true)
70 });
71 if semantics_dirty {
72 self.app.semantics_snapshot_revision =
73 self.app.semantics_snapshot_revision.wrapping_add(1);
74 }
75 }
76 self.app.semantics_snapshot_revision
77 }
78
79 pub fn semantics_tree(&mut self) -> Option<&SemanticsTree> {
81 let app_context = std::rc::Rc::clone(&self.app.app_context);
82 app_context.enter(|| self.surfaces[0].semantics_tree_in_context(&mut self.app))
83 }
84
85 pub fn root_layout_size(&mut self) -> Option<(f32, f32)> {
88 self.layout_tree().map(|tree| {
89 let root = tree.root();
90 (root.rect.width, root.rect.height)
91 })
92 }
93
94 pub fn node_layout_bounds(&mut self, target: NodeId) -> Option<(f32, f32, f32, f32)> {
97 self.layout_tree()
98 .and_then(|tree| find_layout_box(tree.root(), target))
99 .map(layout_box_bounds)
100 }
101
102 #[cfg(any(test, feature = "test-support"))]
103 #[doc(hidden)]
104 pub fn debug_runtime_leak_stats(&mut self) -> RuntimeLeakDebugStats {
105 let runtime = self.app.composition.runtime_handle();
106 let (applier_stats, live_node_heap_bytes, recycled_node_heap_bytes) = {
107 let applier = self.app.composition.applier_mut();
108 (
109 applier.debug_stats(),
110 applier.debug_live_node_heap_bytes(),
111 applier.debug_recycled_node_heap_bytes(),
112 )
113 };
114 RuntimeLeakDebugStats {
115 applier_stats,
116 live_node_heap_bytes,
117 recycled_node_heap_bytes,
118 slot_table_heap_bytes: self.app.composition.slot_table_heap_bytes(),
119 pass_stats: self.app.composition.debug_last_pass_stats(),
120 slot_stats: self.app.composition.debug_slot_table_stats(),
121 runtime_stats: runtime.debug_stats(),
122 state_arena_stats: runtime.state_arena_debug_stats(),
123 recompose_scope_stats: debug_recompose_scope_registry_stats(),
124 snapshot_v2_stats: debug_snapshot_v2_stats(),
125 snapshot_pinning_stats: debug_snapshot_pinning_stats(),
126 }
127 }
128
129 #[cfg(any(test, feature = "test-support"))]
130 #[doc(hidden)]
131 pub fn debug_slot_table_groups(&self) -> Vec<(usize, Key, Option<usize>, usize)> {
132 self.app.composition.debug_dump_slot_table_groups()
133 }
134
135 #[cfg(any(test, feature = "test-support"))]
136 #[doc(hidden)]
137 pub fn debug_slot_entries(&self) -> Vec<cranpose_core::SlotDebugEntry> {
138 self.app.composition.debug_dump_slot_entries()
139 }
140
141 #[cfg(any(test, feature = "test-support"))]
142 #[doc(hidden)]
143 pub fn runtime_handle(&self) -> cranpose_core::RuntimeHandle {
144 self.app.composition.runtime_handle()
145 }
146
147 #[cfg(any(test, feature = "test-support"))]
148 #[doc(hidden)]
149 pub fn debug_live_subcompose_scope_ids(&mut self) -> SubcomposeScopeIds {
150 fn collect_node_ids(layout: &LayoutBox, out: &mut Vec<NodeId>) {
151 out.push(layout.node_id);
152 for child in &layout.children {
153 collect_node_ids(child, out);
154 }
155 }
156
157 let mut node_ids = Vec::new();
158 if let Some(tree) = self.layout_tree() {
159 collect_node_ids(tree.root(), &mut node_ids);
160 }
161
162 let mut applier = self.app.composition.applier_mut();
163 let mut result = Vec::new();
164 for node_id in node_ids {
165 if let Ok(scope_ids) = applier.with_node::<SubcomposeLayoutNode, _>(node_id, |node| {
166 node.debug_scope_ids_by_slot()
167 }) {
168 result.push((node_id, scope_ids));
169 }
170 }
171 result
172 }
173
174 #[cfg(any(test, feature = "test-support"))]
175 #[doc(hidden)]
176 pub fn debug_subcompose_slot_table(
177 &mut self,
178 node_id: NodeId,
179 slot_id: u64,
180 ) -> Option<Vec<cranpose_core::SlotDebugEntry>> {
181 let mut applier = self.app.composition.applier_mut();
182 applier
183 .with_node::<SubcomposeLayoutNode, _>(node_id, |node| {
184 node.debug_slot_table_for_slot(SlotId::new(slot_id))
185 })
186 .ok()
187 .flatten()
188 }
189
190 #[cfg(any(test, feature = "test-support"))]
191 #[doc(hidden)]
192 pub fn debug_subcompose_slot_groups(
193 &mut self,
194 node_id: NodeId,
195 slot_id: u64,
196 ) -> Option<Vec<SlotGroupEntry>> {
197 let mut applier = self.app.composition.applier_mut();
198 applier
199 .with_node::<SubcomposeLayoutNode, _>(node_id, |node| {
200 node.debug_slot_table_groups_for_slot(SlotId::new(slot_id))
201 })
202 .ok()
203 .flatten()
204 }
205}
206
207fn find_layout_box(layout_box: &LayoutBox, target: NodeId) -> Option<&LayoutBox> {
208 if layout_box.node_id == target {
209 return Some(layout_box);
210 }
211
212 layout_box
213 .children
214 .iter()
215 .find_map(|child| find_layout_box(child, target))
216}
217
218fn layout_box_bounds(layout_box: &LayoutBox) -> (f32, f32, f32, f32) {
219 (
220 layout_box.rect.x,
221 layout_box.rect.y,
222 layout_box.rect.width,
223 layout_box.rect.height,
224 )
225}
226
227fn debug_info_report<R: Renderer>(app: &mut ShellApp, surface: &mut RootSurface<R>) -> String {
228 let app_context = std::rc::Rc::clone(&app.app_context);
229 app_context.enter(|| {
230 let mut report = String::new();
231 writeln!(report, "=== DEBUG: CURRENT SCREEN STATE ===").ok();
232 if let Some(layout_tree) = surface.layout_tree_in_context(app) {
233 let renderer = HeadlessRenderer::new();
234 let render_scene = renderer.render(layout_tree);
235 writeln!(report, "{}", format_layout_tree(layout_tree)).ok();
236 writeln!(report, "{}", format_render_scene(&render_scene)).ok();
237 writeln!(
238 report,
239 "{}",
240 format_screen_summary(layout_tree, &render_scene)
241 )
242 .ok();
243 } else {
244 writeln!(report, "No layout available").ok();
245 }
246 report
247 })
248}
249
250impl<R> SurfaceMut<'_, R>
251where
252 R: Renderer,
253 R::Error: Debug,
254{
255 pub fn debug_info_report(&mut self) -> String {
257 let (app, surface) = self.parts();
258 debug_info_report(app, surface)
259 }
260
261 pub fn log_debug_info(&mut self) -> String {
263 let report = self.debug_info_report();
264 log::info!(target: "cranpose::debug::screen", "\n{report}");
265 report
266 }
267}