1use super::*;
2
3use crate::layout_constraints::LayoutSize;
4use crate::layout_constraints::{AvailableSpace, LayoutConstraints};
5use crate::layout_engine::build_viewport_flow_subtree;
6use crate::layout_pass::LayoutPassKind;
7
8impl<H: UiHost> UiTree<H> {
9 pub fn layout_all(
10 &mut self,
11 app: &mut H,
12 services: &mut dyn UiServices,
13 bounds: Rect,
14 scale_factor: f32,
15 ) {
16 self.layout_all_with_pass_kind(app, services, bounds, scale_factor, LayoutPassKind::Final);
17 }
18
19 #[stacksafe::stacksafe]
20 pub(crate) fn layout_all_with_pass_kind(
21 &mut self,
22 app: &mut H,
23 services: &mut dyn UiServices,
24 bounds: Rect,
25 scale_factor: f32,
26 pass_kind: LayoutPassKind,
27 ) {
28 if pass_kind == LayoutPassKind::Final
29 && let Some(window) = self.window
30 {
31 let frame_id = app.frame_id();
32 app.with_global_mut_untracked(
33 fret_core::WindowFrameClockService::default,
34 |svc, _host| svc.record_frame(window, frame_id),
35 );
36 }
37
38 let profile_layout_all = crate::runtime_config::ui_runtime_config().layout_all_profile
39 && pass_kind == LayoutPassKind::Final;
40 let profile_started = profile_layout_all.then(Instant::now);
41 let mut t_invalidate_scroll_handle_bindings: Option<Duration> = None;
42 let mut t_expand_view_cache_invalidations: Option<Duration> = None;
43 let mut t_request_build_roots: Option<Duration> = None;
44 let mut t_layout_roots: Option<Duration> = None;
45 let mut t_pending_barriers: Option<Duration> = None;
46 let mut t_repair_view_cache_bounds: Option<Duration> = None;
47 let mut t_layout_contained_view_cache_roots: Option<Duration> = None;
48 let mut t_collapse_layout_observations: Option<Duration> = None;
49 let mut t_refresh_semantics: Option<Duration> = None;
50 let mut t_prepaint_after_layout: Option<Duration> = None;
51 let mut t_flush_deferred_cleanup: Option<Duration> = None;
52
53 if pass_kind == LayoutPassKind::Final {
54 self.layout_node_profile = LayoutNodeProfileConfig::from_env()
55 .map(|cfg| LayoutNodeProfileState::new(cfg, app.frame_id()));
56 self.measure_node_profile = MeasureNodeProfileConfig::from_env()
57 .map(|cfg| MeasureNodeProfileState::new(cfg, app.frame_id()));
58 } else {
59 self.layout_node_profile = None;
60 self.measure_node_profile = None;
61 }
62
63 self.measure_cache_this_frame.clear();
64 self.scratch_bounds_records.clear();
65
66 if pass_kind == LayoutPassKind::Final {
67 self.update_interactive_resize_state_for_layout(app.frame_id(), bounds, scale_factor);
68 self.prune_detached_layout_followups();
69 }
70 let force_post_resize_rebuild =
71 pass_kind == LayoutPassKind::Final && self.interactive_resize_requires_full_rebuild();
72
73 let started = self.debug_enabled.then(Instant::now);
74 if self.debug_enabled {
75 self.begin_debug_frame_if_needed(app.frame_id());
76 self.debug_stats.frame_id = app.frame_id();
77 self.debug_stats.layout_nodes_visited = 0;
78 self.debug_stats.layout_nodes_performed = 0;
79 self.debug_stats.layout_engine_solves = 0;
80 self.debug_stats.layout_engine_solve_time = Duration::default();
81 self.debug_stats.layout_engine_child_rect_queries = 0;
82 self.debug_stats.layout_engine_child_rect_time = Duration::default();
83 self.debug_stats.layout_engine_widget_fallback_solves = 0;
84 self.debug_stats.layout_collect_roots_time = Duration::default();
85 self.debug_stats
86 .layout_invalidate_scroll_handle_bindings_time = Duration::default();
87 self.debug_stats.layout_expand_view_cache_invalidations_time = Duration::default();
88 self.debug_stats.layout_request_build_roots_time = Duration::default();
89 self.debug_stats.layout_pending_barrier_relayouts_time = Duration::default();
90 self.debug_stats.layout_repair_view_cache_bounds_time = Duration::default();
91 self.debug_stats.layout_contained_view_cache_roots_time = Duration::default();
92 self.debug_stats.layout_collapse_layout_observations_time = Duration::default();
93 self.debug_stats.layout_observation_record_time = Duration::default();
94 self.debug_stats.layout_observation_record_models_items = 0;
95 self.debug_stats.layout_observation_record_globals_items = 0;
96 self.debug_stats.layout_prepaint_after_layout_time = Duration::default();
97 self.debug_stats.layout_skipped_engine_frame = false;
98 self.debug_stats.layout_fast_path_taken = false;
99 self.debug_stats.layout_invalidations_count = self.layout_invalidations_count;
100 self.debug_stats.view_cache_active = self.view_cache_active();
101 self.debug_stats.focus = self.focus;
102 self.debug_stats.captured = self.captured_for(fret_core::PointerId(0));
103 }
104
105 let roots_started = self.debug_enabled.then(Instant::now);
106 let roots: Vec<NodeId> = self
107 .visible_layers_in_paint_order()
108 .map(|layer| self.layers[layer].root)
109 .collect();
110 if let Some(roots_started) = roots_started {
111 self.debug_stats.layout_collect_roots_time += roots_started.elapsed();
112 }
113
114 let roots_len = roots.len();
115 let trace_layout = tracing::enabled!(tracing::Level::TRACE);
116
117 let mut viewport_cursor: usize = 0;
118
119 let layout_phase_time_enabled = self.debug_enabled || profile_layout_all;
120 let window = self.window;
121 let frame_id = app.frame_id();
122 let (_, invalidate_elapsed) = fret_perf::measure_span(
123 layout_phase_time_enabled,
124 trace_layout,
125 || {
126 tracing::trace_span!(
127 "fret.ui.layout.invalidate_scroll_handle_bindings",
128 window = ?window,
129 frame_id = frame_id.0,
130 pass_kind = ?pass_kind,
131 )
132 },
133 || {
134 self.invalidate_scroll_handle_bindings_for_changed_handles(
135 app, pass_kind, true, true,
136 )
137 },
138 );
139 if profile_layout_all {
140 t_invalidate_scroll_handle_bindings = invalidate_elapsed;
141 }
142 if self.debug_enabled
143 && let Some(invalidate_elapsed) = invalidate_elapsed
144 {
145 self.debug_stats
146 .layout_invalidate_scroll_handle_bindings_time += invalidate_elapsed;
147 }
148
149 let any_root_needs_layout_or_bounds = roots.iter().any(|&root| {
150 self.nodes
151 .get(root)
152 .is_some_and(|node| node.invalidation.layout || node.bounds != bounds)
153 });
154 let any_pending_barrier_needs_layout = self.pending_barrier_relayouts.iter().any(|&root| {
155 self.node_is_attached_to_layer_tree(root)
156 && self
157 .nodes
158 .get(root)
159 .is_some_and(|node| node.invalidation.layout)
160 });
161 let any_view_cache_root_needs_layout = self.view_cache_active()
162 && self.nodes.iter().any(|(id, node)| {
163 self.node_is_attached_to_layer_tree(id)
164 && node.view_cache.enabled
165 && node.invalidation.layout
166 });
167
168 if pass_kind == LayoutPassKind::Final
169 && !any_root_needs_layout_or_bounds
170 && !any_view_cache_root_needs_layout
171 && !any_pending_barrier_needs_layout
172 && self.invalidated_paint_nodes == 0
173 && self.invalidated_hit_test_nodes == 0
174 && !force_post_resize_rebuild
175 {
176 self.pending_barrier_relayouts.retain(|&root| {
177 self.nodes
178 .get(root)
179 .is_some_and(|node| node.invalidation.layout)
180 });
181 let prepaint_started = self.debug_enabled.then(Instant::now);
182 self.prepaint_after_layout_stable_frame(app);
183 if let Some(prepaint_started) = prepaint_started {
184 self.debug_stats.layout_prepaint_after_layout_time += prepaint_started.elapsed();
185 }
186 self.debug_stats.layout_skipped_engine_frame = true;
187
188 let focus_started = self.debug_enabled.then(Instant::now);
189 self.resolve_pending_focus_target_if_needed(app);
190 self.repair_focus_node_from_focused_element_if_needed(app);
191 if let Some(focus_started) = focus_started {
192 self.debug_stats.layout_focus_repair_time += focus_started.elapsed();
193 }
194
195 if self.semantics_requested {
196 let semantics_started = self.debug_enabled.then(Instant::now);
197 self.semantics_requested = false;
198 self.refresh_semantics_snapshot(app);
199 if let Some(semantics_started) = semantics_started {
200 self.debug_stats.layout_semantics_refresh_time += semantics_started.elapsed();
201 }
202 }
203
204 let deferred_cleanup_started = self.debug_enabled.then(Instant::now);
205 self.flush_deferred_cleanup(services);
206 if let Some(deferred_cleanup_started) = deferred_cleanup_started {
207 self.debug_stats.layout_deferred_cleanup_time += deferred_cleanup_started.elapsed();
208 }
209 self.last_layout_frame_id = Some(app.frame_id());
210 self.refine_pending_window_runtime_snapshots_after_layout(app);
211 if let Some(started) = started {
212 self.debug_stats.layout_time = started
213 .elapsed()
214 .saturating_sub(self.debug_stats.layout_prepaint_after_layout_time);
215 }
216 return;
217 }
218
219 if pass_kind == LayoutPassKind::Final {
220 let (_, expand_elapsed) = fret_perf::measure_span(
221 layout_phase_time_enabled,
222 trace_layout,
223 || {
224 tracing::trace_span!(
225 "fret.ui.layout.expand_view_cache_invalidations",
226 window = ?window,
227 frame_id = frame_id.0,
228 pass_kind = ?pass_kind,
229 )
230 },
231 || self.expand_view_cache_layout_invalidations_if_needed(),
232 );
233 if profile_layout_all {
234 t_expand_view_cache_invalidations = expand_elapsed;
235 }
236 if self.debug_enabled
237 && let Some(expand_elapsed) = expand_elapsed
238 {
239 self.debug_stats.layout_expand_view_cache_invalidations_time += expand_elapsed;
240 }
241 }
242
243 if pass_kind == LayoutPassKind::Final
247 && self.pending_barrier_relayouts.is_empty()
248 && self.last_layout_bounds == Some(bounds)
249 && self.last_layout_scale_factor == Some(scale_factor)
250 && !self.any_attached_layout_invalidations()
251 && !force_post_resize_rebuild
252 {
253 self.debug_stats.layout_fast_path_taken = true;
254 self.prepaint_after_layout(app, scale_factor);
255
256 self.repair_focus_node_from_focused_element_if_needed(app);
257
258 if self.semantics_requested {
259 self.semantics_requested = false;
260 self.refresh_semantics_snapshot(app);
261 }
262 self.flush_deferred_cleanup(services);
263 self.last_layout_frame_id = Some(app.frame_id());
264 self.refine_pending_window_runtime_snapshots_after_layout(app);
265
266 self.last_layout_bounds = Some(bounds);
267 self.last_layout_scale_factor = Some(scale_factor);
268
269 if let Some(started) = started {
270 self.debug_stats.layout_time = started.elapsed();
271 }
272 return;
273 }
274
275 let (layout_engine_solves_start, layout_engine_solve_time_start) = {
276 self.begin_layout_engine_frame(app);
277 if self.debug_enabled {
278 (
279 self.layout_engine.solve_count(),
280 self.layout_engine.last_solve_time(),
281 )
282 } else {
283 (0, Duration::default())
284 }
285 };
286
287 let (_, request_build_elapsed) = fret_perf::measure_span(
288 layout_phase_time_enabled,
289 trace_layout,
290 || {
291 tracing::trace_span!(
292 "fret.ui.layout.request_build_roots",
293 window = ?window,
294 frame_id = frame_id.0,
295 pass_kind = ?pass_kind,
296 roots_len,
297 )
298 },
299 || {
300 self.request_build_window_roots_if_final(
301 app,
302 services,
303 &roots,
304 bounds,
305 scale_factor,
306 pass_kind,
307 );
308 },
309 );
310 if profile_layout_all {
311 t_request_build_roots = request_build_elapsed;
312 }
313 if self.debug_enabled
314 && let Some(request_build_elapsed) = request_build_elapsed
315 {
316 self.debug_stats.layout_request_build_roots_time += request_build_elapsed;
317 }
318
319 let (_, roots_elapsed) = fret_perf::measure_span(
320 layout_phase_time_enabled,
321 trace_layout,
322 || {
323 tracing::trace_span!(
324 "fret.ui.layout.roots",
325 window = ?window,
326 frame_id = frame_id.0,
327 pass_kind = ?pass_kind,
328 roots_len,
329 )
330 },
331 || {
332 for root in roots {
333 let _ = self.layout_in_with_pass_kind(
334 app,
335 services,
336 root,
337 bounds,
338 scale_factor,
339 pass_kind,
340 crate::layout::overflow::LayoutOverflowContext::default(),
341 );
342
343 self.flush_viewport_roots_after_root(
344 app,
345 services,
346 scale_factor,
347 pass_kind,
348 &mut viewport_cursor,
349 );
350 }
351 },
352 );
353 if profile_layout_all {
354 t_layout_roots = roots_elapsed;
355 }
356 if self.debug_enabled
357 && let Some(roots_elapsed) = roots_elapsed
358 {
359 self.debug_stats.layout_roots_time += roots_elapsed;
360 }
361
362 if pass_kind == LayoutPassKind::Final {
363 let (_, barrier_elapsed) = fret_perf::measure_span(
364 layout_phase_time_enabled,
365 trace_layout,
366 || {
367 tracing::trace_span!(
368 "fret.ui.layout.pending_barriers",
369 window = ?window,
370 frame_id = frame_id.0,
371 pass_kind = ?pass_kind,
372 )
373 },
374 || {
375 self.layout_pending_barrier_relayouts_if_needed(
376 app,
377 services,
378 scale_factor,
379 pass_kind,
380 &mut viewport_cursor,
381 );
382 },
383 );
384 if profile_layout_all {
385 t_pending_barriers = barrier_elapsed;
386 }
387 if self.debug_enabled
388 && let Some(barrier_elapsed) = barrier_elapsed
389 {
390 self.debug_stats.layout_barrier_relayouts_time += barrier_elapsed;
391 self.debug_stats.layout_pending_barrier_relayouts_time += barrier_elapsed;
392 }
393 }
394
395 if pass_kind == LayoutPassKind::Final {
396 let (_, view_cache_elapsed) = fret_perf::measure_span(
397 layout_phase_time_enabled,
398 trace_layout,
399 || {
400 tracing::trace_span!(
401 "fret.ui.layout.view_cache",
402 window = ?window,
403 frame_id = frame_id.0,
404 pass_kind = ?pass_kind,
405 )
406 },
407 || {
408 let (_, repair_elapsed) = fret_perf::measure_span(
409 layout_phase_time_enabled,
410 trace_layout,
411 || {
412 tracing::trace_span!(
413 "fret.ui.layout.view_cache.repair_bounds",
414 window = ?window,
415 frame_id = frame_id.0,
416 pass_kind = ?pass_kind,
417 )
418 },
419 || self.repair_view_cache_root_bounds_from_engine_if_needed(app),
420 );
421 if profile_layout_all {
422 t_repair_view_cache_bounds = repair_elapsed;
423 }
424 if self.debug_enabled
425 && let Some(repair_elapsed) = repair_elapsed
426 {
427 self.debug_stats.layout_repair_view_cache_bounds_time += repair_elapsed;
428 }
429
430 let (_, contained_elapsed) = fret_perf::measure_span(
431 layout_phase_time_enabled,
432 trace_layout,
433 || {
434 tracing::trace_span!(
435 "fret.ui.layout.view_cache.layout_contained_roots",
436 window = ?window,
437 frame_id = frame_id.0,
438 pass_kind = ?pass_kind,
439 )
440 },
441 || {
442 self.layout_contained_view_cache_roots_if_needed(
443 app,
444 services,
445 scale_factor,
446 pass_kind,
447 &mut viewport_cursor,
448 );
449 },
450 );
451 if profile_layout_all {
452 t_layout_contained_view_cache_roots = contained_elapsed;
453 }
454 if self.debug_enabled
455 && let Some(contained_elapsed) = contained_elapsed
456 {
457 self.debug_stats.layout_contained_view_cache_roots_time +=
458 contained_elapsed;
459 }
460
461 let (_, collapse_elapsed) = fret_perf::measure_span(
462 layout_phase_time_enabled,
463 trace_layout,
464 || {
465 tracing::trace_span!(
466 "fret.ui.layout.view_cache.collapse_observations",
467 window = ?window,
468 frame_id = frame_id.0,
469 pass_kind = ?pass_kind,
470 )
471 },
472 || self.collapse_layout_observations_to_view_cache_roots_if_needed(),
473 );
474 if profile_layout_all {
475 t_collapse_layout_observations = collapse_elapsed;
476 }
477 if self.debug_enabled
478 && let Some(collapse_elapsed) = collapse_elapsed
479 {
480 self.debug_stats.layout_collapse_layout_observations_time +=
481 collapse_elapsed;
482 }
483 },
484 );
485 if self.debug_enabled
486 && let Some(view_cache_elapsed) = view_cache_elapsed
487 {
488 self.debug_stats.layout_view_cache_time += view_cache_elapsed;
489 }
490 }
491
492 if pass_kind == LayoutPassKind::Final {
493 self.flush_layout_bounds_records_if_needed(app);
494 let (_, prepaint_elapsed) = fret_perf::measure_span(
495 layout_phase_time_enabled,
496 trace_layout,
497 || {
498 tracing::trace_span!(
499 "fret.ui.layout.prepaint_after_layout",
500 window = ?window,
501 frame_id = frame_id.0,
502 pass_kind = ?pass_kind,
503 )
504 },
505 || self.prepaint_after_layout(app, scale_factor),
506 );
507 if profile_layout_all {
508 t_prepaint_after_layout = prepaint_elapsed;
509 }
510 if self.debug_enabled
511 && let Some(prepaint_elapsed) = prepaint_elapsed
512 {
513 self.debug_stats.layout_prepaint_after_layout_time += prepaint_elapsed;
514 }
515 }
516 if pass_kind == LayoutPassKind::Final {
517 let (_, focus_elapsed) = fret_perf::measure_span(
518 self.debug_enabled,
519 trace_layout,
520 || {
521 tracing::trace_span!(
522 "fret.ui.layout.focus_repair",
523 window = ?window,
524 frame_id = frame_id.0,
525 pass_kind = ?pass_kind,
526 )
527 },
528 || {
529 self.resolve_pending_focus_target_if_needed(app);
530 self.repair_focus_node_from_focused_element_if_needed(app)
531 },
532 );
533 if let Some(focus_elapsed) = focus_elapsed {
534 self.debug_stats.layout_focus_repair_time += focus_elapsed;
535 }
536 }
537
538 if self.semantics_requested {
539 let (_, semantics_elapsed) = fret_perf::measure_span(
540 layout_phase_time_enabled,
541 trace_layout,
542 || {
543 tracing::trace_span!(
544 "fret.ui.layout.refresh_semantics",
545 window = ?window,
546 frame_id = frame_id.0,
547 pass_kind = ?pass_kind,
548 )
549 },
550 || {
551 self.semantics_requested = false;
552 self.refresh_semantics_snapshot(app);
553 },
554 );
555 if profile_layout_all {
556 t_refresh_semantics = semantics_elapsed;
557 }
558 if self.debug_enabled
559 && let Some(semantics_elapsed) = semantics_elapsed
560 {
561 self.debug_stats.layout_semantics_refresh_time += semantics_elapsed;
562 }
563 }
564 let (_, deferred_cleanup_elapsed) = fret_perf::measure_span(
565 layout_phase_time_enabled,
566 trace_layout,
567 || {
568 tracing::trace_span!(
569 "fret.ui.layout.flush_deferred_cleanup",
570 window = ?window,
571 frame_id = frame_id.0,
572 pass_kind = ?pass_kind,
573 )
574 },
575 || self.flush_deferred_cleanup(services),
576 );
577 if profile_layout_all {
578 t_flush_deferred_cleanup = deferred_cleanup_elapsed;
579 }
580 if self.debug_enabled
581 && let Some(deferred_cleanup_elapsed) = deferred_cleanup_elapsed
582 {
583 self.debug_stats.layout_deferred_cleanup_time += deferred_cleanup_elapsed;
584 }
585
586 if let Some(started) = started {
590 self.debug_stats.layout_time = started
591 .elapsed()
592 .saturating_sub(self.debug_stats.layout_prepaint_after_layout_time);
593 }
594
595 if pass_kind == LayoutPassKind::Final {
596 self.finish_final_layout_frame(app);
597 }
598
599 if pass_kind == LayoutPassKind::Final {
600 self.last_layout_bounds = Some(bounds);
601 self.last_layout_scale_factor = Some(scale_factor);
602 }
603
604 if self.debug_enabled {
605 self.debug_stats.layout_engine_solves = self
606 .layout_engine
607 .solve_count()
608 .saturating_sub(layout_engine_solves_start);
609 self.debug_stats.layout_engine_solve_time = self
610 .layout_engine
611 .last_solve_time()
612 .saturating_sub(layout_engine_solve_time_start);
613 }
614
615 if let Some(started) = profile_started {
616 let total = started.elapsed();
617 tracing::info!(
618 window = ?self.window,
619 total_ms = total.as_millis(),
620 invalidate_scroll_handle_bindings_ms =
621 t_invalidate_scroll_handle_bindings.map(|d| d.as_millis()),
622 expand_view_cache_invalidations_ms =
623 t_expand_view_cache_invalidations.map(|d| d.as_millis()),
624 request_build_roots_ms = t_request_build_roots.map(|d| d.as_millis()),
625 layout_roots_ms = t_layout_roots.map(|d| d.as_millis()),
626 pending_barriers_ms = t_pending_barriers.map(|d| d.as_millis()),
627 repair_view_cache_bounds_ms = t_repair_view_cache_bounds.map(|d| d.as_millis()),
628 layout_contained_view_cache_roots_ms =
629 t_layout_contained_view_cache_roots.map(|d| d.as_millis()),
630 collapse_layout_observations_ms =
631 t_collapse_layout_observations.map(|d| d.as_millis()),
632 refresh_semantics_ms = t_refresh_semantics.map(|d| d.as_millis()),
633 prepaint_after_layout_ms = t_prepaint_after_layout.map(|d| d.as_millis()),
634 flush_deferred_cleanup_ms = t_flush_deferred_cleanup.map(|d| d.as_millis()),
635 layout_nodes_performed = self.debug_stats.layout_nodes_performed,
636 "layout_all profile"
637 );
638 }
639
640 if pass_kind == LayoutPassKind::Final {
641 self.emit_layout_node_profile(app);
642 self.emit_measure_node_profile(app);
643 }
644 }
645
646 fn emit_layout_node_profile(&mut self, app: &mut H) {
647 let Some(profile) = self.layout_node_profile.take() else {
648 return;
649 };
650 if profile.entries.is_empty() {
651 return;
652 }
653 let Some(window) = self.window else {
654 return;
655 };
656
657 let mut test_id_by_node: HashMap<NodeId, String> = HashMap::new();
658 if let Some(snapshot) = self.semantics_snapshot() {
659 for node in &snapshot.nodes {
660 if let Some(test_id) = node.test_id.as_deref() {
661 test_id_by_node.insert(node.id, test_id.to_string());
662 }
663 }
664 }
665
666 let resolve_test_id = |tree: &UiTree<H>, id: NodeId| -> Option<&str> {
667 let mut cur = Some(id);
668 while let Some(node) = cur {
669 if let Some(test_id) = test_id_by_node.get(&node) {
670 return Some(test_id.as_str());
671 }
672 cur = tree.nodes.get(node).and_then(|n| n.parent);
673 }
674 None
675 };
676
677 for (rank, entry) in profile.entries.iter().enumerate() {
678 let kind = crate::declarative::frame::element_record_for_node(app, window, entry.node)
679 .map(|r| r.instance.kind_name());
680
681 let element_path: Option<String> = self
682 .nodes
683 .get(entry.node)
684 .and_then(|n| n.element)
685 .and_then(|element| {
686 #[cfg(feature = "diagnostics")]
687 {
688 crate::elements::with_window_state(app, window, |st| {
689 st.debug_path_for_element(element)
690 })
691 }
692 #[cfg(not(feature = "diagnostics"))]
693 {
694 let _ = element;
695 None
696 }
697 });
698
699 tracing::info!(
700 window = ?self.window,
701 frame_id = profile.frame_id.0,
702 nodes_profiled = profile.nodes_profiled,
703 total_self_ms = profile.total_self_time.as_millis() as u64,
704 rank,
705 node = ?entry.node,
706 pass = ?entry.pass_kind,
707 self_us = entry.elapsed_self.as_micros() as u64,
708 total_us = entry.elapsed_total.as_micros() as u64,
709 kind = kind.unwrap_or("<unknown>"),
710 test_id = resolve_test_id(self, entry.node),
711 element_path = element_path.as_deref().unwrap_or("<unknown>"),
712 bounds_w = entry.bounds.size.width.0,
713 bounds_h = entry.bounds.size.height.0,
714 "layout_node profile"
715 );
716 }
717 }
718
719 fn emit_measure_node_profile(&mut self, app: &mut H) {
720 let Some(profile) = self.measure_node_profile.take() else {
721 return;
722 };
723 if profile.entries.is_empty() {
724 return;
725 }
726 let Some(window) = self.window else {
727 return;
728 };
729
730 let mut test_id_by_node: HashMap<NodeId, String> = HashMap::new();
731 if let Some(snapshot) = self.semantics_snapshot() {
732 for node in &snapshot.nodes {
733 if let Some(test_id) = node.test_id.as_deref() {
734 test_id_by_node.insert(node.id, test_id.to_string());
735 }
736 }
737 }
738
739 let resolve_test_id = |tree: &UiTree<H>, id: NodeId| -> Option<&str> {
740 let mut cur = Some(id);
741 while let Some(node) = cur {
742 if let Some(test_id) = test_id_by_node.get(&node) {
743 return Some(test_id.as_str());
744 }
745 cur = tree.nodes.get(node).and_then(|n| n.parent);
746 }
747 None
748 };
749
750 for (rank, entry) in profile.entries.iter().enumerate() {
751 let kind = crate::declarative::frame::element_record_for_node(app, window, entry.node)
752 .map(|r| r.instance.kind_name());
753
754 let element_path: Option<String> = self
755 .nodes
756 .get(entry.node)
757 .and_then(|n| n.element)
758 .and_then(|element| {
759 #[cfg(feature = "diagnostics")]
760 {
761 crate::elements::with_window_state(app, window, |st| {
762 st.debug_path_for_element(element)
763 })
764 }
765 #[cfg(not(feature = "diagnostics"))]
766 {
767 let _ = element;
768 None
769 }
770 });
771
772 tracing::info!(
773 window = ?self.window,
774 frame_id = profile.frame_id.0,
775 nodes_profiled = profile.nodes_profiled,
776 total_self_ms = profile.total_self_time.as_millis() as u64,
777 rank,
778 node = ?entry.node,
779 self_us = entry.elapsed_self.as_micros() as u64,
780 total_us = entry.elapsed_total.as_micros() as u64,
781 kind = kind.unwrap_or("<unknown>"),
782 test_id = resolve_test_id(self, entry.node),
783 element_path = element_path.as_deref().unwrap_or("<unknown>"),
784 known_w = entry.constraints.known.width.map(|p| p.0),
785 known_h = entry.constraints.known.height.map(|p| p.0),
786 avail_w = ?entry.constraints.available.width,
787 avail_h = ?entry.constraints.available.height,
788 "measure_node profile"
789 );
790 }
791 }
792
793 fn repair_focus_node_from_focused_element_if_needed(&mut self, app: &mut H) {
794 let Some(window) = self.window else {
795 return;
796 };
797 let Some(focused) = self.focus() else {
798 return;
799 };
800 let Some(element) = self.node_element(focused) else {
801 #[cfg(debug_assertions)]
802 if crate::runtime_config::ui_runtime_config().debug_focus_repair {
803 eprintln!("focus_repair: focused={focused:?} has no element");
804 }
805 return;
806 };
807 let Some(canonical) =
808 self.resolve_live_attached_node_for_element(app, Some(window), element)
809 else {
810 #[cfg(debug_assertions)]
811 if crate::runtime_config::ui_runtime_config().debug_focus_repair {
812 eprintln!(
813 "focus_repair: focused={focused:?} element={element:?} has no canonical node",
814 );
815 }
816 return;
817 };
818 #[cfg(debug_assertions)]
819 if crate::runtime_config::ui_runtime_config().debug_focus_repair {
820 eprintln!(
821 "focus_repair: focused={focused:?} element={element:?} canonical={canonical:?} canonical_exists={}",
822 self.node_exists(canonical)
823 );
824 }
825 if canonical != focused && self.node_exists(canonical) {
826 self.set_focus(Some(canonical));
827 self.request_post_layout_window_runtime_snapshot_refine();
828 }
829
830 let Some(focused) = self.focus() else {
831 return;
832 };
833 let Some(node) = self.nodes.get(focused) else {
834 return;
835 };
836 if node.bounds.size.width.0 <= 0.0 || node.bounds.size.height.0 <= 0.0 {
837 #[cfg(debug_assertions)]
838 if crate::runtime_config::ui_runtime_config().debug_focus_repair {
839 eprintln!(
840 "focus_repair: clearing focus={focused:?} due to empty bounds={:?}",
841 node.bounds
842 );
843 }
844 self.set_focus(None);
845 self.request_post_layout_window_runtime_snapshot_refine();
846 }
847 }
848
849 fn repair_view_cache_root_bounds_from_engine_if_needed(&mut self, _app: &mut H) {
850 if !self.view_cache_active() {
851 return;
852 }
853
854 let mut targets: Vec<(NodeId, Rect, Point)> = Vec::with_capacity(16);
855 for (id, node) in self.nodes.iter() {
856 if !node.view_cache.enabled {
857 continue;
858 }
859 if node.bounds.size != Size::default() {
860 continue;
861 }
862 let Some(parent) = node.parent else {
863 continue;
864 };
865 let Some(parent_bounds) = self.nodes.get(parent).map(|n| n.bounds) else {
866 continue;
867 };
868 let Some(local) = self.layout_engine_child_local_rect(parent, id) else {
869 continue;
870 };
871
872 let origin = Point::new(
873 Px(parent_bounds.origin.x.0 + local.origin.x.0),
874 Px(parent_bounds.origin.y.0 + local.origin.y.0),
875 );
876 let new_bounds = Rect::new(origin, local.size);
877 targets.push((id, new_bounds, node.bounds.origin));
878 }
879
880 for (root, new_bounds, old_origin) in targets {
881 let delta = Point::new(
882 Px(new_bounds.origin.x.0 - old_origin.x.0),
883 Px(new_bounds.origin.y.0 - old_origin.y.0),
884 );
885
886 if let Some(node) = self.nodes.get_mut(root) {
887 node.bounds = new_bounds;
888 }
889
890 if delta.x.0 == 0.0 && delta.y.0 == 0.0 {
891 continue;
892 }
893
894 let mut stack: Vec<NodeId> = self
895 .nodes
896 .get(root)
897 .map(|n| n.children.clone())
898 .unwrap_or_default();
899 while let Some(id) = stack.pop() {
900 let Some(n) = self.nodes.get_mut(id) else {
901 continue;
902 };
903 n.bounds.origin = Point::new(
904 Px(n.bounds.origin.x.0 + delta.x.0),
905 Px(n.bounds.origin.y.0 + delta.y.0),
906 );
907 stack.extend(n.children.iter().copied());
908 }
909 }
910 }
911
912 fn layout_pending_barrier_relayouts_if_needed(
913 &mut self,
914 app: &mut H,
915 services: &mut dyn UiServices,
916 scale_factor: f32,
917 pass_kind: LayoutPassKind,
918 viewport_cursor: &mut usize,
919 ) {
920 if pass_kind != LayoutPassKind::Final {
921 return;
922 }
923
924 const MAX_PASSES: usize = 4;
932 let mut passes: usize = 0;
933 let mut scheduled_followups: HashSet<NodeId> = HashSet::new();
934
935 while passes < MAX_PASSES {
936 passes = passes.saturating_add(1);
937
938 let pending = self.take_pending_barrier_relayouts();
939 if pending.is_empty() {
940 break;
941 }
942
943 let mut unique = HashSet::<NodeId>::with_capacity(pending.len());
944 let mut targets: Vec<NodeId> = Vec::with_capacity(pending.len());
945 for node in pending {
946 if unique.insert(node) {
947 targets.push(node);
948 }
949 }
950
951 let mut roots_with_bounds: Vec<(NodeId, Rect)> = Vec::with_capacity(targets.len());
952 for root in targets {
953 let Some(node) = self.nodes.get(root) else {
954 continue;
955 };
956 if !node.invalidation.layout {
957 continue;
958 }
959
960 let mut bounds = node.bounds;
964 if (bounds.size == Size::default() || bounds.origin == Point::default())
965 && let Some(parent) = node.parent
966 && let Some(parent_bounds) = self.nodes.get(parent).map(|n| n.bounds)
967 && let Some(local) = self.layout_engine_child_local_rect(parent, root)
968 {
969 let resolved = Rect::new(
970 Point::new(
971 Px(parent_bounds.origin.x.0 + local.origin.x.0),
972 Px(parent_bounds.origin.y.0 + local.origin.y.0),
973 ),
974 local.size,
975 );
976 if resolved.size != Size::default() {
977 bounds = resolved;
978 }
979 }
980
981 if bounds.size == Size::default() {
982 continue;
983 }
984
985 roots_with_bounds.push((root, bounds));
986 }
987
988 self.solve_barrier_flow_roots_if_needed(
992 app,
993 services,
994 &roots_with_bounds,
995 scale_factor,
996 );
997
998 for (root, bounds) in roots_with_bounds {
999 let _ = self.layout_in_with_pass_kind(
1000 app,
1001 services,
1002 root,
1003 bounds,
1004 scale_factor,
1005 pass_kind,
1006 crate::layout::overflow::LayoutOverflowContext::default(),
1007 );
1008 if self.debug_enabled {
1009 self.debug_stats.barrier_relayouts_performed = self
1010 .debug_stats
1011 .barrier_relayouts_performed
1012 .saturating_add(1);
1013 }
1014
1015 let mut current = self.nodes.get(root).and_then(|n| n.parent);
1019 while let Some(id) = current {
1020 let can_scroll = self
1021 .nodes
1022 .get(id)
1023 .and_then(|n| n.widget.as_ref())
1024 .is_some_and(|w| w.can_scroll_descendant_into_view());
1025 if can_scroll {
1026 if scheduled_followups.insert(id) {
1027 self.schedule_barrier_relayout_with_source_and_detail(
1028 id,
1029 UiDebugInvalidationSource::Other,
1030 UiDebugInvalidationDetail::Unknown,
1031 );
1032 }
1033 break;
1034 }
1035 current = self.nodes.get(id).and_then(|n| n.parent);
1036 }
1037
1038 self.flush_viewport_roots_after_root(
1039 app,
1040 services,
1041 scale_factor,
1042 pass_kind,
1043 viewport_cursor,
1044 );
1045 }
1046 }
1047 }
1048
1049 pub fn layout(
1050 &mut self,
1051 app: &mut H,
1052 services: &mut dyn UiServices,
1053 root: NodeId,
1054 available: Size,
1055 scale_factor: f32,
1056 ) -> Size {
1057 let bounds = Rect::new(
1058 Point::new(fret_core::Px(0.0), fret_core::Px(0.0)),
1059 available,
1060 );
1061 self.update_interactive_resize_state_for_layout(app.frame_id(), bounds, scale_factor);
1062 let force_post_resize_rebuild = self.interactive_resize_requires_full_rebuild();
1063 if force_post_resize_rebuild {
1064 self.mark_subtree_invalidation_local(root, Invalidation::Layout);
1065 }
1066
1067 if self.invalidated_layout_nodes == 0
1068 && self.invalidated_hit_test_nodes == 0
1069 && let Some(n) = self.nodes.get(root)
1070 && !n.invalidation.layout
1071 && !n.invalidation.hit_test
1072 && n.bounds == bounds
1073 && n.measured_size != Size::default()
1074 && !force_post_resize_rebuild
1075 {
1076 return n.measured_size;
1077 }
1078
1079 let mut viewport_cursor: usize = 0;
1080 self.begin_layout_engine_frame(app);
1081 self.request_build_window_roots_if_final(
1082 app,
1083 services,
1084 std::slice::from_ref(&root),
1085 bounds,
1086 scale_factor,
1087 LayoutPassKind::Final,
1088 );
1089 let size = self.layout_in_with_pass_kind(
1090 app,
1091 services,
1092 root,
1093 bounds,
1094 scale_factor,
1095 LayoutPassKind::Final,
1096 crate::layout::overflow::LayoutOverflowContext::default(),
1097 );
1098 self.flush_viewport_roots_after_root(
1099 app,
1100 services,
1101 scale_factor,
1102 LayoutPassKind::Final,
1103 &mut viewport_cursor,
1104 );
1105
1106 self.finish_final_layout_frame(app);
1107 size
1108 }
1109
1110 pub fn layout_in(
1111 &mut self,
1112 app: &mut H,
1113 services: &mut dyn UiServices,
1114 root: NodeId,
1115 bounds: Rect,
1116 scale_factor: f32,
1117 ) -> Size {
1118 self.update_interactive_resize_state_for_layout(app.frame_id(), bounds, scale_factor);
1119 let force_post_resize_rebuild = self.interactive_resize_requires_full_rebuild();
1120 if force_post_resize_rebuild {
1121 self.mark_subtree_invalidation_local(root, Invalidation::Layout);
1122 }
1123 if self.invalidated_layout_nodes == 0
1124 && self.invalidated_hit_test_nodes == 0
1125 && let Some(n) = self.nodes.get(root)
1126 && !n.invalidation.layout
1127 && !n.invalidation.hit_test
1128 && n.bounds == bounds
1129 && n.measured_size != Size::default()
1130 && !force_post_resize_rebuild
1131 {
1132 return n.measured_size;
1133 }
1134
1135 let mut viewport_cursor: usize = 0;
1136 self.begin_layout_engine_frame(app);
1137 self.request_build_window_roots_if_final(
1138 app,
1139 services,
1140 std::slice::from_ref(&root),
1141 bounds,
1142 scale_factor,
1143 LayoutPassKind::Final,
1144 );
1145 let size = self.layout_in_with_pass_kind(
1146 app,
1147 services,
1148 root,
1149 bounds,
1150 scale_factor,
1151 LayoutPassKind::Final,
1152 crate::layout::overflow::LayoutOverflowContext::default(),
1153 );
1154 self.flush_viewport_roots_after_root(
1155 app,
1156 services,
1157 scale_factor,
1158 LayoutPassKind::Final,
1159 &mut viewport_cursor,
1160 );
1161 self.finish_final_layout_frame(app);
1162 size
1163 }
1164
1165 #[stacksafe::stacksafe]
1166 pub fn layout_in_with_pass_kind(
1167 &mut self,
1168 app: &mut H,
1169 services: &mut dyn UiServices,
1170 root: NodeId,
1171 bounds: Rect,
1172 scale_factor: f32,
1173 pass_kind: LayoutPassKind,
1174 overflow_ctx: crate::layout::overflow::LayoutOverflowContext,
1175 ) -> Size {
1176 self.layout_node(
1177 app,
1178 services,
1179 root,
1180 bounds,
1181 scale_factor,
1182 pass_kind,
1183 overflow_ctx,
1184 )
1185 }
1186
1187 fn sync_element_bounds_cache_after_layout(&mut self, app: &mut H) {
1188 let Some(window) = self.window else {
1189 return;
1190 };
1191
1192 let nodes = &self.nodes;
1193 let scratch_element_nodes = &mut self.scratch_element_nodes;
1194
1195 crate::elements::with_window_state(app, window, |st| {
1196 st.element_nodes_copy_into(scratch_element_nodes);
1197 for &(element, node) in scratch_element_nodes.iter() {
1198 let Some(rect) = nodes.get(node).map(|n| n.bounds) else {
1199 continue;
1200 };
1201 st.record_bounds(element, rect);
1202 }
1203 });
1204 }
1205
1206 fn finish_final_layout_frame(&mut self, app: &mut H) {
1207 self.layout_engine.end_frame();
1208 if let Some(window) = self.window {
1209 let frame_id = app.frame_id();
1210 crate::elements::with_window_state(app, window, |st| {
1211 st.clear_stale_interaction_targets_for_frame(frame_id);
1212 st.sync_active_text_selection_node(|element, seeded| {
1213 self.resolve_live_attached_node_for_element_seeded(element, seeded)
1214 });
1215 st.sync_interaction_target_nodes(|element, seeded| {
1216 self.resolve_live_attached_node_for_element_seeded(element, seeded)
1217 });
1218 });
1219 }
1220
1221 self.sync_element_bounds_cache_after_layout(app);
1225 self.validate_subtree_layout_dirty_counts_if_enabled();
1226 if !self.interactive_resize_active() {
1227 self.interactive_resize_needs_full_rebuild = false;
1228 }
1229 self.last_layout_frame_id = Some(app.frame_id());
1230 self.refine_pending_window_runtime_snapshots_after_layout(app);
1231 }
1232
1233 pub fn measure_in(
1234 &mut self,
1235 app: &mut H,
1236 services: &mut dyn UiServices,
1237 node: NodeId,
1238 constraints: LayoutConstraints,
1239 scale_factor: f32,
1240 ) -> Size {
1241 self.measure_node(app, services, node, constraints, scale_factor)
1242 }
1243
1244 pub(crate) fn node_is_attached_to_layer_tree(&self, node: NodeId) -> bool {
1245 self.node_root(node)
1246 .is_some_and(|root| self.root_to_layer.contains_key(&root))
1247 }
1248
1249 fn any_attached_layout_invalidations(&self) -> bool {
1250 self.nodes
1251 .iter()
1252 .any(|(id, node)| node.invalidation.layout && self.node_is_attached_to_layer_tree(id))
1253 }
1254
1255 fn prune_detached_layout_followups(&mut self) {
1256 let retained_dirty_cache_roots: std::collections::HashSet<NodeId> = self
1257 .dirty_cache_roots
1258 .iter()
1259 .copied()
1260 .filter(|&root| self.node_is_attached_to_layer_tree(root))
1261 .collect();
1262 self.dirty_cache_roots = retained_dirty_cache_roots;
1263 self.dirty_cache_root_reasons
1264 .retain(|root, _| self.dirty_cache_roots.contains(root));
1265 self.pending_barrier_relayouts = self
1266 .pending_barrier_relayouts
1267 .iter()
1268 .copied()
1269 .filter(|&root| self.node_is_attached_to_layer_tree(root))
1270 .collect();
1271 }
1272
1273 fn begin_layout_engine_frame(&mut self, app: &mut H) {
1274 self.layout_engine.begin_frame(app.frame_id());
1275 self.viewport_roots.clear();
1276 }
1277
1278 fn mark_layout_engine_seen_subtree_from_ui_children(
1279 &mut self,
1280 engine: &mut crate::layout_engine::TaffyLayoutEngine,
1281 root: NodeId,
1282 ) {
1283 if engine.layout_id_for_node(root).is_none() {
1284 return;
1285 }
1286
1287 self.scratch_node_stack.clear();
1288 self.scratch_node_stack.push(root);
1289 while let Some(node) = self.scratch_node_stack.pop() {
1290 engine.mark_seen_if_present(node);
1291 if let Some(entry) = self.nodes.get(node) {
1292 for &child in &entry.children {
1293 self.scratch_node_stack.push(child);
1294 }
1295 }
1296 }
1297 }
1298
1299 fn layout_contained_view_cache_roots_if_needed(
1300 &mut self,
1301 app: &mut H,
1302 services: &mut dyn UiServices,
1303 scale_factor: f32,
1304 pass_kind: LayoutPassKind,
1305 viewport_cursor: &mut usize,
1306 ) {
1307 if !self.view_cache_active() {
1308 return;
1309 }
1310
1311 let mut candidates: Vec<NodeId> = Vec::with_capacity(16);
1317 for &id in &self.dirty_cache_roots {
1318 let Some(node) = self.nodes.get(id) else {
1319 continue;
1320 };
1321 if !node.view_cache.enabled || !node.view_cache.contained_layout {
1322 continue;
1323 }
1324 if !node.invalidation.layout {
1325 continue;
1326 }
1327 candidates.push(id);
1328 }
1329
1330 if candidates.is_empty() {
1331 return;
1332 }
1333
1334 let candidate_set: std::collections::HashSet<NodeId> = candidates.iter().copied().collect();
1335 let mut scheduled_followups: std::collections::HashSet<NodeId> =
1336 std::collections::HashSet::new();
1337
1338 let mut targets: Vec<(NodeId, Rect)> = Vec::with_capacity(candidates.len());
1339 for id in candidates {
1340 let mut skip = false;
1341 let mut parent = self.nodes.get(id).and_then(|n| n.parent);
1342 while let Some(p) = parent {
1343 if candidate_set.contains(&p) {
1344 skip = true;
1345 break;
1346 }
1347 parent = self.nodes.get(p).and_then(|n| n.parent);
1348 }
1349 if skip {
1350 continue;
1351 }
1352
1353 let Some(node) = self.nodes.get(id) else {
1354 continue;
1355 };
1356
1357 let mut bounds = node.bounds;
1365 if (bounds.size == Size::default() || bounds.origin == Point::default())
1366 && let Some(parent) = node.parent
1367 && let Some(parent_bounds) = self.nodes.get(parent).map(|n| n.bounds)
1368 && let Some(local) = self.layout_engine_child_local_rect(parent, id)
1369 {
1370 let resolved = Rect::new(
1371 Point::new(
1372 Px(parent_bounds.origin.x.0 + local.origin.x.0),
1373 Px(parent_bounds.origin.y.0 + local.origin.y.0),
1374 ),
1375 local.size,
1376 );
1377 if resolved.size != Size::default() {
1378 bounds = resolved;
1379 }
1380 }
1381
1382 targets.push((id, bounds));
1383 }
1384
1385 self.solve_barrier_flow_roots_if_needed(app, services, &targets, scale_factor);
1389
1390 for (root, bounds) in targets {
1391 if self.debug_enabled {
1392 self.debug_stats.view_cache_contained_relayouts = self
1393 .debug_stats
1394 .view_cache_contained_relayouts
1395 .saturating_add(1);
1396 self.debug_view_cache_contained_relayout_roots.push(root);
1397 }
1398 let _ = self.layout_in_with_pass_kind(
1399 app,
1400 services,
1401 root,
1402 bounds,
1403 scale_factor,
1404 pass_kind,
1405 crate::layout::overflow::LayoutOverflowContext::default(),
1406 );
1407 self.flush_viewport_roots_after_root(
1408 app,
1409 services,
1410 scale_factor,
1411 pass_kind,
1412 viewport_cursor,
1413 );
1414 let layout_transition = self.nodes.get_mut(root).map(|node| {
1415 let prev = node.invalidation;
1416 let layout_before = node.invalidation.layout;
1417 node.invalidation.layout = false;
1418 let next = node.invalidation;
1419 let layout_after = node.invalidation.layout;
1420 (prev, next, layout_before, layout_after)
1421 });
1422 if let Some((prev, next, layout_before, layout_after)) = layout_transition
1423 && layout_before != layout_after
1424 {
1425 record_layout_invalidation_transition(
1426 &mut self.layout_invalidations_count,
1427 layout_before,
1428 layout_after,
1429 );
1430 self.note_layout_invalidation_transition_for_subtree_aggregation(
1431 root,
1432 layout_before,
1433 layout_after,
1434 );
1435 self.update_invalidation_counters(prev, next);
1436 }
1437 self.clear_cache_root_dirty_tracking_if_clean(root);
1442
1443 let mut current = self.nodes.get(root).and_then(|n| n.parent);
1448 while let Some(id) = current {
1449 let can_scroll = self
1450 .nodes
1451 .get(id)
1452 .and_then(|n| n.widget.as_ref())
1453 .is_some_and(|w| w.can_scroll_descendant_into_view());
1454 if can_scroll {
1455 if scheduled_followups.insert(id) {
1456 self.schedule_barrier_relayout_with_source_and_detail(
1457 id,
1458 UiDebugInvalidationSource::Other,
1459 UiDebugInvalidationDetail::Unknown,
1460 );
1461 }
1462 break;
1463 }
1464 current = self.nodes.get(id).and_then(|n| n.parent);
1465 }
1466 }
1467
1468 if !scheduled_followups.is_empty() {
1469 self.layout_pending_barrier_relayouts_if_needed(
1470 app,
1471 services,
1472 scale_factor,
1473 pass_kind,
1474 viewport_cursor,
1475 );
1476 }
1477 }
1478
1479 fn request_build_window_roots_if_final(
1480 &mut self,
1481 app: &mut H,
1482 services: &mut dyn UiServices,
1483 roots: &[NodeId],
1484 bounds: Rect,
1485 scale_factor: f32,
1486 pass_kind: LayoutPassKind,
1487 ) {
1488 if pass_kind != LayoutPassKind::Final {
1489 return;
1490 }
1491
1492 let Some(window) = self.window else {
1493 return;
1494 };
1495
1496 let runtime_cfg = crate::runtime_config::ui_runtime_config();
1497 let profile_layout = runtime_cfg.layout_profile;
1498 let total_started = profile_layout.then(Instant::now);
1499
1500 let sf = scale_factor;
1501 let available = LayoutSize::new(
1502 AvailableSpace::Definite(bounds.size.width),
1503 AvailableSpace::Definite(bounds.size.height),
1504 );
1505
1506 let mut engine = self.take_layout_engine();
1507 engine.set_measure_profiling_enabled(self.debug_enabled && profile_layout);
1508
1509 let phase1_started = profile_layout.then(Instant::now);
1510 let reuse_cached_flow = self.interactive_resize_active();
1511 let allow_translation_only_skip = runtime_cfg.layout_skip_request_build_translation_only;
1512 let force_post_resize_rebuild = self.interactive_resize_requires_full_rebuild();
1513 if force_post_resize_rebuild {
1514 for &root in roots {
1515 self.mark_subtree_invalidation_local(root, Invalidation::Layout);
1516 }
1517 }
1518 for &root in roots {
1520 let Some((
1521 has_element,
1522 layout_invalidated,
1523 subtree_layout_dirty,
1524 prev_bounds,
1525 measured,
1526 )) = self.nodes.get(root).map(|node| {
1527 (
1528 node.element.is_some(),
1529 node.invalidation.layout,
1530 self.node_subtree_layout_dirty(root),
1531 node.bounds,
1532 node.measured_size,
1533 )
1534 })
1535 else {
1536 continue;
1537 };
1538 if !has_element {
1539 continue;
1540 }
1541
1542 let needs_layout = layout_invalidated || prev_bounds != bounds;
1543 let is_translation_only = allow_translation_only_skip
1544 && !layout_invalidated
1545 && prev_bounds.size == bounds.size
1546 && prev_bounds.origin != bounds.origin
1547 && measured != Size::default();
1548
1549 if engine.layout_id_for_node(root).is_some() && (!needs_layout || is_translation_only) {
1550 self.mark_layout_engine_seen_subtree_from_ui_children(&mut engine, root);
1551 continue;
1552 }
1553 if reuse_cached_flow
1554 && engine.layout_id_for_node(root).is_some()
1555 && !layout_invalidated
1556 && !subtree_layout_dirty
1557 {
1558 engine.set_viewport_root_override_size(root, bounds.size, sf);
1559 self.note_interactive_resize_cached_flow_reuse();
1560 self.mark_layout_engine_seen_subtree_from_ui_children(&mut engine, root);
1561 } else {
1562 build_viewport_flow_subtree(
1563 &mut engine,
1564 app,
1565 &*self,
1566 window,
1567 sf,
1568 root,
1569 bounds.size,
1570 );
1571 }
1572 }
1573 let phase1_elapsed = phase1_started.map(|s| s.elapsed());
1574
1575 let phase2_started = profile_layout.then(Instant::now);
1576 let mut pending_solves: Vec<(NodeId, LayoutSize<AvailableSpace>)> = Vec::new();
1582 for &root in roots {
1583 let (has_element, needs_layout, is_translation_only) = match self.nodes.get(root) {
1584 Some(node) => {
1585 let has_element = node.element.is_some();
1586 let needs_layout = node.invalidation.layout || node.bounds != bounds;
1587 let is_translation_only = !node.invalidation.layout
1588 && node.bounds.size == bounds.size
1589 && node.bounds.origin != bounds.origin
1590 && node.measured_size != Size::default();
1591 (has_element, needs_layout, is_translation_only)
1592 }
1593 None => continue,
1594 };
1595
1596 if !has_element || !needs_layout || is_translation_only {
1597 continue;
1598 }
1599
1600 pending_solves.push((root, available));
1601 }
1602
1603 if !pending_solves.is_empty() {
1604 let solves_before = engine.solve_count();
1605 let solve_time_before = engine.last_solve_time();
1606 engine.compute_independent_roots_with_measure_if_needed(&pending_solves, sf, |n, c| {
1607 self.measure_in(app, services, n, c, sf)
1608 });
1609
1610 if self.debug_enabled && engine.solve_count() > solves_before {
1611 let elapsed = engine.last_solve_time().saturating_sub(solve_time_before);
1612 let top_measures = engine
1613 .last_solve_measure_hotspots()
1614 .iter()
1615 .map(|h| {
1616 let mut element: Option<GlobalElementId> = None;
1617 let mut element_kind: Option<&'static str> = None;
1618 if let Some(record) =
1619 crate::declarative::frame::element_record_for_node(app, window, h.node)
1620 {
1621 element = Some(record.element);
1622 element_kind = Some(record.instance.kind_name());
1623 }
1624 let top_children = self
1625 .debug_take_top_measure_children(h.node, 3)
1626 .into_iter()
1627 .map(|(child, r)| {
1628 let mut child_element: Option<GlobalElementId> = None;
1629 let mut child_kind: Option<&'static str> = None;
1630 if let Some(record) =
1631 crate::declarative::frame::element_record_for_node(
1632 app, window, child,
1633 )
1634 {
1635 child_element = Some(record.element);
1636 child_kind = Some(record.instance.kind_name());
1637 }
1638 super::UiDebugLayoutEngineMeasureChildHotspot {
1639 child,
1640 measure_time: r.total_time,
1641 calls: r.calls,
1642 element: child_element,
1643 element_kind: child_kind,
1644 }
1645 })
1646 .collect();
1647 super::UiDebugLayoutEngineMeasureHotspot {
1648 node: h.node,
1649 measure_time: h.total_time,
1650 calls: h.calls,
1651 cache_hits: h.cache_hits,
1652 element,
1653 element_kind,
1654 top_children,
1655 }
1656 })
1657 .collect();
1658 let solve_root = engine
1659 .last_solve_root()
1660 .unwrap_or_else(|| pending_solves[0].0);
1661 let (root_element, root_element_kind, root_element_path) =
1662 self.debug_resolve_layout_solve_root_label(app, window, solve_root);
1663
1664 self.debug_record_layout_engine_solve(
1665 solve_root,
1666 root_element,
1667 root_element_kind,
1668 root_element_path,
1669 elapsed,
1670 engine.last_solve_measure_calls(),
1671 engine.last_solve_measure_cache_hits(),
1672 engine.last_solve_measure_time(),
1673 top_measures,
1674 );
1675 self.debug_measure_children.clear();
1676 }
1677
1678 for &(root, _available) in &pending_solves {
1679 self.maybe_dump_taffy_subtree(app, window, &engine, root, bounds, sf);
1680 }
1681 }
1682 let phase2_elapsed = phase2_started.map(|s| s.elapsed());
1683
1684 self.put_layout_engine(engine);
1685
1686 if let Some(started) = total_started {
1687 let total = started.elapsed();
1688 tracing::info!(
1689 window = ?window,
1690 roots = roots.len(),
1691 total_ms = total.as_millis(),
1692 phase1_ms = phase1_elapsed.map(|d| d.as_millis()),
1693 phase2_ms = phase2_elapsed.map(|d| d.as_millis()),
1694 "layout root request/build profile"
1695 );
1696 }
1697 }
1698
1699 fn flush_viewport_roots_after_root(
1700 &mut self,
1701 app: &mut H,
1702 services: &mut dyn UiServices,
1703 scale_factor: f32,
1704 pass_kind: LayoutPassKind,
1705 viewport_cursor: &mut usize,
1706 ) {
1707 let sf = scale_factor;
1708 let window = self.window;
1709
1710 while *viewport_cursor < self.viewport_roots.len() {
1711 let batch_start = *viewport_cursor;
1712 let batch_end = self.viewport_roots.len();
1713 let force_post_resize_rebuild = pass_kind == LayoutPassKind::Final
1714 && self.interactive_resize_requires_full_rebuild();
1715
1716 if force_post_resize_rebuild {
1717 let roots_to_invalidate: Vec<NodeId> = self.viewport_roots[batch_start..batch_end]
1718 .iter()
1719 .map(|(root, _)| *root)
1720 .collect();
1721 for root in roots_to_invalidate {
1722 self.mark_subtree_invalidation_local(root, Invalidation::Layout);
1723 }
1724 }
1725
1726 struct ViewportWorkItem {
1727 root: NodeId,
1728 bounds: Rect,
1729 needs_layout: bool,
1730 is_translation_only: bool,
1731 layout_invalidated: bool,
1732 subtree_layout_dirty: bool,
1733 }
1734
1735 let mut batch: Vec<ViewportWorkItem> = Vec::with_capacity(batch_end - batch_start);
1736 for &(root, bounds) in &self.viewport_roots[batch_start..batch_end] {
1737 let Some((prev_bounds, invalidated, measured)) = self
1738 .nodes
1739 .get(root)
1740 .map(|n| (n.bounds, n.invalidation.layout, n.measured_size))
1741 else {
1742 continue;
1743 };
1744
1745 let needs_layout = invalidated || prev_bounds != bounds;
1746 let is_translation_only = !invalidated
1747 && prev_bounds.size == bounds.size
1748 && prev_bounds.origin != bounds.origin
1749 && measured != Size::default();
1750
1751 batch.push(ViewportWorkItem {
1752 root,
1753 bounds,
1754 needs_layout,
1755 is_translation_only,
1756 layout_invalidated: invalidated,
1757 subtree_layout_dirty: self.node_subtree_layout_dirty(root),
1758 });
1759 }
1760
1761 if pass_kind == LayoutPassKind::Final
1762 && let Some(window) = window
1763 {
1764 let mut engine = self.take_layout_engine();
1765 engine.set_measure_profiling_enabled(
1766 self.debug_enabled && crate::runtime_config::ui_runtime_config().layout_profile,
1767 );
1768
1769 let reuse_cached_flow = self.interactive_resize_active();
1770
1771 for item in &batch {
1774 if self
1775 .nodes
1776 .get(item.root)
1777 .is_none_or(|node| node.element.is_none())
1778 {
1779 continue;
1780 }
1781 if engine.layout_id_for_node(item.root).is_some()
1782 && (!item.needs_layout || item.is_translation_only)
1783 {
1784 self.mark_layout_engine_seen_subtree_from_ui_children(
1785 &mut engine,
1786 item.root,
1787 );
1788 continue;
1789 }
1790 if reuse_cached_flow
1791 && engine.layout_id_for_node(item.root).is_some()
1792 && !item.layout_invalidated
1793 && !item.subtree_layout_dirty
1794 {
1795 engine.set_viewport_root_override_size(item.root, item.bounds.size, sf);
1796 self.note_interactive_resize_cached_flow_reuse();
1797 self.mark_layout_engine_seen_subtree_from_ui_children(
1798 &mut engine,
1799 item.root,
1800 );
1801 } else {
1802 build_viewport_flow_subtree(
1803 &mut engine,
1804 app,
1805 &*self,
1806 window,
1807 sf,
1808 item.root,
1809 item.bounds.size,
1810 );
1811 }
1812 }
1813
1814 let mut pending_solves: Vec<(NodeId, LayoutSize<AvailableSpace>)> = Vec::new();
1816 for item in &batch {
1817 if !item.needs_layout || item.is_translation_only {
1818 continue;
1819 }
1820 pending_solves.push((
1821 item.root,
1822 LayoutSize::new(
1823 AvailableSpace::Definite(item.bounds.size.width),
1824 AvailableSpace::Definite(item.bounds.size.height),
1825 ),
1826 ));
1827 }
1828
1829 if !pending_solves.is_empty() {
1830 let solves_before = engine.solve_count();
1831 let solve_time_before = engine.last_solve_time();
1832 engine.compute_independent_roots_with_measure_if_needed(
1833 &pending_solves,
1834 sf,
1835 |n, c| self.measure_in(app, services, n, c, sf),
1836 );
1837
1838 if self.debug_enabled && engine.solve_count() > solves_before {
1839 let elapsed = engine.last_solve_time().saturating_sub(solve_time_before);
1840 let top_measures = engine
1841 .last_solve_measure_hotspots()
1842 .iter()
1843 .map(|h| {
1844 let mut element: Option<GlobalElementId> = None;
1845 let mut element_kind: Option<&'static str> = None;
1846 if let Some(record) =
1847 crate::declarative::frame::element_record_for_node(
1848 app, window, h.node,
1849 )
1850 {
1851 element = Some(record.element);
1852 element_kind = Some(record.instance.kind_name());
1853 }
1854 let top_children = self
1855 .debug_take_top_measure_children(h.node, 3)
1856 .into_iter()
1857 .map(|(child, r)| {
1858 let mut child_element: Option<GlobalElementId> = None;
1859 let mut child_kind: Option<&'static str> = None;
1860 if let Some(record) =
1861 crate::declarative::frame::element_record_for_node(
1862 app, window, child,
1863 )
1864 {
1865 child_element = Some(record.element);
1866 child_kind = Some(record.instance.kind_name());
1867 }
1868 super::UiDebugLayoutEngineMeasureChildHotspot {
1869 child,
1870 measure_time: r.total_time,
1871 calls: r.calls,
1872 element: child_element,
1873 element_kind: child_kind,
1874 }
1875 })
1876 .collect();
1877 super::UiDebugLayoutEngineMeasureHotspot {
1878 node: h.node,
1879 measure_time: h.total_time,
1880 calls: h.calls,
1881 cache_hits: h.cache_hits,
1882 element,
1883 element_kind,
1884 top_children,
1885 }
1886 })
1887 .collect();
1888 let solve_root = engine
1889 .last_solve_root()
1890 .unwrap_or_else(|| pending_solves[0].0);
1891 let (root_element, root_element_kind, root_element_path) =
1892 self.debug_resolve_layout_solve_root_label(app, window, solve_root);
1893
1894 self.debug_record_layout_engine_solve(
1895 solve_root,
1896 root_element,
1897 root_element_kind,
1898 root_element_path,
1899 elapsed,
1900 engine.last_solve_measure_calls(),
1901 engine.last_solve_measure_cache_hits(),
1902 engine.last_solve_measure_time(),
1903 top_measures,
1904 );
1905 self.debug_measure_children.clear();
1906 }
1907
1908 for item in &batch {
1909 if !item.needs_layout || item.is_translation_only {
1910 continue;
1911 }
1912 self.maybe_dump_taffy_subtree(
1913 app,
1914 window,
1915 &engine,
1916 item.root,
1917 item.bounds,
1918 sf,
1919 );
1920 }
1921 }
1922
1923 self.put_layout_engine(engine);
1924 }
1925
1926 for item in &batch {
1930 if !item.needs_layout {
1931 continue;
1932 }
1933
1934 let _ = self.layout_in_with_pass_kind(
1935 app,
1936 services,
1937 item.root,
1938 item.bounds,
1939 scale_factor,
1940 LayoutPassKind::Final,
1941 crate::layout::overflow::LayoutOverflowContext::default(),
1942 );
1943 }
1944
1945 *viewport_cursor = batch_end;
1946 }
1947 }
1948}