fret_ui/tree/ui_tree_debug/
query.rs1use super::super::*;
2
3impl<H: UiHost> UiTree<H> {
4 pub fn debug_cache_root_stats(&self) -> Vec<UiDebugCacheRootStats> {
5 if !self.debug_enabled {
6 return Vec::new();
7 }
8
9 let mut out: Vec<UiDebugCacheRootStats> = self
10 .debug_view_cache_roots
11 .iter()
12 .map(|r| UiDebugCacheRootStats {
13 root: r.root,
14 element: self.nodes.get(r.root).and_then(|n| n.element),
15 reused: r.reused,
16 contained_layout: r.contained_layout,
17 paint_replayed_ops: self
18 .debug_paint_cache_replays
19 .get(&r.root)
20 .copied()
21 .unwrap_or(0),
22 reuse_reason: r.reuse_reason,
23 })
24 .collect();
25
26 out.sort_by_key(|s| std::cmp::Reverse(s.paint_replayed_ops));
27 out
28 }
29
30 pub fn debug_view_cache_contained_relayout_roots(&self) -> &[NodeId] {
31 if !self.debug_enabled {
32 return &[];
33 }
34 &self.debug_view_cache_contained_relayout_roots
35 }
36
37 #[cfg(feature = "diagnostics")]
38 pub fn debug_set_children_write_for(&self, parent: NodeId) -> Option<UiDebugSetChildrenWrite> {
39 if !self.debug_enabled {
40 return None;
41 }
42 self.debug_set_children_writes.get(&parent).copied()
43 }
44
45 #[cfg(feature = "diagnostics")]
46 pub fn debug_parent_sever_write_for(&self, child: NodeId) -> Option<UiDebugParentSeverWrite> {
47 if !self.debug_enabled {
48 return None;
49 }
50 self.debug_parent_sever_writes.get(&child).copied()
51 }
52
53 #[cfg(feature = "diagnostics")]
54 pub fn debug_layer_visible_writes(&self) -> &[UiDebugSetLayerVisibleWrite] {
55 if !self.debug_enabled {
56 return &[];
57 }
58 self.debug_layer_visible_writes.as_slice()
59 }
60
61 #[cfg(feature = "diagnostics")]
62 pub fn debug_overlay_policy_decisions(&self) -> &[UiDebugOverlayPolicyDecisionWrite] {
63 if !self.debug_enabled {
64 return &[];
65 }
66 self.debug_overlay_policy_decisions.as_slice()
67 }
68
69 pub(crate) fn debug_record_notify_request(
70 &mut self,
71 frame_id: FrameId,
72 caller_node: NodeId,
73 location: Option<crate::widget::UiSourceLocation>,
74 ) {
75 #[cfg(feature = "diagnostics")]
76 {
77 if !self.debug_enabled {
78 return;
79 }
80
81 let Some(location) = location else {
82 return;
83 };
84
85 let target = self
88 .nearest_view_cache_root(caller_node)
89 .unwrap_or(caller_node);
90
91 if self.debug_notify_requests.len() >= 256 {
92 return;
93 }
94
95 self.debug_notify_requests.push(UiDebugNotifyRequest {
96 frame_id,
97 caller_node,
98 target_view: ViewId(target),
99 file: location.file,
100 line: location.line,
101 column: location.column,
102 });
103 }
104
105 #[cfg(not(feature = "diagnostics"))]
106 {
107 let _ = (frame_id, caller_node, location);
108 }
109 }
110
111 #[track_caller]
112 #[allow(clippy::too_many_arguments)]
113 pub fn debug_record_overlay_policy_decision(
114 &mut self,
115 frame_id: FrameId,
116 layer: UiLayerId,
117 kind: &'static str,
118 present: bool,
119 interactive: bool,
120 wants_timer_events: bool,
121 reason: &'static str,
122 ) {
123 #[cfg(feature = "diagnostics")]
124 {
125 if !self.debug_enabled {
126 return;
127 }
128 let caller = std::panic::Location::caller();
129 self.debug_overlay_policy_decisions
130 .push(UiDebugOverlayPolicyDecisionWrite {
131 layer,
132 frame_id,
133 kind,
134 present,
135 interactive,
136 wants_timer_events,
137 reason,
138 file: caller.file(),
139 line: caller.line(),
140 column: caller.column(),
141 });
142 }
143
144 #[cfg(not(feature = "diagnostics"))]
145 {
146 let _ = (
147 frame_id,
148 layer,
149 kind,
150 present,
151 interactive,
152 wants_timer_events,
153 reason,
154 );
155 }
156 }
157
158 #[cfg(feature = "diagnostics")]
159 pub(crate) fn debug_set_remove_subtree_frame_context(
160 &mut self,
161 root: NodeId,
162 ctx: UiDebugRemoveSubtreeFrameContext,
163 ) {
164 if !self.debug_enabled {
165 return;
166 }
167 self.debug_remove_subtree_frame_context.insert(root, ctx);
168 }
169
170 #[cfg(feature = "diagnostics")]
171 pub fn debug_removed_subtrees(&self) -> &[UiDebugRemoveSubtreeRecord] {
172 if !self.debug_enabled {
173 return &[];
174 }
175 self.debug_removed_subtrees.as_slice()
176 }
177
178 pub fn debug_layout_engine_solves(&self) -> &[UiDebugLayoutEngineSolve] {
179 if !self.debug_enabled {
180 return &[];
181 }
182 self.debug_layout_engine_solves.as_slice()
183 }
184
185 pub fn debug_layout_hotspots(&self) -> &[UiDebugLayoutHotspot] {
186 if !self.debug_enabled {
187 return &[];
188 }
189 self.debug_layout_hotspots.as_slice()
190 }
191
192 pub fn debug_layout_inclusive_hotspots(&self) -> &[UiDebugLayoutHotspot] {
193 if !self.debug_enabled {
194 return &[];
195 }
196 self.debug_layout_inclusive_hotspots.as_slice()
197 }
198
199 pub fn debug_widget_measure_hotspots(&self) -> &[UiDebugWidgetMeasureHotspot] {
200 if !self.debug_enabled {
201 return &[];
202 }
203 self.debug_widget_measure_hotspots.as_slice()
204 }
205
206 pub fn debug_paint_widget_hotspots(&self) -> &[UiDebugPaintWidgetHotspot] {
207 if !self.debug_enabled {
208 return &[];
209 }
210 self.debug_paint_widget_hotspots.as_slice()
211 }
212
213 pub fn debug_paint_text_prepare_hotspots(&self) -> &[UiDebugPaintTextPrepareHotspot] {
214 if !self.debug_enabled {
215 return &[];
216 }
217 self.debug_paint_text_prepare_hotspots.as_slice()
218 }
219
220 #[cfg(feature = "diagnostics")]
221 pub(in crate::tree) fn debug_sample_child_elements_head(
222 &self,
223 children: &[NodeId],
224 ) -> [Option<GlobalElementId>; 4] {
225 let mut out: [Option<GlobalElementId>; 4] = [None; 4];
226 for (i, &child) in children.iter().take(out.len()).enumerate() {
227 out[i] = self.nodes.get(child).and_then(|n| n.element);
228 }
229 out
230 }
231
232 pub fn set_debug_enabled(&mut self, enabled: bool) {
233 self.debug_enabled = enabled;
234 }
235
236 pub(crate) fn debug_enabled(&self) -> bool {
237 self.debug_enabled
238 }
239
240 pub fn debug_stats(&self) -> UiDebugFrameStats {
241 self.debug_stats
242 }
243
244 pub(crate) fn debug_set_element_children_vec_pool_stats(&mut self, reuses: u32, misses: u32) {
245 if !self.debug_enabled {
246 return;
247 }
248 self.debug_stats.element_children_vec_pool_reuses = reuses;
249 self.debug_stats.element_children_vec_pool_misses = misses;
250 }
251
252 #[cfg(test)]
253 pub(crate) fn debug_measure_child_calls_for_parent(&self, parent: NodeId) -> u64 {
254 self.debug_measure_children
255 .get(&parent)
256 .map(|m| m.values().map(|r| r.calls).sum())
257 .unwrap_or(0)
258 }
259
260 pub fn debug_hover_declarative_invalidation_hotspots(
261 &self,
262 max: usize,
263 ) -> Vec<UiDebugHoverDeclarativeInvalidationHotspot> {
264 if !self.debug_enabled || max == 0 {
265 return Vec::new();
266 }
267
268 let mut out: Vec<UiDebugHoverDeclarativeInvalidationHotspot> = self
269 .debug_hover_declarative_invalidations
270 .iter()
271 .map(
272 |(&node, counts)| UiDebugHoverDeclarativeInvalidationHotspot {
273 node,
274 element: self.nodes.get(node).and_then(|n| n.element),
275 hit_test: counts.hit_test,
276 layout: counts.layout,
277 paint: counts.paint,
278 },
279 )
280 .collect();
281
282 out.sort_by_key(|hs| {
283 (
284 std::cmp::Reverse(hs.layout),
285 std::cmp::Reverse(hs.hit_test),
286 std::cmp::Reverse(hs.paint),
287 )
288 });
289 out.truncate(max);
290 out
291 }
292
293 pub fn debug_invalidation_walks(&self) -> &[UiDebugInvalidationWalk] {
294 if !self.debug_enabled {
295 return &[];
296 }
297 self.debug_invalidation_walks.as_slice()
298 }
299
300 pub fn debug_dirty_views(&self) -> &[UiDebugDirtyView] {
301 if !self.debug_enabled {
302 return &[];
303 }
304 self.debug_dirty_views.as_slice()
305 }
306
307 pub fn debug_notify_requests(&self) -> &[UiDebugNotifyRequest] {
308 #[cfg(feature = "diagnostics")]
309 {
310 if !self.debug_enabled {
311 &[]
312 } else {
313 self.debug_notify_requests.as_slice()
314 }
315 }
316
317 #[cfg(not(feature = "diagnostics"))]
318 {
319 &[]
320 }
321 }
322
323 pub fn debug_virtual_list_windows(&self) -> &[UiDebugVirtualListWindow] {
324 if !self.debug_enabled {
325 return &[];
326 }
327 self.debug_virtual_list_windows.as_slice()
328 }
329
330 pub fn debug_virtual_list_window_shift_samples(
331 &self,
332 ) -> &[UiDebugVirtualListWindowShiftSample] {
333 if !self.debug_enabled {
334 return &[];
335 }
336 self.debug_virtual_list_window_shift_samples.as_slice()
337 }
338
339 pub fn debug_retained_virtual_list_reconciles(&self) -> &[UiDebugRetainedVirtualListReconcile] {
340 if !self.debug_enabled {
341 return &[];
342 }
343 self.debug_retained_virtual_list_reconciles.as_slice()
344 }
345
346 pub fn debug_scroll_handle_changes(&self) -> &[UiDebugScrollHandleChange] {
347 if !self.debug_enabled {
348 return &[];
349 }
350 self.debug_scroll_handle_changes.as_slice()
351 }
352
353 pub fn debug_scroll_nodes(&self) -> &[UiDebugScrollNodeTelemetry] {
354 if !self.debug_enabled {
355 return &[];
356 }
357 self.debug_scroll_nodes.as_slice()
358 }
359
360 pub fn debug_scrollbars(&self) -> &[UiDebugScrollbarTelemetry] {
361 if !self.debug_enabled {
362 return &[];
363 }
364 self.debug_scrollbars.as_slice()
365 }
366
367 pub fn debug_prepaint_actions(&self) -> &[UiDebugPrepaintAction] {
368 if !self.debug_enabled {
369 return &[];
370 }
371 self.debug_prepaint_actions.as_slice()
372 }
373
374 pub fn debug_model_change_hotspots(&self) -> &[UiDebugModelChangeHotspot] {
375 if !self.debug_enabled {
376 return &[];
377 }
378 self.debug_model_change_hotspots.as_slice()
379 }
380
381 pub fn debug_model_change_unobserved(&self) -> &[UiDebugModelChangeUnobserved] {
382 if !self.debug_enabled {
383 return &[];
384 }
385 self.debug_model_change_unobserved.as_slice()
386 }
387
388 pub fn debug_global_change_hotspots(&self) -> &[UiDebugGlobalChangeHotspot] {
389 if !self.debug_enabled {
390 return &[];
391 }
392 self.debug_global_change_hotspots.as_slice()
393 }
394
395 pub fn debug_global_change_unobserved(&self) -> &[UiDebugGlobalChangeUnobserved] {
396 if !self.debug_enabled {
397 return &[];
398 }
399 self.debug_global_change_unobserved.as_slice()
400 }
401
402 pub fn debug_node_bounds(&self, node: NodeId) -> Option<Rect> {
403 self.nodes.get(node).map(|n| n.bounds)
404 }
405
406 pub fn debug_node_element(&self, node: NodeId) -> Option<GlobalElementId> {
407 self.nodes.get(node).and_then(|n| n.element)
408 }
409
410 pub fn debug_node_clips_hit_test(&self, node: NodeId) -> Option<bool> {
411 let n = self.nodes.get(node)?;
412 let widget = n.widget.as_ref();
413 let prepaint = (!self.inspection_active && !n.invalidation.hit_test)
414 .then_some(n.prepaint_hit_test)
415 .flatten();
416 Some(
417 prepaint
418 .as_ref()
419 .map(|p| p.clips_hit_test)
420 .unwrap_or_else(|| widget.map(|w| w.clips_hit_test(n.bounds)).unwrap_or(true)),
421 )
422 }
423
424 pub fn debug_node_can_scroll_descendant_into_view(&self, node: NodeId) -> Option<bool> {
425 let n = self.nodes.get(node)?;
426 let widget = n.widget.as_ref();
427 let prepaint = (!self.inspection_active && !n.invalidation.hit_test)
428 .then_some(n.prepaint_hit_test)
429 .flatten();
430 Some(
431 prepaint
432 .as_ref()
433 .map(|p| p.can_scroll_descendant_into_view)
434 .unwrap_or_else(|| {
435 widget
436 .map(|w| w.can_scroll_descendant_into_view())
437 .unwrap_or(false)
438 }),
439 )
440 }
441
442 pub fn debug_node_render_transform(&self, node: NodeId) -> Option<Transform2D> {
443 let n = self.nodes.get(node)?;
444 let widget = n.widget.as_ref();
445 let prepaint = (!self.inspection_active && !n.invalidation.hit_test)
446 .then_some(n.prepaint_hit_test)
447 .flatten();
448 if let Some(inv) = prepaint.as_ref().and_then(|p| p.render_transform_inv) {
449 return inv.inverse();
450 }
451 widget.and_then(|w| w.render_transform(n.bounds))
452 }
453
454 pub fn debug_node_children_render_transform(&self, node: NodeId) -> Option<Transform2D> {
455 let n = self.nodes.get(node)?;
456 let widget = n.widget.as_ref();
457 let prepaint = (!self.inspection_active && !n.invalidation.hit_test)
458 .then_some(n.prepaint_hit_test)
459 .flatten();
460 if let Some(inv) = prepaint
461 .as_ref()
462 .and_then(|p| p.children_render_transform_inv)
463 {
464 return inv.inverse();
465 }
466 widget.and_then(|w| w.children_render_transform(n.bounds))
467 }
468
469 pub fn debug_text_constraints_snapshot(&self, node: NodeId) -> UiDebugTextConstraintsSnapshot {
470 #[cfg(feature = "diagnostics")]
471 {
472 return UiDebugTextConstraintsSnapshot {
473 measured: self.debug_text_constraints_measured.get(&node).copied(),
474 prepared: self.debug_text_constraints_prepared.get(&node).copied(),
475 };
476 }
477 #[cfg(not(feature = "diagnostics"))]
478 {
479 let _ = node;
480 }
481
482 #[allow(unreachable_code)]
483 UiDebugTextConstraintsSnapshot::default()
484 }
485
486 pub fn debug_node_visual_bounds(&self, node: NodeId) -> Option<Rect> {
495 let bounds = self.nodes.get(node).map(|n| n.bounds)?;
496 let path = self.debug_node_path(node);
497 let mut before = Transform2D::IDENTITY;
498 let mut transform = Transform2D::IDENTITY;
499 for (idx, id) in path.iter().copied().enumerate() {
500 let node_transform = self
501 .debug_node_render_transform(id)
502 .unwrap_or(Transform2D::IDENTITY);
503 let at_node = before.compose(node_transform);
504 if id == node {
505 transform = at_node;
506 break;
507 }
508 let child_transform = self
509 .debug_node_children_render_transform(id)
510 .unwrap_or(Transform2D::IDENTITY);
511 before = at_node.compose(child_transform);
512
513 if idx == path.len().saturating_sub(1) {
515 transform = at_node;
516 }
517 }
518
519 Some(rect_aabb_transformed(bounds, transform))
520 }
521
522 pub fn debug_node_path(&self, node: NodeId) -> Vec<NodeId> {
523 let mut out: Vec<NodeId> = Vec::new();
524 let mut current = Some(node);
525 while let Some(id) = current {
526 out.push(id);
527 current = self.nodes.get(id).and_then(|n| n.parent);
528 }
529 out.reverse();
530 out
531 }
532
533 pub fn debug_layers_in_paint_order(&self) -> Vec<UiDebugLayerInfo> {
534 self.layer_order
535 .iter()
536 .copied()
537 .filter_map(|id| {
538 let layer = self.layers.get(id)?;
539 Some(UiDebugLayerInfo {
540 id,
541 root: layer.root,
542 visible: layer.visible,
543 blocks_underlay_input: layer.blocks_underlay_input,
544 hit_testable: layer.hit_testable,
545 pointer_occlusion: layer.pointer_occlusion,
546 wants_pointer_down_outside_events: layer.wants_pointer_down_outside_events,
547 consume_pointer_down_outside_events: layer.consume_pointer_down_outside_events,
548 pointer_down_outside_branches: layer.pointer_down_outside_branches.clone(),
549 wants_pointer_move_events: layer.wants_pointer_move_events,
550 wants_timer_events: layer.wants_timer_events,
551 })
552 })
553 .collect()
554 }
555
556 pub fn debug_hit_test(&self, position: Point) -> UiDebugHitTest {
557 let (active_roots, barrier_root) = self.active_input_layers();
558 let hit = self.hit_test_layers(&active_roots, position);
559 UiDebugHitTest {
560 hit,
561 active_layer_roots: active_roots,
562 barrier_root,
563 }
564 }
565
566 pub fn debug_hit_test_routing(&mut self, position: Point) -> UiDebugHitTest {
573 let (active_roots, barrier_root) = self.active_input_layers();
574
575 self.hit_test_path_cache = None;
578
579 let hit = self.hit_test_layers_cached(&active_roots, position);
580 UiDebugHitTest {
581 hit,
582 active_layer_roots: active_roots,
583 barrier_root,
584 }
585 }
586
587 #[cfg(feature = "diagnostics")]
588 pub fn debug_dispatch_snapshot(&mut self, frame_id: FrameId) -> UiDebugDispatchSnapshot {
589 if self
590 .debug_dispatch_snapshot
591 .as_ref()
592 .is_some_and(|s| s.frame_id == frame_id)
593 {
594 let snapshot = self
595 .debug_dispatch_snapshot
596 .as_ref()
597 .expect("snapshot presence checked");
598 return UiDebugDispatchSnapshot::from_snapshot(snapshot);
599 }
600
601 let snapshot = self.build_dispatch_snapshot(frame_id);
602 let debug = UiDebugDispatchSnapshot::from_snapshot(&snapshot);
603 self.debug_dispatch_snapshot = Some(snapshot);
604 debug
605 }
606
607 #[cfg(feature = "diagnostics")]
608 pub fn debug_dispatch_snapshot_parity(
609 &mut self,
610 frame_id: FrameId,
611 ) -> UiDebugDispatchSnapshotParityReport {
612 let snapshot = if self
615 .debug_dispatch_snapshot
616 .as_ref()
617 .is_some_and(|s| s.frame_id == frame_id)
618 {
619 self.debug_dispatch_snapshot
620 .as_ref()
621 .expect("snapshot presence checked")
622 .clone()
623 } else {
624 let snapshot = self.build_dispatch_snapshot(frame_id);
625 self.debug_dispatch_snapshot = Some(snapshot.clone());
626 snapshot
627 };
628
629 let mut reachable: HashSet<NodeId> = HashSet::new();
631 let mut stack: Vec<NodeId> = snapshot.active_layer_roots.clone();
632 while let Some(id) = stack.pop() {
633 if !self.nodes.contains_key(id) {
634 continue;
635 }
636 if !reachable.insert(id) {
637 continue;
638 }
639 if let Some(entry) = self.nodes.get(id) {
640 for &child in &entry.children {
641 stack.push(child);
642 }
643 }
644 }
645
646 let snapshot_nodes: HashSet<NodeId> = snapshot.nodes.iter().copied().collect();
647
648 const SAMPLE_LIMIT: usize = 64;
649
650 let mut missing_in_snapshot_sample: Vec<NodeId> = Vec::new();
651 let mut missing_in_snapshot_total = 0usize;
652 for id in reachable.iter().copied() {
653 if snapshot_nodes.contains(&id) {
654 continue;
655 }
656 missing_in_snapshot_total = missing_in_snapshot_total.saturating_add(1);
657 if missing_in_snapshot_sample.len() < SAMPLE_LIMIT {
658 missing_in_snapshot_sample.push(id);
659 }
660 }
661
662 let mut extra_in_snapshot_sample: Vec<NodeId> = Vec::new();
663 let mut extra_in_snapshot_total = 0usize;
664 for id in snapshot_nodes.iter().copied() {
665 if reachable.contains(&id) {
666 continue;
667 }
668 extra_in_snapshot_total = extra_in_snapshot_total.saturating_add(1);
669 if extra_in_snapshot_sample.len() < SAMPLE_LIMIT {
670 extra_in_snapshot_sample.push(id);
671 }
672 }
673
674 UiDebugDispatchSnapshotParityReport {
675 frame_id,
676 window: snapshot.window,
677 active_layer_roots: snapshot.active_layer_roots,
678 barrier_root: snapshot.barrier_root,
679 reachable_count: reachable.len(),
680 snapshot_count: snapshot_nodes.len(),
681 missing_in_snapshot_total,
682 missing_in_snapshot_sample,
683 extra_in_snapshot_total,
684 extra_in_snapshot_sample,
685 }
686 }
687}