1use std::any::{Any, TypeId};
2use std::collections::{HashMap, HashSet};
3#[cfg(feature = "diagnostics")]
4use std::sync::Mutex;
5use std::sync::{
6 Arc,
7 atomic::{AtomicUsize, Ordering},
8};
9
10use fret_core::{
11 AppWindowId, Color, ColorScheme, ContrastPreference, Edges, ForcedColorsMode, NodeId,
12 PointerType, Rect,
13};
14use fret_runtime::{FrameId, ModelId, TimerToken};
15#[cfg(feature = "diagnostics")]
16use slotmap::Key as _;
17#[cfg(feature = "diagnostics")]
18use std::sync::Arc as StdArc;
19
20use crate::element::AnyElement;
21#[cfg(feature = "diagnostics")]
22use crate::overlay_placement::{AnchoredPanelLayoutTrace, Side};
23use crate::widget::Invalidation;
24
25use super::GlobalElementId;
26use super::hash::stable_hash;
27
28#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
29pub(crate) enum TimerTarget {
30 Element(GlobalElementId),
31 Node(NodeId),
32}
33
34#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
35pub(crate) enum EnvironmentQueryKey {
36 ViewportSize,
37 ScaleFactor,
38 ColorScheme,
39 PrefersReducedMotion,
40 TextScaleFactor,
41 PrefersReducedTransparency,
42 AccentColor,
43 PrefersContrast,
44 ForcedColorsMode,
45 PrimaryPointerType,
46 SafeAreaInsets,
47 OcclusionInsets,
48}
49
50#[cfg(feature = "diagnostics")]
51#[derive(Debug, Clone)]
52pub struct WindowElementDiagnosticsSnapshot {
53 pub focused_element: Option<GlobalElementId>,
54 pub focused_element_node: Option<NodeId>,
55 pub focused_element_bounds: Option<Rect>,
56 pub focused_element_visual_bounds: Option<Rect>,
57 pub active_text_selection: Option<(GlobalElementId, GlobalElementId)>,
58 pub hovered_pressable: Option<GlobalElementId>,
59 pub hovered_pressable_node: Option<NodeId>,
60 pub hovered_pressable_bounds: Option<Rect>,
61 pub hovered_pressable_visual_bounds: Option<Rect>,
62 pub pressed_pressable: Option<GlobalElementId>,
63 pub pressed_pressable_node: Option<NodeId>,
64 pub pressed_pressable_bounds: Option<Rect>,
65 pub pressed_pressable_visual_bounds: Option<Rect>,
66 pub hovered_hover_region: Option<GlobalElementId>,
67 pub wants_continuous_frames: bool,
68 pub continuous_frame_leases: Vec<ContinuousFrameLeaseDiagnosticsSnapshot>,
69 pub animation_frame_request_roots: Vec<AnimationFrameRequestRootDiagnosticsSnapshot>,
70 pub observed_models: Vec<(GlobalElementId, Vec<(u64, Invalidation)>)>,
71 pub observed_globals: Vec<(GlobalElementId, Vec<(String, Invalidation)>)>,
72 pub observed_layout_queries: Vec<ElementObservedLayoutQueriesDiagnosticsSnapshot>,
73 pub layout_query_regions: Vec<LayoutQueryRegionDiagnosticsSnapshot>,
74 pub environment: EnvironmentQueryDiagnosticsSnapshot,
75 pub observed_environment: Vec<ElementObservedEnvironmentDiagnosticsSnapshot>,
76 pub view_cache_reuse_roots: Vec<GlobalElementId>,
77 pub view_cache_reuse_root_element_counts: Vec<(GlobalElementId, u32)>,
78 pub view_cache_reuse_root_element_samples: Vec<ViewCacheReuseRootElementsSample>,
79 pub rendered_state_entries: u64,
80 pub next_state_entries: u64,
81 pub lag_state_frames: u64,
82 pub lag_state_entries_total: u64,
83 pub state_entries_total: u64,
84 pub nodes_count: u64,
85 pub bounds_entries_total: u64,
86 pub timer_targets_count: u64,
87 pub transient_events_count: u64,
88 pub view_cache_state_key_roots_count: u64,
89 pub view_cache_state_key_entries_total: u64,
90 pub view_cache_element_roots_count: u64,
91 pub view_cache_element_entries_total: u64,
92 pub view_cache_key_mismatch_roots_count: u64,
93 pub scratch_element_children_vec_pool_len: u64,
94 pub scratch_element_children_vec_pool_capacity_total: u64,
95 pub scratch_element_children_vec_pool_bytes_estimate_total: u64,
96 pub retained_keep_alive_roots_len: u32,
100 pub retained_keep_alive_roots_head: Vec<NodeId>,
101 pub retained_keep_alive_roots_tail: Vec<NodeId>,
102 pub node_entry_root_overwrites: Vec<NodeEntryRootOverwrite>,
103 pub overlay_placement: Vec<OverlayPlacementDiagnosticsRecord>,
104}
105
106#[cfg(feature = "diagnostics")]
107#[derive(Debug, Clone)]
108pub struct ContinuousFrameLeaseDiagnosticsSnapshot {
109 pub element: GlobalElementId,
110 pub count: u32,
111 pub debug_path: Option<String>,
112}
113
114#[cfg(feature = "diagnostics")]
115#[derive(Debug, Clone)]
116pub struct AnimationFrameRequestRootDiagnosticsSnapshot {
117 pub element: GlobalElementId,
118 pub debug_path: Option<String>,
119}
120
121#[cfg(feature = "diagnostics")]
122#[allow(clippy::large_enum_variant)]
123#[derive(Debug, Clone)]
124pub enum OverlayPlacementDiagnosticsRecord {
125 AnchoredPanel(OverlayAnchoredPanelPlacementDiagnosticsRecord),
127 PlacedRect(OverlayPlacedRectDiagnosticsRecord),
129}
130
131#[cfg(feature = "diagnostics")]
132#[derive(Debug, Clone)]
133pub struct OverlayAnchoredPanelPlacementDiagnosticsRecord {
134 pub frame_id: FrameId,
135 pub overlay_root_name: Option<StdArc<str>>,
136 pub anchor_element: Option<GlobalElementId>,
137 pub content_element: Option<GlobalElementId>,
138 pub trace: AnchoredPanelLayoutTrace,
139}
140
141#[cfg(feature = "diagnostics")]
142#[derive(Debug, Clone)]
143pub struct OverlayPlacedRectDiagnosticsRecord {
144 pub frame_id: FrameId,
145 pub overlay_root_name: Option<StdArc<str>>,
146 pub anchor_element: Option<GlobalElementId>,
147 pub content_element: Option<GlobalElementId>,
148 pub outer: Rect,
149 pub anchor: Rect,
150 pub placed: Rect,
151 pub side: Option<Side>,
152}
153
154#[cfg(feature = "diagnostics")]
155#[derive(Debug, Clone)]
156pub struct LayoutQueryRegionDiagnosticsSnapshot {
157 pub region: GlobalElementId,
158 pub name: Option<StdArc<str>>,
159 pub revision: u64,
160 pub changed_this_frame: bool,
161 pub committed_bounds: Option<Rect>,
163 pub current_bounds: Option<Rect>,
165}
166
167#[cfg(feature = "diagnostics")]
168#[derive(Debug, Clone)]
169pub struct ElementObservedLayoutQueriesDiagnosticsSnapshot {
170 pub element: GlobalElementId,
171 pub deps_fingerprint: u64,
172 pub regions: Vec<ObservedLayoutQueryRegionDiagnosticsSnapshot>,
173}
174
175#[cfg(feature = "diagnostics")]
176#[derive(Debug, Clone)]
177pub struct ObservedLayoutQueryRegionDiagnosticsSnapshot {
178 pub region: GlobalElementId,
179 pub invalidation: Invalidation,
180 pub region_revision: u64,
181 pub region_changed_this_frame: bool,
182 pub region_name: Option<StdArc<str>>,
183 pub region_committed_bounds: Option<Rect>,
184}
185
186#[cfg(feature = "diagnostics")]
187#[derive(Debug, Clone)]
188pub struct EnvironmentQueryDiagnosticsSnapshot {
189 pub viewport_bounds: Rect,
190 pub scale_factor: f32,
191 pub color_scheme: Option<ColorScheme>,
192 pub prefers_reduced_motion: Option<bool>,
193 pub text_scale_factor: Option<f32>,
194 pub prefers_reduced_transparency: Option<bool>,
195 pub accent_color: Option<Color>,
196 pub contrast_preference: Option<ContrastPreference>,
197 pub forced_colors_mode: Option<ForcedColorsMode>,
198 pub primary_pointer_type: PointerType,
199 pub safe_area_insets: Option<Edges>,
200 pub occlusion_insets: Option<Edges>,
201}
202
203#[cfg(feature = "diagnostics")]
204#[derive(Debug, Clone)]
205pub struct ElementObservedEnvironmentDiagnosticsSnapshot {
206 pub element: GlobalElementId,
207 pub deps_fingerprint: u64,
208 pub keys: Vec<ObservedEnvironmentKeyDiagnosticsSnapshot>,
209}
210
211#[cfg(feature = "diagnostics")]
212#[derive(Debug, Clone)]
213pub struct ObservedEnvironmentKeyDiagnosticsSnapshot {
214 pub key: StdArc<str>,
215 pub invalidation: Invalidation,
216 pub key_revision: u64,
217 pub key_changed_this_frame: bool,
218}
219
220#[cfg(feature = "diagnostics")]
221#[derive(Debug, Clone)]
222pub struct ViewCacheReuseRootElementsSample {
223 pub root: GlobalElementId,
224 pub node: Option<NodeId>,
225 pub elements_len: u32,
226 pub elements_head: Vec<GlobalElementId>,
227 pub elements_tail: Vec<GlobalElementId>,
228}
229
230#[derive(Default)]
231pub struct ElementRuntime {
232 windows: HashMap<AppWindowId, WindowElementState>,
233 gc_lag_frames: u64,
234}
235
236impl ElementRuntime {
237 pub fn new() -> Self {
238 Self {
239 windows: HashMap::new(),
240 gc_lag_frames: 2,
241 }
242 }
243
244 pub fn gc_lag_frames(&self) -> u64 {
245 self.gc_lag_frames
246 }
247
248 pub fn set_gc_lag_frames(&mut self, frames: u64) {
249 self.gc_lag_frames = frames;
250 }
251
252 pub fn set_window_prefers_reduced_motion(
253 &mut self,
254 window: AppWindowId,
255 prefers_reduced_motion: Option<bool>,
256 ) {
257 self.for_window_mut(window)
258 .set_committed_prefers_reduced_motion(prefers_reduced_motion);
259 }
260
261 pub fn set_window_color_scheme(&mut self, window: AppWindowId, scheme: Option<ColorScheme>) {
262 self.for_window_mut(window)
263 .set_committed_color_scheme(scheme);
264 }
265
266 pub fn set_window_contrast_preference(
267 &mut self,
268 window: AppWindowId,
269 value: Option<ContrastPreference>,
270 ) {
271 self.for_window_mut(window)
272 .set_committed_contrast_preference(value);
273 }
274
275 pub fn set_window_forced_colors_mode(
276 &mut self,
277 window: AppWindowId,
278 value: Option<ForcedColorsMode>,
279 ) {
280 self.for_window_mut(window)
281 .set_committed_forced_colors_mode(value);
282 }
283
284 pub fn set_window_primary_pointer_type(
285 &mut self,
286 window: AppWindowId,
287 pointer_type: PointerType,
288 ) {
289 self.for_window_mut(window)
290 .record_committed_primary_pointer_type(pointer_type);
291 }
292
293 pub fn set_window_safe_area_insets(&mut self, window: AppWindowId, insets: Option<Edges>) {
294 self.for_window_mut(window)
295 .record_committed_safe_area_insets(insets);
296 }
297
298 pub fn set_window_occlusion_insets(&mut self, window: AppWindowId, insets: Option<Edges>) {
299 self.for_window_mut(window)
300 .record_committed_occlusion_insets(insets);
301 }
302
303 pub fn for_window_mut(&mut self, window: AppWindowId) -> &mut WindowElementState {
304 self.windows.entry(window).or_default()
305 }
306
307 pub(crate) fn for_window(&self, window: AppWindowId) -> Option<&WindowElementState> {
308 self.windows.get(&window)
309 }
310
311 pub fn prepare_window_for_frame(&mut self, window: AppWindowId, frame_id: FrameId) {
312 let lag = self.gc_lag_frames;
313 self.for_window_mut(window).prepare_for_frame(frame_id, lag);
314 }
315
316 #[cfg(feature = "diagnostics")]
317 pub fn diagnostics_snapshot(
318 &self,
319 window: AppWindowId,
320 ) -> Option<WindowElementDiagnosticsSnapshot> {
321 let state = self.windows.get(&window)?;
322 Some(state.diagnostics_snapshot())
323 }
324
325 #[cfg(feature = "diagnostics")]
326 pub fn element_for_node(&self, window: AppWindowId, node: NodeId) -> Option<GlobalElementId> {
327 let state = self.windows.get(&window)?;
328 state.element_for_node(node)
329 }
330
331 #[cfg(feature = "diagnostics")]
332 pub fn node_for_element(
333 &self,
334 window: AppWindowId,
335 element: GlobalElementId,
336 ) -> Option<NodeId> {
337 let state = self.windows.get(&window)?;
338 state.node_entry(element).map(|e| e.node)
339 }
340
341 #[cfg(any(test, feature = "diagnostics"))]
342 pub fn selectable_text_interactive_span_bounds_for_node(
343 &self,
344 window: AppWindowId,
345 node: NodeId,
346 ) -> Option<Vec<crate::element::SelectableTextInteractiveSpanBounds>> {
347 let state = self.windows.get(&window)?;
348 let element = state.element_for_node(node)?;
349 self.selectable_text_interactive_span_bounds_for_element(window, element)
350 }
351
352 #[cfg(any(test, feature = "diagnostics"))]
353 pub fn selectable_text_interactive_span_bounds_for_element(
354 &self,
355 window: AppWindowId,
356 element: GlobalElementId,
357 ) -> Option<Vec<crate::element::SelectableTextInteractiveSpanBounds>> {
358 let state = self.windows.get(&window)?;
359 let key = (element, TypeId::of::<crate::element::SelectableTextState>());
360
361 let boxed = state
362 .next_state
363 .get(&key)
364 .or_else(|| state.rendered_state.get(&key))
365 .or_else(|| state.lag_state.iter().rev().find_map(|m| m.get(&key)))?;
366
367 let state = boxed.downcast_ref::<crate::element::SelectableTextState>()?;
368 Some(state.interactive_span_bounds.clone())
369 }
370
371 #[cfg(feature = "diagnostics")]
372 pub fn debug_path_for_element(
373 &self,
374 window: AppWindowId,
375 element: GlobalElementId,
376 ) -> Option<String> {
377 let state = self.windows.get(&window)?;
378 state.debug_path_for_element(element)
379 }
380}
381
382#[derive(Default)]
383pub struct WindowElementState {
384 pub(super) rendered_state: HashMap<(GlobalElementId, TypeId), Box<dyn Any>>,
385 pub(super) next_state: HashMap<(GlobalElementId, TypeId), Box<dyn Any>>,
386 pub(super) lag_state: Vec<HashMap<(GlobalElementId, TypeId), Box<dyn Any>>>,
387 pub(super) view_cache_state_keys_rendered:
388 HashMap<GlobalElementId, Vec<(GlobalElementId, TypeId)>>,
389 pub(super) view_cache_state_keys_next: HashMap<GlobalElementId, Vec<(GlobalElementId, TypeId)>>,
390 pub(super) view_cache_authoring_identities_rendered:
391 HashMap<GlobalElementId, Vec<GlobalElementId>>,
392 pub(super) view_cache_authoring_identities_next: HashMap<GlobalElementId, Vec<GlobalElementId>>,
393 view_cache_keys_rendered: HashMap<GlobalElementId, u64>,
394 view_cache_keys_next: HashMap<GlobalElementId, u64>,
395 view_cache_key_mismatch_roots: HashSet<GlobalElementId>,
396 pub(super) view_cache_elements_rendered: HashMap<GlobalElementId, Vec<GlobalElementId>>,
397 pub(super) view_cache_elements_next: HashMap<GlobalElementId, Vec<GlobalElementId>>,
398 pub(super) view_cache_reuse_roots: HashSet<GlobalElementId>,
399 view_cache_last_reused_frame: HashMap<GlobalElementId, FrameId>,
400 view_cache_transitioned_reuse_roots: HashSet<GlobalElementId>,
401 view_cache_stack: Vec<GlobalElementId>,
402 raf_notify_roots: HashSet<GlobalElementId>,
403 #[cfg(feature = "diagnostics")]
404 raf_notify_roots_debug: HashSet<GlobalElementId>,
405 pub(super) pending_retained_virtual_list_reconciles:
406 HashMap<GlobalElementId, crate::tree::UiDebugRetainedVirtualListReconcileKind>,
407 retained_virtual_list_keep_alive_roots: HashSet<NodeId>,
408 prepared_frame: FrameId,
409 #[cfg(any(test, feature = "diagnostics"))]
410 strict_ownership: bool,
411 pub(super) prev_unkeyed_fingerprints: HashMap<u64, Vec<u64>>,
412 pub(super) cur_unkeyed_fingerprints: HashMap<u64, Vec<u64>>,
413 pub(super) observed_models_rendered: HashMap<GlobalElementId, Vec<(ModelId, Invalidation)>>,
414 pub(super) observed_models_next: HashMap<GlobalElementId, Vec<(ModelId, Invalidation)>>,
415 pub(super) observed_globals_rendered: HashMap<GlobalElementId, Vec<(TypeId, Invalidation)>>,
416 pub(super) observed_globals_next: HashMap<GlobalElementId, Vec<(TypeId, Invalidation)>>,
417 pub(super) observed_layout_queries_rendered:
418 HashMap<GlobalElementId, Vec<(GlobalElementId, Invalidation)>>,
419 pub(super) observed_layout_queries_next:
420 HashMap<GlobalElementId, Vec<(GlobalElementId, Invalidation)>>,
421 pub(super) observed_environment_rendered:
422 HashMap<GlobalElementId, Vec<(EnvironmentQueryKey, Invalidation)>>,
423 pub(super) observed_environment_next:
424 HashMap<GlobalElementId, Vec<(EnvironmentQueryKey, Invalidation)>>,
425 layout_query_region_revisions: HashMap<GlobalElementId, u64>,
426 layout_query_regions_changed_this_frame: HashSet<GlobalElementId>,
427 environment_revisions: HashMap<EnvironmentQueryKey, u64>,
428 environment_changed_this_frame: HashSet<EnvironmentQueryKey>,
429 pub(super) timer_targets: HashMap<TimerToken, TimerTarget>,
430 scratch_view_cache_keep_alive_elements: HashSet<GlobalElementId>,
431 scratch_view_cache_keep_alive_visited_roots: HashSet<GlobalElementId>,
432 scratch_view_cache_keep_alive_stack: Vec<GlobalElementId>,
433 scratch_element_children_vec_pool: Vec<Vec<AnyElement>>,
434 element_children_vec_pool_reuses: u32,
435 element_children_vec_pool_misses: u32,
436 transient_events: HashMap<(GlobalElementId, u64), FrameId>,
437 authoring_identities_current_frame: HashSet<GlobalElementId>,
438 nodes: HashMap<GlobalElementId, NodeEntry>,
439 root_bounds: HashMap<GlobalElementId, Rect>,
440 prev_bounds: HashMap<GlobalElementId, Rect>,
441 cur_bounds: HashMap<GlobalElementId, Rect>,
442 prev_visual_bounds: HashMap<GlobalElementId, Rect>,
443 cur_visual_bounds: HashMap<GlobalElementId, Rect>,
444 committed_viewport_bounds: Rect,
445 committed_scale_factor: f32,
446 committed_color_scheme: Option<ColorScheme>,
447 committed_prefers_reduced_motion: Option<bool>,
448 committed_text_scale_factor: Option<f32>,
449 committed_prefers_reduced_transparency: Option<bool>,
450 committed_accent_color: Option<Color>,
451 committed_contrast_preference: Option<ContrastPreference>,
452 committed_forced_colors_mode: Option<ForcedColorsMode>,
453 committed_primary_pointer_type: Option<PointerType>,
454 committed_safe_area_insets: Option<Edges>,
455 committed_occlusion_insets: Option<Edges>,
456 pub(super) focused_element: Option<GlobalElementId>,
457 pub(super) active_text_selection: Option<ActiveTextSelection>,
458 pub(super) hovered_pressable: Option<GlobalElementId>,
459 pub(super) hovered_pressable_node: Option<NodeId>,
460 pub(super) hovered_pressable_raw: Option<GlobalElementId>,
461 pub(super) hovered_pressable_raw_node: Option<NodeId>,
462 pub(super) hovered_pressable_raw_below_barrier: Option<GlobalElementId>,
463 pub(super) hovered_pressable_raw_below_barrier_node: Option<NodeId>,
464 pub(super) pressed_pressable: Option<GlobalElementId>,
465 pub(super) pressed_pressable_node: Option<NodeId>,
466 pub(super) hovered_hover_region: Option<GlobalElementId>,
467 pub(super) hovered_hover_region_node: Option<NodeId>,
468 continuous_frames: Arc<AtomicUsize>,
469 #[cfg(feature = "diagnostics")]
470 continuous_frame_owners: Arc<Mutex<HashMap<GlobalElementId, u32>>>,
471 #[cfg(feature = "diagnostics")]
472 debug_identity: DebugIdentityRegistry,
473 #[cfg(feature = "diagnostics")]
474 debug_node_entry_root_overwrites: Vec<NodeEntryRootOverwrite>,
475 #[cfg(feature = "diagnostics")]
476 overlay_placement: Vec<OverlayPlacementDiagnosticsRecord>,
477}
478
479#[derive(Debug, Clone, Copy, PartialEq, Eq)]
480pub(crate) struct ActiveTextSelection {
481 pub root: GlobalElementId,
482 pub element: GlobalElementId,
483 pub node: NodeId,
484}
485
486#[derive(Debug, Clone, Copy)]
487pub(crate) struct NodeEntry {
488 pub node: NodeId,
489 pub last_seen_frame: FrameId,
490 pub root: GlobalElementId,
491}
492
493#[cfg(feature = "diagnostics")]
494#[derive(Debug, Clone, Copy)]
495pub struct NodeEntryRootOverwrite {
496 pub frame_id: FrameId,
497 pub element: GlobalElementId,
498 pub old_root: GlobalElementId,
499 pub new_root: GlobalElementId,
500 pub old_node: NodeId,
501 pub new_node: NodeId,
502 pub file: &'static str,
503 pub line: u32,
504 pub column: u32,
505}
506
507#[derive(Debug, Default, Clone)]
508pub(crate) struct LayoutQueryRegionMarker {
509 pub name: Option<Arc<str>>,
510}
511
512impl WindowElementState {
513 pub(crate) fn element_children_vec_pool_reuses(&self) -> u32 {
514 self.element_children_vec_pool_reuses
515 }
516
517 pub(crate) fn element_children_vec_pool_misses(&self) -> u32 {
518 self.element_children_vec_pool_misses
519 }
520
521 pub(crate) fn take_scratch_element_children_vec(
522 &mut self,
523 min_capacity: usize,
524 ) -> Vec<AnyElement> {
525 if let Some(mut out) = self.scratch_element_children_vec_pool.pop() {
526 self.element_children_vec_pool_reuses =
527 self.element_children_vec_pool_reuses.saturating_add(1);
528 out.clear();
529 if out.capacity() < min_capacity {
530 out.reserve(min_capacity - out.capacity());
531 }
532 return out;
533 }
534 self.element_children_vec_pool_misses =
535 self.element_children_vec_pool_misses.saturating_add(1);
536 Vec::with_capacity(min_capacity)
537 }
538
539 pub(crate) fn restore_scratch_element_children_vec(&mut self, mut scratch: Vec<AnyElement>) {
540 const POOL_MAX: usize = 2048;
541 const MAX_RETAIN_BYTES: usize = 64 * 1024;
542 if self.scratch_element_children_vec_pool.len() >= POOL_MAX {
543 return;
544 }
545 let bytes = scratch
546 .capacity()
547 .saturating_mul(std::mem::size_of::<AnyElement>());
548 if bytes > MAX_RETAIN_BYTES {
549 return;
550 }
551 scratch.clear();
552 self.scratch_element_children_vec_pool.push(scratch);
553 }
554
555 pub(crate) fn take_scratch_view_cache_keep_alive_elements(
556 &mut self,
557 ) -> HashSet<GlobalElementId> {
558 std::mem::take(&mut self.scratch_view_cache_keep_alive_elements)
559 }
560
561 pub(crate) fn restore_scratch_view_cache_keep_alive_elements(
562 &mut self,
563 scratch: HashSet<GlobalElementId>,
564 ) {
565 self.scratch_view_cache_keep_alive_elements = scratch;
566 }
567
568 pub(crate) fn take_scratch_view_cache_keep_alive_visited_roots(
569 &mut self,
570 ) -> HashSet<GlobalElementId> {
571 std::mem::take(&mut self.scratch_view_cache_keep_alive_visited_roots)
572 }
573
574 pub(crate) fn restore_scratch_view_cache_keep_alive_visited_roots(
575 &mut self,
576 scratch: HashSet<GlobalElementId>,
577 ) {
578 self.scratch_view_cache_keep_alive_visited_roots = scratch;
579 }
580
581 pub(crate) fn take_scratch_view_cache_keep_alive_stack(&mut self) -> Vec<GlobalElementId> {
582 std::mem::take(&mut self.scratch_view_cache_keep_alive_stack)
583 }
584
585 pub(crate) fn restore_scratch_view_cache_keep_alive_stack(
586 &mut self,
587 scratch: Vec<GlobalElementId>,
588 ) {
589 self.scratch_view_cache_keep_alive_stack = scratch;
590 }
591
592 #[cfg(any(test, feature = "diagnostics"))]
593 #[allow(dead_code)]
594 pub(crate) fn set_strict_ownership(&mut self, strict: bool) {
595 self.strict_ownership = strict;
596 }
597
598 fn prepare_for_frame(&mut self, frame_id: FrameId, lag_frames: u64) {
599 if self.prepared_frame == frame_id {
600 return;
601 }
602 self.prepared_frame = frame_id;
603
604 self.advance_element_state_buffers(lag_frames);
605
606 let cutoff = frame_id.0.saturating_sub(1);
607 self.transient_events
608 .retain(|_, recorded| recorded.0 >= cutoff);
609 self.raf_notify_roots.clear();
610 #[cfg(feature = "diagnostics")]
611 self.raf_notify_roots_debug.clear();
612 self.view_cache_key_mismatch_roots.clear();
613 self.element_children_vec_pool_reuses = 0;
614 self.element_children_vec_pool_misses = 0;
615
616 std::mem::swap(
617 &mut self.view_cache_keys_rendered,
618 &mut self.view_cache_keys_next,
619 );
620 self.view_cache_keys_next.clear();
621
622 std::mem::swap(
623 &mut self.view_cache_state_keys_rendered,
624 &mut self.view_cache_state_keys_next,
625 );
626 self.view_cache_state_keys_next.clear();
627
628 std::mem::swap(
629 &mut self.view_cache_authoring_identities_rendered,
630 &mut self.view_cache_authoring_identities_next,
631 );
632 self.view_cache_authoring_identities_next.clear();
633
634 std::mem::swap(
635 &mut self.view_cache_elements_rendered,
636 &mut self.view_cache_elements_next,
637 );
638 self.view_cache_elements_next.clear();
639
640 self.view_cache_reuse_roots.clear();
641 self.view_cache_transitioned_reuse_roots.clear();
642 self.view_cache_stack.clear();
643
644 std::mem::swap(
645 &mut self.prev_unkeyed_fingerprints,
646 &mut self.cur_unkeyed_fingerprints,
647 );
648 self.cur_unkeyed_fingerprints.clear();
649
650 std::mem::swap(
651 &mut self.observed_models_rendered,
652 &mut self.observed_models_next,
653 );
654 self.observed_models_next.clear();
655 std::mem::swap(
656 &mut self.observed_globals_rendered,
657 &mut self.observed_globals_next,
658 );
659 self.observed_globals_next.clear();
660 std::mem::swap(
661 &mut self.observed_layout_queries_rendered,
662 &mut self.observed_layout_queries_next,
663 );
664 self.observed_layout_queries_next.clear();
665 self.layout_query_regions_changed_this_frame.clear();
666
667 std::mem::swap(
668 &mut self.observed_environment_rendered,
669 &mut self.observed_environment_next,
670 );
671 self.observed_environment_next.clear();
672 self.environment_changed_this_frame.clear();
673
674 if !self.cur_bounds.is_empty() {
681 self.prev_bounds.reserve(self.cur_bounds.len());
682 self.prev_bounds.extend(self.cur_bounds.drain());
683 }
684 self.cur_bounds.clear();
685
686 if !self.cur_visual_bounds.is_empty() {
687 self.prev_visual_bounds
688 .reserve(self.cur_visual_bounds.len());
689 self.prev_visual_bounds
690 .extend(self.cur_visual_bounds.drain());
691 }
692 self.cur_visual_bounds.clear();
693
694 self.focused_element = None;
695 self.authoring_identities_current_frame.clear();
696
697 #[cfg(feature = "diagnostics")]
698 {
699 let cutoff = frame_id.0.saturating_sub(lag_frames);
700 self.debug_identity
701 .entries
702 .retain(|_, v| v.last_seen_frame.0 >= cutoff);
703 self.debug_node_entry_root_overwrites
704 .retain(|r| r.frame_id.0 >= cutoff);
705
706 const KEEP_FRAMES: u64 = 120;
709 let cutoff = frame_id.0.saturating_sub(KEEP_FRAMES);
710 self.overlay_placement.retain(|rec| match rec {
711 OverlayPlacementDiagnosticsRecord::AnchoredPanel(r) => r.frame_id.0 >= cutoff,
712 OverlayPlacementDiagnosticsRecord::PlacedRect(r) => r.frame_id.0 >= cutoff,
713 });
714 const MAX_RECORDS: usize = 512;
715 if self.overlay_placement.len() > MAX_RECORDS {
716 let extra = self.overlay_placement.len() - MAX_RECORDS;
717 self.overlay_placement.drain(0..extra);
718 }
719 }
720 }
721
722 pub(crate) fn retained_virtual_list_keep_alive_roots(
723 &self,
724 ) -> impl Iterator<Item = NodeId> + '_ {
725 self.retained_virtual_list_keep_alive_roots.iter().copied()
726 }
727
728 pub(crate) fn add_retained_virtual_list_keep_alive_root(&mut self, node: NodeId) {
729 self.retained_virtual_list_keep_alive_roots.insert(node);
730 }
731
732 pub(crate) fn remove_retained_virtual_list_keep_alive_root(&mut self, node: NodeId) {
733 self.retained_virtual_list_keep_alive_roots.remove(&node);
734 }
735
736 pub(crate) fn retain_retained_virtual_list_keep_alive_roots(
737 &mut self,
738 mut f: impl FnMut(NodeId) -> bool,
739 ) {
740 self.retained_virtual_list_keep_alive_roots
741 .retain(|node| f(*node));
742 }
743
744 pub(crate) fn record_transient_event(&mut self, element: GlobalElementId, key: u64) {
745 self.transient_events
746 .insert((element, key), self.prepared_frame);
747 }
748
749 pub(crate) fn mark_authoring_identity_seen(&mut self, element: GlobalElementId) {
750 self.authoring_identities_current_frame.insert(element);
751 for &root in &self.view_cache_stack {
752 self.view_cache_authoring_identities_next
753 .entry(root)
754 .or_default()
755 .push(element);
756 }
757 #[cfg(feature = "diagnostics")]
758 self.touch_debug_identity_for_element(self.prepared_frame, element);
759 }
760
761 pub(crate) fn element_identity_is_live_in_current_frame(
762 &self,
763 element: GlobalElementId,
764 ) -> bool {
765 self.authoring_identities_current_frame.contains(&element)
766 || self
767 .node_entry(element)
768 .is_some_and(|entry| entry.last_seen_frame == self.prepared_frame)
769 }
770
771 pub(crate) fn take_transient_event(&mut self, element: GlobalElementId, key: u64) -> bool {
772 self.transient_events.remove(&(element, key)).is_some()
773 }
774
775 fn advance_element_state_buffers(&mut self, lag_frames: u64) {
776 if lag_frames == 0 {
777 self.lag_state.clear();
778 } else {
779 self.lag_state
780 .push(std::mem::take(&mut self.rendered_state));
781 let max = lag_frames as usize;
782 if self.lag_state.len() > max {
783 let drain = self.lag_state.len() - max;
784 self.lag_state.drain(0..drain);
785 }
786 }
787
788 self.rendered_state = std::mem::take(&mut self.next_state);
789 self.next_state.clear();
790 }
791
792 pub(super) fn state_any_ref(&self, key: &(GlobalElementId, TypeId)) -> Option<&dyn Any> {
793 if let Some(v) = self.next_state.get(key) {
794 return Some(&**v);
795 }
796 if let Some(v) = self.rendered_state.get(key) {
797 return Some(&**v);
798 }
799 for map in self.lag_state.iter().rev() {
800 if let Some(v) = map.get(key) {
801 return Some(&**v);
802 }
803 }
804 None
805 }
806
807 pub(crate) fn has_state<S: Any>(&self, element: GlobalElementId) -> bool {
808 let key = (element, TypeId::of::<S>());
809 self.state_any_ref(&key).is_some()
810 }
811
812 pub(crate) fn layout_query_region_revision(&self, element: GlobalElementId) -> u64 {
813 self.layout_query_region_revisions
814 .get(&element)
815 .copied()
816 .unwrap_or(0)
817 }
818
819 pub(crate) fn layout_query_deps_fingerprint_rendered(&self, root: GlobalElementId) -> u64 {
820 self.layout_query_deps_fingerprint_from_list(
821 self.observed_layout_queries_rendered.get(&root),
822 )
823 }
824
825 pub(crate) fn layout_query_deps_fingerprint_next(&self, root: GlobalElementId) -> u64 {
826 self.layout_query_deps_fingerprint_from_list(self.observed_layout_queries_next.get(&root))
827 }
828
829 fn layout_query_deps_fingerprint_from_list(
830 &self,
831 deps: Option<&Vec<(GlobalElementId, Invalidation)>>,
832 ) -> u64 {
833 let Some(deps) = deps else {
834 return 0;
835 };
836 if deps.is_empty() {
837 return 0;
838 }
839
840 let mut entries: Vec<(u64, u64, u8)> = deps
841 .iter()
842 .map(|(region, inv)| {
843 (
844 region.0,
845 self.layout_query_region_revision(*region),
846 *inv as u8,
847 )
848 })
849 .collect();
850 entries.sort_by(|a, b| a.0.cmp(&b.0).then(a.2.cmp(&b.2)));
851 stable_hash(&entries)
852 }
853
854 pub(crate) fn environment_deps_fingerprint_rendered(&self, root: GlobalElementId) -> u64 {
855 self.environment_deps_fingerprint_from_list(self.observed_environment_rendered.get(&root))
856 }
857
858 pub(crate) fn environment_deps_fingerprint_next(&self, root: GlobalElementId) -> u64 {
859 self.environment_deps_fingerprint_from_list(self.observed_environment_next.get(&root))
860 }
861
862 fn environment_revision(&self, key: EnvironmentQueryKey) -> u64 {
863 self.environment_revisions.get(&key).copied().unwrap_or(0)
864 }
865
866 fn environment_deps_fingerprint_from_list(
867 &self,
868 deps: Option<&Vec<(EnvironmentQueryKey, Invalidation)>>,
869 ) -> u64 {
870 let Some(deps) = deps else {
871 return 0;
872 };
873 if deps.is_empty() {
874 return 0;
875 }
876
877 let key_id = |k: EnvironmentQueryKey| match k {
878 EnvironmentQueryKey::ViewportSize => 0u8,
879 EnvironmentQueryKey::ScaleFactor => 1u8,
880 EnvironmentQueryKey::ColorScheme => 2u8,
881 EnvironmentQueryKey::PrefersReducedMotion => 3u8,
882 EnvironmentQueryKey::PrimaryPointerType => 4u8,
883 EnvironmentQueryKey::SafeAreaInsets => 5u8,
884 EnvironmentQueryKey::OcclusionInsets => 6u8,
885 EnvironmentQueryKey::PrefersContrast => 7u8,
886 EnvironmentQueryKey::ForcedColorsMode => 8u8,
887 EnvironmentQueryKey::TextScaleFactor => 9u8,
888 EnvironmentQueryKey::PrefersReducedTransparency => 10u8,
889 EnvironmentQueryKey::AccentColor => 11u8,
890 };
891
892 let mut entries: Vec<(u8, u64, u8)> = deps
893 .iter()
894 .map(|(key, inv)| (key_id(*key), self.environment_revision(*key), *inv as u8))
895 .collect();
896 entries.sort_by(|a, b| a.0.cmp(&b.0).then(a.2.cmp(&b.2)));
897 stable_hash(&entries)
898 }
899
900 pub(super) fn take_state_box(
901 &mut self,
902 key: &(GlobalElementId, TypeId),
903 ) -> Option<Box<dyn Any>> {
904 if let Some(v) = self.next_state.remove(key) {
905 return Some(v);
906 }
907 if let Some(v) = self.rendered_state.remove(key) {
908 return Some(v);
909 }
910 for map in self.lag_state.iter_mut().rev() {
911 if let Some(v) = map.remove(key) {
912 return Some(v);
913 }
914 }
915 None
916 }
917
918 pub(super) fn insert_state_box(&mut self, key: (GlobalElementId, TypeId), value: Box<dyn Any>) {
919 self.next_state.insert(key, value);
920 }
921
922 pub(crate) fn with_state_mut<S: Any, R>(
923 &mut self,
924 element: GlobalElementId,
925 init: impl FnOnce() -> S,
926 f: impl FnOnce(&mut S) -> R,
927 ) -> R {
928 struct StateBoxGuard<'a> {
929 window_state: &'a mut WindowElementState,
930 key: (GlobalElementId, TypeId),
931 value: Option<Box<dyn Any>>,
932 }
933
934 impl Drop for StateBoxGuard<'_> {
935 fn drop(&mut self) {
936 let Some(value) = self.value.take() else {
937 return;
938 };
939 self.window_state.insert_state_box(self.key, value);
940 }
941 }
942
943 let key = (element, TypeId::of::<S>());
944 self.record_state_key_access(key);
945
946 let mut init = Some(init);
947 let value = self.take_state_box(&key).unwrap_or_else(|| {
948 let init = init.take().expect("init is available");
949 Box::new(init())
950 });
951 let mut guard = StateBoxGuard {
952 window_state: self,
953 key,
954 value: Some(value),
955 };
956
957 let state = match guard
958 .value
959 .as_deref_mut()
960 .expect("guard has a value")
961 .downcast_mut::<S>()
962 {
963 Some(state) => state,
964 None => {
965 if crate::strict_runtime::strict_runtime_enabled() {
966 panic!(
967 "element state type mismatch: element={:?}, expected={}",
968 element,
969 std::any::type_name::<S>()
970 );
971 }
972
973 #[cfg(debug_assertions)]
974 {
975 eprintln!(
976 "element state type mismatch: element={:?}, expected={}; dropping corrupt state and re-initializing",
977 element,
978 std::any::type_name::<S>()
979 );
980 }
981
982 let init = init.take().expect("init is available");
983 guard.value = Some(Box::new(init()));
984 guard
985 .value
986 .as_deref_mut()
987 .expect("guard has a value")
988 .downcast_mut::<S>()
989 .expect("re-initialized state has the expected type")
990 }
991 };
992
993 f(state)
994 }
995
996 pub(crate) fn try_with_state_mut<S: Any, R>(
997 &mut self,
998 element: GlobalElementId,
999 f: impl FnOnce(&mut S) -> R,
1000 ) -> Option<R> {
1001 struct StateBoxGuard<'a> {
1002 window_state: &'a mut WindowElementState,
1003 key: (GlobalElementId, TypeId),
1004 value: Option<Box<dyn Any>>,
1005 }
1006
1007 impl Drop for StateBoxGuard<'_> {
1008 fn drop(&mut self) {
1009 let Some(value) = self.value.take() else {
1010 return;
1011 };
1012 self.window_state.insert_state_box(self.key, value);
1013 }
1014 }
1015
1016 let key = (element, TypeId::of::<S>());
1017 self.record_state_key_access(key);
1018
1019 let value = self.take_state_box(&key)?;
1020 let mut guard = StateBoxGuard {
1021 window_state: self,
1022 key,
1023 value: Some(value),
1024 };
1025
1026 let state = match guard
1027 .value
1028 .as_deref_mut()
1029 .expect("guard has a value")
1030 .downcast_mut::<S>()
1031 {
1032 Some(state) => state,
1033 None => {
1034 if crate::strict_runtime::strict_runtime_enabled() {
1035 panic!(
1036 "element state type mismatch: element={:?}, expected={}",
1037 element,
1038 std::any::type_name::<S>()
1039 );
1040 }
1041
1042 #[cfg(debug_assertions)]
1043 {
1044 eprintln!(
1045 "element state type mismatch: element={:?}, expected={}; dropping corrupt state",
1046 element,
1047 std::any::type_name::<S>()
1048 );
1049 }
1050
1051 guard.value = None;
1052 return None;
1053 }
1054 };
1055
1056 Some(f(state))
1057 }
1058
1059 pub(crate) fn mark_retained_virtual_list_needs_reconcile(
1060 &mut self,
1061 element: GlobalElementId,
1062 kind: crate::tree::UiDebugRetainedVirtualListReconcileKind,
1063 ) {
1064 self.pending_retained_virtual_list_reconciles
1065 .entry(element)
1066 .and_modify(|existing| {
1067 if *existing == crate::tree::UiDebugRetainedVirtualListReconcileKind::Prefetch
1070 && kind == crate::tree::UiDebugRetainedVirtualListReconcileKind::Escape
1071 {
1072 *existing = kind;
1073 }
1074 })
1075 .or_insert(kind);
1076 }
1077
1078 pub(crate) fn take_retained_virtual_list_reconciles(
1079 &mut self,
1080 ) -> Vec<(
1081 GlobalElementId,
1082 crate::tree::UiDebugRetainedVirtualListReconcileKind,
1083 )> {
1084 self.pending_retained_virtual_list_reconciles
1085 .drain()
1086 .collect()
1087 }
1088
1089 pub(super) fn record_state_key_access(&mut self, key: (GlobalElementId, TypeId)) {
1090 if self.view_cache_stack.is_empty() {
1091 return;
1092 };
1093 for &root in &self.view_cache_stack {
1097 self.view_cache_state_keys_next
1098 .entry(root)
1099 .or_default()
1100 .push(key);
1101 }
1102 }
1103
1104 pub(super) fn begin_view_cache_scope(&mut self, root: GlobalElementId) {
1105 self.view_cache_stack.push(root);
1106 self.view_cache_state_keys_next.remove(&root);
1107 self.view_cache_authoring_identities_next.remove(&root);
1108 self.view_cache_elements_next.remove(&root);
1109 }
1110
1111 pub(super) fn end_view_cache_scope(&mut self, root: GlobalElementId) {
1112 let popped = self.view_cache_stack.pop();
1113 debug_assert_eq!(popped, Some(root));
1114 if let Some(keys) = self.view_cache_state_keys_next.get_mut(&root) {
1115 let mut seen: HashSet<(GlobalElementId, TypeId)> = HashSet::with_capacity(keys.len());
1116 keys.retain(|&key| seen.insert(key));
1117 }
1118 if let Some(identities) = self.view_cache_authoring_identities_next.get_mut(&root) {
1119 let mut seen: HashSet<GlobalElementId> = HashSet::with_capacity(identities.len());
1120 identities.retain(|&id| seen.insert(id));
1121 }
1122 if let Some(elements) = self.view_cache_elements_next.get_mut(&root) {
1123 let mut seen: HashSet<GlobalElementId> = HashSet::with_capacity(elements.len());
1124 elements.retain(|&id| seen.insert(id));
1125 }
1126 }
1127
1128 pub(crate) fn touch_observed_models_for_element_if_recorded(
1129 &mut self,
1130 element: GlobalElementId,
1131 ) {
1132 if self.observed_models_next.contains_key(&element) {
1133 return;
1134 }
1135 let Some(list) = self.observed_models_rendered.get(&element) else {
1136 return;
1137 };
1138 self.observed_models_next.insert(element, list.clone());
1139 }
1140
1141 pub(crate) fn touch_observed_globals_for_element_if_recorded(
1142 &mut self,
1143 element: GlobalElementId,
1144 ) {
1145 if self.observed_globals_next.contains_key(&element) {
1146 return;
1147 }
1148 let Some(list) = self.observed_globals_rendered.get(&element) else {
1149 return;
1150 };
1151 self.observed_globals_next.insert(element, list.clone());
1152 }
1153
1154 pub(crate) fn touch_observed_layout_queries_for_element_if_recorded(
1155 &mut self,
1156 element: GlobalElementId,
1157 ) {
1158 if self.observed_layout_queries_next.contains_key(&element) {
1159 return;
1160 }
1161 let Some(list) = self.observed_layout_queries_rendered.get(&element) else {
1162 return;
1163 };
1164 self.observed_layout_queries_next
1165 .insert(element, list.clone());
1166 }
1167
1168 pub(crate) fn touch_observed_environment_for_element_if_recorded(
1169 &mut self,
1170 element: GlobalElementId,
1171 ) {
1172 if self.observed_environment_next.contains_key(&element) {
1173 return;
1174 }
1175 let Some(list) = self.observed_environment_rendered.get(&element) else {
1176 return;
1177 };
1178 self.observed_environment_next.insert(element, list.clone());
1179 }
1180
1181 pub(crate) fn mark_view_cache_reuse_root(&mut self, root: GlobalElementId) {
1182 self.view_cache_reuse_roots.insert(root);
1183 }
1184
1185 pub(crate) fn record_view_cache_reuse_frame(
1190 &mut self,
1191 root: GlobalElementId,
1192 frame_id: FrameId,
1193 ) -> bool {
1194 let transitioned = self
1195 .view_cache_last_reused_frame
1196 .get(&root)
1197 .is_none_or(|last| last.0.saturating_add(1) < frame_id.0);
1198 self.view_cache_last_reused_frame.insert(root, frame_id);
1199 if transitioned {
1200 self.view_cache_transitioned_reuse_roots.insert(root);
1201 }
1202 transitioned
1203 }
1204
1205 pub(crate) fn view_cache_transitioned_reuse_roots(
1206 &self,
1207 ) -> impl Iterator<Item = GlobalElementId> + '_ {
1208 self.view_cache_transitioned_reuse_roots.iter().copied()
1209 }
1210
1211 pub(crate) fn should_reuse_view_cache_root(&self, root: GlobalElementId) -> bool {
1212 self.view_cache_reuse_roots.contains(&root)
1213 }
1214
1215 pub(crate) fn view_cache_reuse_roots(&self) -> impl Iterator<Item = GlobalElementId> + '_ {
1216 self.view_cache_reuse_roots.iter().copied()
1217 }
1218
1219 pub(crate) fn current_view_cache_root(&self) -> Option<GlobalElementId> {
1220 self.view_cache_stack.last().copied()
1221 }
1222
1223 pub(crate) fn request_notify_for_animation_frame(&mut self, root: GlobalElementId) {
1224 self.raf_notify_roots.insert(root);
1225 #[cfg(feature = "diagnostics")]
1226 self.raf_notify_roots_debug.insert(root);
1227 }
1228
1229 pub(crate) fn take_notify_for_animation_frame(&mut self) -> Vec<GlobalElementId> {
1230 if self.raf_notify_roots.is_empty() {
1231 return Vec::new();
1232 }
1233 let out: Vec<GlobalElementId> = self.raf_notify_roots.iter().copied().collect();
1234 self.raf_notify_roots.clear();
1235 out
1236 }
1237
1238 pub(crate) fn view_cache_key_matches_and_touch(
1239 &mut self,
1240 root: GlobalElementId,
1241 key: u64,
1242 ) -> bool {
1243 if self.view_cache_keys_next.contains_key(&root) {
1244 return self.view_cache_keys_next.get(&root).copied() == Some(key);
1245 }
1246 let Some(prev) = self.view_cache_keys_rendered.get(&root).copied() else {
1247 return false;
1248 };
1249 if prev != key {
1250 return false;
1251 }
1252 self.view_cache_keys_next.insert(root, key);
1253 true
1254 }
1255
1256 pub(crate) fn set_view_cache_key(&mut self, root: GlobalElementId, key: u64) {
1257 self.view_cache_keys_next.insert(root, key);
1258 }
1259
1260 pub(crate) fn record_view_cache_key_mismatch(&mut self, root: GlobalElementId) {
1261 self.view_cache_key_mismatch_roots.insert(root);
1262 }
1263
1264 pub(crate) fn view_cache_key_mismatch(&self, root: GlobalElementId) -> bool {
1265 self.view_cache_key_mismatch_roots.contains(&root)
1266 }
1267
1268 pub(super) fn touch_state_key(&mut self, key: (GlobalElementId, TypeId)) {
1269 let Some(value) = self.take_state_box(&key) else {
1270 return;
1271 };
1272 self.insert_state_box(key, value);
1273 }
1274
1275 pub(crate) fn touch_view_cache_state_keys_if_recorded(&mut self, root: GlobalElementId) {
1276 let Some(keys) = self.view_cache_state_keys_rendered.get(&root).cloned() else {
1277 return;
1278 };
1279 for &key in &keys {
1280 self.touch_state_key(key);
1281 }
1282 self.view_cache_state_keys_next.insert(root, keys);
1283 }
1284
1285 pub(crate) fn touch_view_cache_authoring_identities_if_recorded(
1286 &mut self,
1287 root: GlobalElementId,
1288 ) {
1289 let Some(identities) = self
1290 .view_cache_authoring_identities_rendered
1291 .get(&root)
1292 .cloned()
1293 else {
1294 return;
1295 };
1296 for &identity in &identities {
1297 self.authoring_identities_current_frame.insert(identity);
1298 #[cfg(feature = "diagnostics")]
1299 self.touch_debug_identity_for_element(self.prepared_frame, identity);
1300 }
1301 self.view_cache_authoring_identities_next
1302 .insert(root, identities);
1303 }
1304
1305 pub(crate) fn touch_view_cache_action_hook_state_for_subtree_elements(
1313 &mut self,
1314 root: GlobalElementId,
1315 ) {
1316 let Some(elements) = self.view_cache_elements_for_root(root) else {
1317 return;
1318 };
1319 if elements.is_empty() {
1320 return;
1321 }
1322
1323 let elements = elements.to_vec();
1324 for element in elements {
1325 self.touch_state_key((element, TypeId::of::<crate::action::PressableActionHooks>()));
1326 self.touch_state_key((
1327 element,
1328 TypeId::of::<crate::action::PressableHoverActionHooks>(),
1329 ));
1330 self.touch_state_key((
1331 element,
1332 TypeId::of::<crate::action::DismissibleActionHooks>(),
1333 ));
1334 self.touch_state_key((element, TypeId::of::<crate::action::PointerActionHooks>()));
1335 self.touch_state_key((element, TypeId::of::<crate::action::KeyActionHooks>()));
1336 self.touch_state_key((element, TypeId::of::<crate::action::ActionRouteHooks>()));
1337 self.touch_state_key((element, TypeId::of::<crate::action::CommandActionHooks>()));
1338 self.touch_state_key((
1339 element,
1340 TypeId::of::<crate::action::CommandAvailabilityActionHooks>(),
1341 ));
1342 self.touch_state_key((element, TypeId::of::<crate::action::TimerActionHooks>()));
1343 self.touch_state_key((element, TypeId::of::<crate::action::RovingActionHooks>()));
1344 self.touch_state_key((element, TypeId::of::<crate::element::SelectableTextState>()));
1345 self.touch_state_key((
1346 element,
1347 TypeId::of::<crate::action::SelectableTextActionHooks>(),
1348 ));
1349 self.touch_state_key((
1350 element,
1351 TypeId::of::<crate::action::TextInputRegionActionHooks>(),
1352 ));
1353 self.touch_state_key((
1354 element,
1355 TypeId::of::<crate::action::InternalDragActionHooks>(),
1356 ));
1357 self.touch_state_key((
1358 element,
1359 TypeId::of::<crate::action::ExternalDragActionHooks>(),
1360 ));
1361 }
1362 }
1363
1364 pub(crate) fn record_view_cache_subtree_elements(
1365 &mut self,
1366 root: GlobalElementId,
1367 elements: Vec<GlobalElementId>,
1368 ) {
1369 self.view_cache_elements_next.insert(root, elements);
1370 }
1371
1372 pub(crate) fn forget_view_cache_subtree_elements(&mut self, root: GlobalElementId) {
1373 self.view_cache_authoring_identities_rendered.remove(&root);
1374 self.view_cache_authoring_identities_next.remove(&root);
1375 self.view_cache_elements_rendered.remove(&root);
1376 self.view_cache_elements_next.remove(&root);
1377 }
1378
1379 pub(crate) fn touch_view_cache_subtree_elements_if_recorded(
1380 &mut self,
1381 root: GlobalElementId,
1382 frame_id: FrameId,
1383 root_id: GlobalElementId,
1384 mut resolve_live_attached_node: impl FnMut(GlobalElementId, Option<NodeId>) -> Option<NodeId>,
1385 ) -> bool {
1386 if self.view_cache_elements_next.contains_key(&root) {
1387 return true;
1388 }
1389
1390 let Some(elements) = self.view_cache_elements_rendered.get(&root).cloned() else {
1391 return false;
1392 };
1393
1394 let mut authoritative_nodes: Vec<(GlobalElementId, NodeId, GlobalElementId)> =
1395 Vec::with_capacity(elements.len());
1396 for &element in &elements {
1397 let seeded_entry = self.nodes.get(&element).copied();
1398 let Some(node) =
1399 resolve_live_attached_node(element, seeded_entry.map(|entry| entry.node))
1400 else {
1401 return false;
1402 };
1403 let owner_root = seeded_entry.map(|entry| entry.root).unwrap_or(root_id);
1404 authoritative_nodes.push((element, node, owner_root));
1405 }
1406
1407 self.view_cache_elements_next.insert(root, elements);
1408
1409 for (element, node, owner_root) in authoritative_nodes {
1410 if owner_root == root_id {
1414 }
1416 self.nodes.insert(
1417 element,
1418 NodeEntry {
1419 node,
1420 last_seen_frame: frame_id,
1421 root: owner_root,
1422 },
1423 );
1424
1425 #[cfg(feature = "diagnostics")]
1426 self.touch_debug_identity_for_element(frame_id, element);
1427 }
1428
1429 true
1430 }
1431
1432 pub(crate) fn view_cache_elements_for_root(
1433 &self,
1434 root: GlobalElementId,
1435 ) -> Option<&[GlobalElementId]> {
1436 if let Some(v) = self.view_cache_elements_next.get(&root) {
1437 return Some(v.as_slice());
1438 }
1439 self.view_cache_elements_rendered
1440 .get(&root)
1441 .map(|v| v.as_slice())
1442 }
1443
1444 pub(crate) fn active_text_selection(&self) -> Option<ActiveTextSelection> {
1445 self.active_text_selection
1446 }
1447
1448 pub(crate) fn set_active_text_selection(&mut self, selection: Option<ActiveTextSelection>) {
1449 self.active_text_selection = selection;
1450 }
1451
1452 pub(crate) fn node_entry(&self, id: GlobalElementId) -> Option<NodeEntry> {
1453 self.nodes.get(&id).copied()
1454 }
1455
1456 pub(crate) fn for_each_observed_model_for_invalidation(
1457 &self,
1458 frame_id: FrameId,
1459 mut f: impl FnMut(GlobalElementId, &Vec<(ModelId, Invalidation)>),
1460 ) {
1461 if self.prepared_frame != frame_id {
1462 for (&element, observations) in &self.observed_models_next {
1463 f(element, observations);
1464 }
1465 return;
1466 }
1467
1468 for (&element, observations) in &self.observed_models_rendered {
1469 if self.observed_models_next.contains_key(&element) {
1470 continue;
1471 }
1472 f(element, observations);
1473 }
1474 for (&element, observations) in &self.observed_models_next {
1475 f(element, observations);
1476 }
1477 }
1478
1479 pub(crate) fn for_each_observed_global_for_invalidation(
1480 &self,
1481 frame_id: FrameId,
1482 mut f: impl FnMut(GlobalElementId, &Vec<(TypeId, Invalidation)>),
1483 ) {
1484 if self.prepared_frame != frame_id {
1485 for (&element, observations) in &self.observed_globals_next {
1486 f(element, observations);
1487 }
1488 return;
1489 }
1490
1491 for (&element, observations) in &self.observed_globals_rendered {
1492 if self.observed_globals_next.contains_key(&element) {
1493 continue;
1494 }
1495 f(element, observations);
1496 }
1497 for (&element, observations) in &self.observed_globals_next {
1498 f(element, observations);
1499 }
1500 }
1501
1502 pub(crate) fn element_for_node(&self, node: NodeId) -> Option<GlobalElementId> {
1503 self.nodes
1504 .iter()
1505 .find_map(|(&element, entry)| (entry.node == node).then_some(element))
1506 }
1507
1508 #[track_caller]
1509 pub(crate) fn set_node_entry(&mut self, id: GlobalElementId, entry: NodeEntry) {
1510 #[cfg(feature = "diagnostics")]
1511 if let Some(prev) = self.nodes.get(&id)
1512 && prev.root != entry.root
1513 {
1514 let location = std::panic::Location::caller();
1515 self.debug_node_entry_root_overwrites
1516 .push(NodeEntryRootOverwrite {
1517 frame_id: entry.last_seen_frame,
1518 element: id,
1519 old_root: prev.root,
1520 new_root: entry.root,
1521 old_node: prev.node,
1522 new_node: entry.node,
1523 file: location.file(),
1524 line: location.line(),
1525 column: location.column(),
1526 });
1527 const MAX_RECORDS: usize = 256;
1528 if self.debug_node_entry_root_overwrites.len() > MAX_RECORDS {
1529 let drain = self.debug_node_entry_root_overwrites.len() - MAX_RECORDS;
1530 self.debug_node_entry_root_overwrites.drain(0..drain);
1531 }
1532 }
1533 #[cfg(any(test, feature = "diagnostics"))]
1534 if self.strict_ownership
1535 && let Some(prev) = self.nodes.get(&id)
1536 {
1537 assert_eq!(
1538 prev.root, entry.root,
1539 "ownership root overwrite detected for element {id:?}: old_root={:?} new_root={:?} (cross-root reparenting must be explicit; see ADR 0176)",
1540 prev.root, entry.root
1541 );
1542 }
1543 self.nodes.insert(id, entry);
1544 }
1545
1546 pub(crate) fn retain_nodes(&mut self, f: impl FnMut(&GlobalElementId, &mut NodeEntry) -> bool) {
1547 self.nodes.retain(f);
1548 if let Some(selection) = self.active_text_selection
1549 && !self.nodes.contains_key(&selection.element)
1550 {
1551 self.active_text_selection = None;
1552 }
1553 }
1554
1555 pub(crate) fn clear_stale_interaction_targets_for_frame(&mut self, frame_id: FrameId) {
1556 let is_live_this_frame = |id: GlobalElementId| {
1557 self.nodes
1558 .get(&id)
1559 .is_some_and(|entry| entry.last_seen_frame == frame_id)
1560 };
1561
1562 if let Some(id) = self.hovered_pressable
1563 && !is_live_this_frame(id)
1564 {
1565 self.hovered_pressable = None;
1566 self.hovered_pressable_node = None;
1567 }
1568 if let Some(id) = self.hovered_pressable_raw
1569 && !is_live_this_frame(id)
1570 {
1571 self.hovered_pressable_raw = None;
1572 self.hovered_pressable_raw_node = None;
1573 }
1574 if let Some(id) = self.hovered_pressable_raw_below_barrier
1575 && !is_live_this_frame(id)
1576 {
1577 self.hovered_pressable_raw_below_barrier = None;
1578 self.hovered_pressable_raw_below_barrier_node = None;
1579 }
1580 if let Some(id) = self.pressed_pressable
1581 && !is_live_this_frame(id)
1582 {
1583 self.pressed_pressable = None;
1584 self.pressed_pressable_node = None;
1585 }
1586 if let Some(id) = self.hovered_hover_region
1587 && !is_live_this_frame(id)
1588 {
1589 self.hovered_hover_region = None;
1590 self.hovered_hover_region_node = None;
1591 }
1592 }
1593
1594 pub(crate) fn sync_interaction_target_nodes(
1595 &mut self,
1596 mut resolve: impl FnMut(GlobalElementId, Option<NodeId>) -> Option<NodeId>,
1597 ) {
1598 let hovered_pressable = self.hovered_pressable;
1599 self.hovered_pressable_node = hovered_pressable.and_then(|element| {
1600 resolve(
1601 element,
1602 self.hovered_pressable_node
1603 .or_else(|| self.node_entry(element).map(|entry| entry.node)),
1604 )
1605 });
1606
1607 let hovered_pressable_raw = self.hovered_pressable_raw;
1608 self.hovered_pressable_raw_node = hovered_pressable_raw.and_then(|element| {
1609 resolve(
1610 element,
1611 self.hovered_pressable_raw_node
1612 .or_else(|| self.node_entry(element).map(|entry| entry.node)),
1613 )
1614 });
1615
1616 let hovered_pressable_raw_below_barrier = self.hovered_pressable_raw_below_barrier;
1617 self.hovered_pressable_raw_below_barrier_node = hovered_pressable_raw_below_barrier
1618 .and_then(|element| {
1619 resolve(
1620 element,
1621 self.hovered_pressable_raw_below_barrier_node
1622 .or_else(|| self.node_entry(element).map(|entry| entry.node)),
1623 )
1624 });
1625
1626 let pressed_pressable = self.pressed_pressable;
1627 self.pressed_pressable_node = pressed_pressable.and_then(|element| {
1628 resolve(
1629 element,
1630 self.pressed_pressable_node
1631 .or_else(|| self.node_entry(element).map(|entry| entry.node)),
1632 )
1633 });
1634
1635 let hovered_hover_region = self.hovered_hover_region;
1636 self.hovered_hover_region_node = hovered_hover_region.and_then(|element| {
1637 resolve(
1638 element,
1639 self.hovered_hover_region_node
1640 .or_else(|| self.node_entry(element).map(|entry| entry.node)),
1641 )
1642 });
1643 }
1644
1645 pub(crate) fn sync_active_text_selection_node(
1646 &mut self,
1647 mut resolve: impl FnMut(GlobalElementId, Option<NodeId>) -> Option<NodeId>,
1648 ) {
1649 let Some(mut selection) = self.active_text_selection else {
1650 return;
1651 };
1652
1653 let Some(node) = resolve(
1654 selection.element,
1655 Some(selection.node)
1656 .or_else(|| self.node_entry(selection.element).map(|entry| entry.node)),
1657 ) else {
1658 self.active_text_selection = None;
1659 return;
1660 };
1661
1662 selection.node = node;
1663 if let Some(root) = self.node_entry(selection.element).map(|entry| entry.root) {
1664 selection.root = root;
1665 }
1666 self.active_text_selection = Some(selection);
1667 }
1668
1669 pub(crate) fn set_root_bounds(&mut self, root: GlobalElementId, bounds: Rect) {
1670 self.root_bounds.insert(root, bounds);
1671 }
1672
1673 pub(crate) fn root_bounds(&self, root: GlobalElementId) -> Option<Rect> {
1674 self.root_bounds.get(&root).copied()
1675 }
1676
1677 pub(crate) fn record_bounds(&mut self, element: GlobalElementId, bounds: Rect) {
1678 self.cur_bounds.insert(element, bounds);
1679
1680 if !self.has_state::<LayoutQueryRegionMarker>(element) {
1681 return;
1682 }
1683 if self
1684 .layout_query_regions_changed_this_frame
1685 .contains(&element)
1686 {
1687 return;
1688 }
1689
1690 const EPS_PX: f32 = 0.5;
1693 let changed = match self.prev_bounds.get(&element).copied() {
1694 None => true,
1695 Some(prev) => {
1696 (prev.size.width.0 - bounds.size.width.0).abs() > EPS_PX
1697 || (prev.size.height.0 - bounds.size.height.0).abs() > EPS_PX
1698 }
1699 };
1700
1701 if changed {
1702 self.layout_query_region_revisions
1703 .entry(element)
1704 .and_modify(|v| *v = v.saturating_add(1))
1705 .or_insert(1);
1706 self.layout_query_regions_changed_this_frame.insert(element);
1707 }
1708 }
1709
1710 pub(crate) fn element_nodes_copy_into(&self, out: &mut Vec<(GlobalElementId, NodeId)>) {
1711 out.clear();
1712 if out.capacity() < self.nodes.len() {
1713 out.reserve(self.nodes.len() - out.capacity());
1714 }
1715 out.extend(
1716 self.nodes
1717 .iter()
1718 .map(|(&element, entry)| (element, entry.node)),
1719 );
1720 }
1721
1722 pub(crate) fn last_bounds(&self, element: GlobalElementId) -> Option<Rect> {
1723 self.prev_bounds.get(&element).copied()
1724 }
1725
1726 pub(crate) fn current_bounds(&self, element: GlobalElementId) -> Option<Rect> {
1727 self.cur_bounds
1728 .get(&element)
1729 .copied()
1730 .or_else(|| self.prev_bounds.get(&element).copied())
1731 }
1732
1733 pub(crate) fn record_visual_bounds(&mut self, element: GlobalElementId, bounds: Rect) {
1734 self.cur_visual_bounds.insert(element, bounds);
1735 }
1736
1737 pub(crate) fn last_visual_bounds(&self, element: GlobalElementId) -> Option<Rect> {
1738 self.prev_visual_bounds.get(&element).copied()
1739 }
1740
1741 pub(crate) fn current_visual_bounds(&self, element: GlobalElementId) -> Option<Rect> {
1742 self.cur_visual_bounds
1743 .get(&element)
1744 .copied()
1745 .or_else(|| self.prev_visual_bounds.get(&element).copied())
1746 }
1747
1748 pub(crate) fn committed_viewport_bounds(&self) -> Rect {
1749 self.committed_viewport_bounds
1750 }
1751
1752 pub(crate) fn committed_scale_factor(&self) -> f32 {
1753 self.committed_scale_factor
1754 }
1755
1756 pub(crate) fn committed_color_scheme(&self) -> Option<ColorScheme> {
1757 self.committed_color_scheme
1758 }
1759
1760 pub(crate) fn committed_prefers_reduced_motion(&self) -> Option<bool> {
1761 self.committed_prefers_reduced_motion
1762 }
1763
1764 pub(crate) fn committed_text_scale_factor(&self) -> Option<f32> {
1765 self.committed_text_scale_factor
1766 }
1767
1768 pub(crate) fn committed_prefers_reduced_transparency(&self) -> Option<bool> {
1769 self.committed_prefers_reduced_transparency
1770 }
1771
1772 pub(crate) fn committed_accent_color(&self) -> Option<Color> {
1773 self.committed_accent_color
1774 }
1775
1776 pub(crate) fn committed_contrast_preference(&self) -> Option<ContrastPreference> {
1777 self.committed_contrast_preference
1778 }
1779
1780 pub(crate) fn committed_forced_colors_mode(&self) -> Option<ForcedColorsMode> {
1781 self.committed_forced_colors_mode
1782 }
1783
1784 pub(crate) fn committed_primary_pointer_type(&self) -> PointerType {
1785 self.committed_primary_pointer_type
1786 .unwrap_or(PointerType::Unknown)
1787 }
1788
1789 pub(crate) fn committed_safe_area_insets(&self) -> Option<Edges> {
1790 self.committed_safe_area_insets
1791 }
1792
1793 pub(crate) fn committed_occlusion_insets(&self) -> Option<Edges> {
1794 self.committed_occlusion_insets
1795 }
1796
1797 pub(crate) fn occlusion_insets_changed_this_frame(&self) -> bool {
1798 self.environment_changed_this_frame
1799 .contains(&EnvironmentQueryKey::OcclusionInsets)
1800 }
1801
1802 pub(crate) fn set_committed_prefers_reduced_motion(&mut self, value: Option<bool>) {
1803 if self.committed_prefers_reduced_motion == value {
1804 return;
1805 }
1806 self.committed_prefers_reduced_motion = value;
1807 self.environment_revisions
1808 .entry(EnvironmentQueryKey::PrefersReducedMotion)
1809 .and_modify(|v| *v = v.saturating_add(1))
1810 .or_insert(1);
1811 self.environment_changed_this_frame
1812 .insert(EnvironmentQueryKey::PrefersReducedMotion);
1813 }
1814
1815 #[allow(dead_code)]
1816 pub(crate) fn set_committed_text_scale_factor(&mut self, value: Option<f32>) {
1817 let changed = match (self.committed_text_scale_factor, value) {
1818 (Some(a), Some(b)) => (a - b).abs() > 0.0001,
1819 (None, None) => false,
1820 _ => true,
1821 };
1822 if !changed {
1823 return;
1824 }
1825
1826 self.committed_text_scale_factor = value;
1827 self.environment_revisions
1828 .entry(EnvironmentQueryKey::TextScaleFactor)
1829 .and_modify(|v| *v = v.saturating_add(1))
1830 .or_insert(1);
1831 self.environment_changed_this_frame
1832 .insert(EnvironmentQueryKey::TextScaleFactor);
1833 }
1834
1835 #[allow(dead_code)]
1836 pub(crate) fn set_committed_prefers_reduced_transparency(&mut self, value: Option<bool>) {
1837 if self.committed_prefers_reduced_transparency == value {
1838 return;
1839 }
1840 self.committed_prefers_reduced_transparency = value;
1841 self.environment_revisions
1842 .entry(EnvironmentQueryKey::PrefersReducedTransparency)
1843 .and_modify(|v| *v = v.saturating_add(1))
1844 .or_insert(1);
1845 self.environment_changed_this_frame
1846 .insert(EnvironmentQueryKey::PrefersReducedTransparency);
1847 }
1848
1849 #[allow(dead_code)]
1850 pub(crate) fn set_committed_accent_color(&mut self, value: Option<Color>) {
1851 if self.committed_accent_color == value {
1852 return;
1853 }
1854 self.committed_accent_color = value;
1855 self.environment_revisions
1856 .entry(EnvironmentQueryKey::AccentColor)
1857 .and_modify(|v| *v = v.saturating_add(1))
1858 .or_insert(1);
1859 self.environment_changed_this_frame
1860 .insert(EnvironmentQueryKey::AccentColor);
1861 }
1862
1863 pub(crate) fn set_committed_color_scheme(&mut self, value: Option<ColorScheme>) {
1864 if self.committed_color_scheme == value {
1865 return;
1866 }
1867 self.committed_color_scheme = value;
1868 self.environment_revisions
1869 .entry(EnvironmentQueryKey::ColorScheme)
1870 .and_modify(|v| *v = v.saturating_add(1))
1871 .or_insert(1);
1872 self.environment_changed_this_frame
1873 .insert(EnvironmentQueryKey::ColorScheme);
1874 }
1875
1876 pub(crate) fn set_committed_contrast_preference(&mut self, value: Option<ContrastPreference>) {
1877 if self.committed_contrast_preference == value {
1878 return;
1879 }
1880 self.committed_contrast_preference = value;
1881 self.environment_revisions
1882 .entry(EnvironmentQueryKey::PrefersContrast)
1883 .and_modify(|v| *v = v.saturating_add(1))
1884 .or_insert(1);
1885 self.environment_changed_this_frame
1886 .insert(EnvironmentQueryKey::PrefersContrast);
1887 }
1888
1889 pub(crate) fn set_committed_forced_colors_mode(&mut self, value: Option<ForcedColorsMode>) {
1890 if self.committed_forced_colors_mode == value {
1891 return;
1892 }
1893 self.committed_forced_colors_mode = value;
1894 self.environment_revisions
1895 .entry(EnvironmentQueryKey::ForcedColorsMode)
1896 .and_modify(|v| *v = v.saturating_add(1))
1897 .or_insert(1);
1898 self.environment_changed_this_frame
1899 .insert(EnvironmentQueryKey::ForcedColorsMode);
1900 }
1901
1902 pub(crate) fn record_committed_primary_pointer_type(&mut self, pointer_type: PointerType) {
1903 if self.committed_primary_pointer_type == Some(pointer_type) {
1904 return;
1905 }
1906 self.committed_primary_pointer_type = Some(pointer_type);
1907 self.environment_revisions
1908 .entry(EnvironmentQueryKey::PrimaryPointerType)
1909 .and_modify(|v| *v = v.saturating_add(1))
1910 .or_insert(1);
1911 self.environment_changed_this_frame
1912 .insert(EnvironmentQueryKey::PrimaryPointerType);
1913 }
1914
1915 pub(crate) fn record_committed_safe_area_insets(&mut self, insets: Option<Edges>) {
1916 if self.committed_safe_area_insets == insets {
1917 return;
1918 }
1919 self.committed_safe_area_insets = insets;
1920 self.environment_revisions
1921 .entry(EnvironmentQueryKey::SafeAreaInsets)
1922 .and_modify(|v| *v = v.saturating_add(1))
1923 .or_insert(1);
1924 self.environment_changed_this_frame
1925 .insert(EnvironmentQueryKey::SafeAreaInsets);
1926 }
1927
1928 pub(crate) fn record_committed_occlusion_insets(&mut self, insets: Option<Edges>) {
1929 if self.committed_occlusion_insets == insets {
1930 return;
1931 }
1932 self.committed_occlusion_insets = insets;
1933 self.environment_revisions
1934 .entry(EnvironmentQueryKey::OcclusionInsets)
1935 .and_modify(|v| *v = v.saturating_add(1))
1936 .or_insert(1);
1937 self.environment_changed_this_frame
1938 .insert(EnvironmentQueryKey::OcclusionInsets);
1939 }
1940
1941 pub(crate) fn record_committed_viewport_bounds(&mut self, bounds: Rect) {
1942 const EPS_PX: f32 = 0.5;
1945 let prev = self.committed_viewport_bounds.size;
1946 let next = bounds.size;
1947 let changed = (prev.width.0 - next.width.0).abs() > EPS_PX
1948 || (prev.height.0 - next.height.0).abs() > EPS_PX;
1949
1950 self.committed_viewport_bounds = bounds;
1951 if !changed {
1952 return;
1953 }
1954
1955 self.environment_revisions
1956 .entry(EnvironmentQueryKey::ViewportSize)
1957 .and_modify(|v| *v = v.saturating_add(1))
1958 .or_insert(1);
1959 self.environment_changed_this_frame
1960 .insert(EnvironmentQueryKey::ViewportSize);
1961 }
1962
1963 pub(crate) fn record_committed_scale_factor(&mut self, scale_factor: f32) {
1964 if (self.committed_scale_factor - scale_factor).abs() < 0.0001 {
1965 return;
1966 }
1967 self.committed_scale_factor = scale_factor;
1968 self.environment_revisions
1969 .entry(EnvironmentQueryKey::ScaleFactor)
1970 .and_modify(|v| *v = v.saturating_add(1))
1971 .or_insert(1);
1972 self.environment_changed_this_frame
1973 .insert(EnvironmentQueryKey::ScaleFactor);
1974 }
1975
1976 pub(crate) fn wants_continuous_frames(&self) -> bool {
1977 self.continuous_frames.load(Ordering::Relaxed) > 0
1978 }
1979
1980 pub(crate) fn begin_continuous_frames(
1981 &self,
1982 owner: Option<GlobalElementId>,
1983 ) -> ContinuousFrames {
1984 self.continuous_frames.fetch_add(1, Ordering::Relaxed);
1985 #[cfg(not(feature = "diagnostics"))]
1986 let _ = owner;
1987 #[cfg(feature = "diagnostics")]
1988 if let Some(owner) = owner {
1989 let mut owners = self
1990 .continuous_frame_owners
1991 .lock()
1992 .unwrap_or_else(|err| err.into_inner());
1993 owners
1994 .entry(owner)
1995 .and_modify(|count| *count = count.saturating_add(1))
1996 .or_insert(1);
1997 }
1998 ContinuousFrames {
1999 leases: self.continuous_frames.clone(),
2000 #[cfg(feature = "diagnostics")]
2001 owners: owner.map(|owner| (self.continuous_frame_owners.clone(), owner)),
2002 }
2003 }
2004
2005 #[cfg(feature = "diagnostics")]
2006 fn diagnostics_snapshot(&self) -> WindowElementDiagnosticsSnapshot {
2007 let invalidation_sort_key = |inv: Invalidation| match inv {
2008 Invalidation::Layout => 0u8,
2009 Invalidation::Paint => 1u8,
2010 Invalidation::HitTest => 2u8,
2011 Invalidation::HitTestOnly => 3u8,
2012 };
2013
2014 let env_key_label = |key: EnvironmentQueryKey| match key {
2015 EnvironmentQueryKey::ViewportSize => "viewport_size",
2016 EnvironmentQueryKey::ScaleFactor => "scale_factor",
2017 EnvironmentQueryKey::ColorScheme => "color_scheme",
2018 EnvironmentQueryKey::PrefersReducedMotion => "prefers_reduced_motion",
2019 EnvironmentQueryKey::TextScaleFactor => "text_scale_factor",
2020 EnvironmentQueryKey::PrefersReducedTransparency => "prefers_reduced_transparency",
2021 EnvironmentQueryKey::AccentColor => "accent_color",
2022 EnvironmentQueryKey::PrefersContrast => "prefers_contrast",
2023 EnvironmentQueryKey::ForcedColorsMode => "forced_colors_mode",
2024 EnvironmentQueryKey::PrimaryPointerType => "primary_pointer_type",
2025 EnvironmentQueryKey::SafeAreaInsets => "safe_area_insets",
2026 EnvironmentQueryKey::OcclusionInsets => "occlusion_insets",
2027 };
2028
2029 let bounds_for = |element: Option<GlobalElementId>| {
2030 element.and_then(|id| {
2031 self.prev_bounds
2032 .get(&id)
2033 .copied()
2034 .or_else(|| self.cur_bounds.get(&id).copied())
2035 })
2036 };
2037 let visual_bounds_for = |element: Option<GlobalElementId>| {
2038 element.and_then(|id| {
2039 self.prev_visual_bounds
2040 .get(&id)
2041 .copied()
2042 .or_else(|| self.cur_visual_bounds.get(&id).copied())
2043 .or_else(|| {
2044 self.prev_bounds
2045 .get(&id)
2046 .copied()
2047 .or_else(|| self.cur_bounds.get(&id).copied())
2048 })
2049 })
2050 };
2051 let node_for = |element: Option<GlobalElementId>| {
2052 element.and_then(|id| self.node_entry(id).map(|e| e.node))
2053 };
2054
2055 let layout_query_region_name_for = |element: GlobalElementId| -> Option<StdArc<str>> {
2056 let key = (element, TypeId::of::<LayoutQueryRegionMarker>());
2057 self.state_any_ref(&key)
2058 .and_then(|any| any.downcast_ref::<LayoutQueryRegionMarker>())
2059 .and_then(|marker| marker.name.clone())
2060 };
2061
2062 let mut layout_query_regions: Vec<LayoutQueryRegionDiagnosticsSnapshot> = self
2063 .nodes
2064 .iter()
2065 .filter(|(_, entry)| entry.last_seen_frame == self.prepared_frame)
2066 .map(|(&element, _)| element)
2067 .filter(|&element| self.has_state::<LayoutQueryRegionMarker>(element))
2068 .map(|element| {
2069 let committed_bounds = self.prev_bounds.get(&element).copied();
2070 let current_bounds = self.cur_bounds.get(&element).copied().or(committed_bounds);
2071 LayoutQueryRegionDiagnosticsSnapshot {
2072 region: element,
2073 name: layout_query_region_name_for(element),
2074 revision: self.layout_query_region_revision(element),
2075 changed_this_frame: self
2076 .layout_query_regions_changed_this_frame
2077 .contains(&element),
2078 committed_bounds,
2079 current_bounds,
2080 }
2081 })
2082 .collect();
2083 layout_query_regions.sort_by_key(|r| r.region.0);
2084
2085 let mut observed_layout_queries: Vec<ElementObservedLayoutQueriesDiagnosticsSnapshot> =
2086 self.observed_layout_queries_next
2087 .iter()
2088 .map(|(element, list)| {
2089 let deps_fingerprint = self.layout_query_deps_fingerprint_from_list(Some(list));
2090 let mut regions: Vec<ObservedLayoutQueryRegionDiagnosticsSnapshot> = list
2091 .iter()
2092 .map(
2093 |(region, inv)| ObservedLayoutQueryRegionDiagnosticsSnapshot {
2094 region: *region,
2095 invalidation: *inv,
2096 region_revision: self.layout_query_region_revision(*region),
2097 region_changed_this_frame: self
2098 .layout_query_regions_changed_this_frame
2099 .contains(region),
2100 region_name: layout_query_region_name_for(*region),
2101 region_committed_bounds: self.prev_bounds.get(region).copied(),
2102 },
2103 )
2104 .collect();
2105 regions.sort_by(|a, b| {
2106 a.region.0.cmp(&b.region.0).then_with(|| {
2107 let key = |inv: Invalidation| match inv {
2108 Invalidation::Layout => 0u8,
2109 Invalidation::Paint => 1u8,
2110 Invalidation::HitTest => 2u8,
2111 Invalidation::HitTestOnly => 3u8,
2112 };
2113 key(a.invalidation).cmp(&key(b.invalidation))
2114 })
2115 });
2116
2117 ElementObservedLayoutQueriesDiagnosticsSnapshot {
2118 element: *element,
2119 deps_fingerprint,
2120 regions,
2121 }
2122 })
2123 .collect();
2124 observed_layout_queries.sort_by_key(|e| e.element.0);
2125
2126 let mut observed_environment: Vec<ElementObservedEnvironmentDiagnosticsSnapshot> = self
2127 .observed_environment_next
2128 .iter()
2129 .map(|(element, list)| {
2130 let deps_fingerprint = self.environment_deps_fingerprint_from_list(Some(list));
2131 let mut keys: Vec<ObservedEnvironmentKeyDiagnosticsSnapshot> = list
2132 .iter()
2133 .map(|(key, inv)| ObservedEnvironmentKeyDiagnosticsSnapshot {
2134 key: StdArc::<str>::from(env_key_label(*key)),
2135 invalidation: *inv,
2136 key_revision: self.environment_revision(*key),
2137 key_changed_this_frame: self.environment_changed_this_frame.contains(key),
2138 })
2139 .collect();
2140 keys.sort_by(|a, b| {
2141 a.key.as_ref().cmp(b.key.as_ref()).then_with(|| {
2142 invalidation_sort_key(a.invalidation)
2143 .cmp(&invalidation_sort_key(b.invalidation))
2144 })
2145 });
2146
2147 ElementObservedEnvironmentDiagnosticsSnapshot {
2148 element: *element,
2149 deps_fingerprint,
2150 keys,
2151 }
2152 })
2153 .collect();
2154 observed_environment.sort_by_key(|e| e.element.0);
2155
2156 let environment = EnvironmentQueryDiagnosticsSnapshot {
2157 viewport_bounds: self.committed_viewport_bounds,
2158 scale_factor: self.committed_scale_factor,
2159 color_scheme: self.committed_color_scheme,
2160 prefers_reduced_motion: self.committed_prefers_reduced_motion,
2161 text_scale_factor: self.committed_text_scale_factor,
2162 prefers_reduced_transparency: self.committed_prefers_reduced_transparency,
2163 accent_color: self.committed_accent_color,
2164 contrast_preference: self.committed_contrast_preference,
2165 forced_colors_mode: self.committed_forced_colors_mode,
2166 primary_pointer_type: self.committed_primary_pointer_type(),
2167 safe_area_insets: self.committed_safe_area_insets,
2168 occlusion_insets: self.committed_occlusion_insets,
2169 };
2170
2171 let mut view_cache_reuse_roots: Vec<GlobalElementId> =
2172 self.view_cache_reuse_roots.iter().copied().collect();
2173 view_cache_reuse_roots.sort_by_key(|id| id.0);
2174
2175 let view_cache_reuse_root_element_counts: Vec<(GlobalElementId, u32)> =
2176 view_cache_reuse_roots
2177 .iter()
2178 .map(|root| {
2179 let count = self
2180 .view_cache_elements_rendered
2181 .get(root)
2182 .map(|v| v.len())
2183 .unwrap_or(0);
2184 (*root, count.min(u32::MAX as usize) as u32)
2185 })
2186 .collect();
2187
2188 const ELEMENT_SAMPLE: usize = 16;
2189 let view_cache_reuse_root_element_samples: Vec<ViewCacheReuseRootElementsSample> =
2190 view_cache_reuse_roots
2191 .iter()
2192 .map(|&root| {
2193 let elements = self.view_cache_elements_for_root(root).unwrap_or(&[]);
2194 let elements_len = elements.len().min(u32::MAX as usize) as u32;
2195 let elements_head: Vec<GlobalElementId> =
2196 elements.iter().take(ELEMENT_SAMPLE).copied().collect();
2197 let elements_tail: Vec<GlobalElementId> = if elements.len() > ELEMENT_SAMPLE {
2198 elements
2199 .iter()
2200 .skip(elements.len().saturating_sub(ELEMENT_SAMPLE))
2201 .copied()
2202 .collect()
2203 } else {
2204 Vec::new()
2205 };
2206
2207 ViewCacheReuseRootElementsSample {
2208 root,
2209 node: self.node_entry(root).map(|e| e.node),
2210 elements_len,
2211 elements_head,
2212 elements_tail,
2213 }
2214 })
2215 .collect();
2216
2217 let mut continuous_frame_leases: Vec<ContinuousFrameLeaseDiagnosticsSnapshot> = {
2218 let owners = self
2219 .continuous_frame_owners
2220 .lock()
2221 .unwrap_or_else(|err| err.into_inner());
2222 owners
2223 .iter()
2224 .map(
2225 |(&element, &count)| ContinuousFrameLeaseDiagnosticsSnapshot {
2226 element,
2227 count,
2228 debug_path: self.debug_path_for_element(element),
2229 },
2230 )
2231 .collect()
2232 };
2233 continuous_frame_leases.sort_by(|a, b| {
2234 b.count
2235 .cmp(&a.count)
2236 .then_with(|| a.element.0.cmp(&b.element.0))
2237 });
2238
2239 let mut animation_frame_request_roots: Vec<AnimationFrameRequestRootDiagnosticsSnapshot> =
2240 self.raf_notify_roots_debug
2241 .iter()
2242 .copied()
2243 .map(|element| AnimationFrameRequestRootDiagnosticsSnapshot {
2244 element,
2245 debug_path: self.debug_path_for_element(element),
2246 })
2247 .collect();
2248 animation_frame_request_roots.sort_by_key(|entry| entry.element.0);
2249
2250 let rendered_state_entries = self.rendered_state.len() as u64;
2251 let next_state_entries = self.next_state.len() as u64;
2252 let lag_state_frames = self.lag_state.len() as u64;
2253 let lag_state_entries_total = self
2254 .lag_state
2255 .iter()
2256 .fold(0u64, |acc, state| acc.saturating_add(state.len() as u64));
2257 let state_entries_total = rendered_state_entries
2258 .saturating_add(next_state_entries)
2259 .saturating_add(lag_state_entries_total);
2260 let nodes_count = self.nodes.len() as u64;
2261 let bounds_entries_total = (self.root_bounds.len() as u64)
2262 .saturating_add(self.prev_bounds.len() as u64)
2263 .saturating_add(self.cur_bounds.len() as u64)
2264 .saturating_add(self.prev_visual_bounds.len() as u64)
2265 .saturating_add(self.cur_visual_bounds.len() as u64);
2266 let timer_targets_count = self.timer_targets.len() as u64;
2267 let transient_events_count = self.transient_events.len() as u64;
2268 let view_cache_state_key_roots_count = (self.view_cache_state_keys_rendered.len() as u64)
2269 .saturating_add(self.view_cache_state_keys_next.len() as u64);
2270 let view_cache_state_key_entries_total = self
2271 .view_cache_state_keys_rendered
2272 .values()
2273 .fold(0u64, |acc, keys| acc.saturating_add(keys.len() as u64))
2274 .saturating_add(
2275 self.view_cache_state_keys_next
2276 .values()
2277 .fold(0u64, |acc, keys| acc.saturating_add(keys.len() as u64)),
2278 );
2279 let view_cache_element_roots_count = (self.view_cache_elements_rendered.len() as u64)
2280 .saturating_add(self.view_cache_elements_next.len() as u64);
2281 let view_cache_element_entries_total = self
2282 .view_cache_elements_rendered
2283 .values()
2284 .fold(0u64, |acc, elements| {
2285 acc.saturating_add(elements.len() as u64)
2286 })
2287 .saturating_add(
2288 self.view_cache_elements_next
2289 .values()
2290 .fold(0u64, |acc, elements| {
2291 acc.saturating_add(elements.len() as u64)
2292 }),
2293 );
2294 let view_cache_key_mismatch_roots_count = self.view_cache_key_mismatch_roots.len() as u64;
2295 let scratch_element_children_vec_pool_len =
2296 self.scratch_element_children_vec_pool.len() as u64;
2297 let scratch_element_children_vec_pool_capacity_total = self
2298 .scratch_element_children_vec_pool
2299 .iter()
2300 .fold(0u64, |acc, vec| acc.saturating_add(vec.capacity() as u64));
2301 let scratch_element_children_vec_pool_bytes_estimate_total = self
2302 .scratch_element_children_vec_pool
2303 .iter()
2304 .fold(0u64, |acc, vec| {
2305 acc.saturating_add(
2306 (vec.capacity() as u64)
2307 .saturating_mul(std::mem::size_of::<AnyElement>() as u64),
2308 )
2309 });
2310
2311 const KEEP_ALIVE_ROOT_SAMPLE: usize = 16;
2312 let mut retained_keep_alive_roots: Vec<NodeId> = self
2313 .retained_virtual_list_keep_alive_roots
2314 .iter()
2315 .copied()
2316 .collect();
2317 retained_keep_alive_roots.sort_by_key(|n| n.data().as_ffi());
2318 let retained_keep_alive_roots_len =
2319 retained_keep_alive_roots.len().min(u32::MAX as usize) as u32;
2320 let retained_keep_alive_roots_head: Vec<NodeId> = retained_keep_alive_roots
2321 .iter()
2322 .take(KEEP_ALIVE_ROOT_SAMPLE)
2323 .copied()
2324 .collect();
2325 let retained_keep_alive_roots_tail: Vec<NodeId> =
2326 if retained_keep_alive_roots.len() > KEEP_ALIVE_ROOT_SAMPLE {
2327 retained_keep_alive_roots
2328 .iter()
2329 .skip(
2330 retained_keep_alive_roots
2331 .len()
2332 .saturating_sub(KEEP_ALIVE_ROOT_SAMPLE),
2333 )
2334 .copied()
2335 .collect()
2336 } else {
2337 Vec::new()
2338 };
2339
2340 WindowElementDiagnosticsSnapshot {
2341 focused_element: self.focused_element,
2342 focused_element_node: node_for(self.focused_element),
2343 focused_element_bounds: bounds_for(self.focused_element),
2344 focused_element_visual_bounds: visual_bounds_for(self.focused_element),
2345 active_text_selection: self
2346 .active_text_selection
2347 .map(|sel| (sel.root, sel.element)),
2348 hovered_pressable: self.hovered_pressable,
2349 hovered_pressable_node: self.hovered_pressable_node,
2350 hovered_pressable_bounds: bounds_for(self.hovered_pressable),
2351 hovered_pressable_visual_bounds: visual_bounds_for(self.hovered_pressable),
2352 pressed_pressable: self.pressed_pressable,
2353 pressed_pressable_node: self.pressed_pressable_node,
2354 pressed_pressable_bounds: bounds_for(self.pressed_pressable),
2355 pressed_pressable_visual_bounds: visual_bounds_for(self.pressed_pressable),
2356 hovered_hover_region: self.hovered_hover_region,
2357 wants_continuous_frames: self.wants_continuous_frames(),
2358 continuous_frame_leases,
2359 animation_frame_request_roots,
2360 observed_models: self
2361 .observed_models_next
2362 .iter()
2363 .map(|(element, list)| {
2364 (
2365 *element,
2366 list.iter()
2367 .map(|(model, inv)| (model.data().as_ffi(), *inv))
2368 .collect(),
2369 )
2370 })
2371 .collect(),
2372 observed_globals: self
2373 .observed_globals_next
2374 .iter()
2375 .map(|(element, list)| {
2376 (
2377 *element,
2378 list.iter()
2379 .map(|(ty, inv)| (format!("{ty:?}"), *inv))
2380 .collect(),
2381 )
2382 })
2383 .collect(),
2384 observed_layout_queries,
2385 layout_query_regions,
2386 environment,
2387 observed_environment,
2388 view_cache_reuse_roots,
2389 view_cache_reuse_root_element_counts,
2390 view_cache_reuse_root_element_samples,
2391 rendered_state_entries,
2392 next_state_entries,
2393 lag_state_frames,
2394 lag_state_entries_total,
2395 state_entries_total,
2396 nodes_count,
2397 bounds_entries_total,
2398 timer_targets_count,
2399 transient_events_count,
2400 view_cache_state_key_roots_count,
2401 view_cache_state_key_entries_total,
2402 view_cache_element_roots_count,
2403 view_cache_element_entries_total,
2404 view_cache_key_mismatch_roots_count,
2405 scratch_element_children_vec_pool_len,
2406 scratch_element_children_vec_pool_capacity_total,
2407 scratch_element_children_vec_pool_bytes_estimate_total,
2408 retained_keep_alive_roots_len,
2409 retained_keep_alive_roots_head,
2410 retained_keep_alive_roots_tail,
2411 node_entry_root_overwrites: self.debug_node_entry_root_overwrites.clone(),
2412 overlay_placement: self.overlay_placement.clone(),
2413 }
2414 }
2415
2416 #[cfg(feature = "diagnostics")]
2417 pub(crate) fn record_overlay_placement_anchored_panel(
2418 &mut self,
2419 frame_id: FrameId,
2420 overlay_root_name: Option<StdArc<str>>,
2421 anchor_element: Option<GlobalElementId>,
2422 content_element: Option<GlobalElementId>,
2423 trace: AnchoredPanelLayoutTrace,
2424 ) {
2425 let name = overlay_root_name.clone();
2426 let next = OverlayPlacementDiagnosticsRecord::AnchoredPanel(
2427 OverlayAnchoredPanelPlacementDiagnosticsRecord {
2428 frame_id,
2429 overlay_root_name,
2430 anchor_element,
2431 content_element,
2432 trace,
2433 },
2434 );
2435
2436 if let Some(existing) = self
2437 .overlay_placement
2438 .iter_mut()
2439 .rev()
2440 .find(|rec| match rec {
2441 OverlayPlacementDiagnosticsRecord::AnchoredPanel(r) => {
2442 r.overlay_root_name == name
2443 && r.anchor_element == anchor_element
2444 && r.content_element == content_element
2445 }
2446 _ => false,
2447 })
2448 {
2449 *existing = next;
2450 return;
2451 }
2452
2453 self.overlay_placement.push(next);
2454 }
2455
2456 #[cfg(feature = "diagnostics")]
2457 #[allow(clippy::too_many_arguments)]
2458 pub(crate) fn record_overlay_placement_placed_rect(
2459 &mut self,
2460 frame_id: FrameId,
2461 overlay_root_name: Option<StdArc<str>>,
2462 anchor_element: Option<GlobalElementId>,
2463 content_element: Option<GlobalElementId>,
2464 outer: Rect,
2465 anchor: Rect,
2466 placed: Rect,
2467 side: Option<Side>,
2468 ) {
2469 let name = overlay_root_name.clone();
2470 let next =
2471 OverlayPlacementDiagnosticsRecord::PlacedRect(OverlayPlacedRectDiagnosticsRecord {
2472 frame_id,
2473 overlay_root_name,
2474 anchor_element,
2475 content_element,
2476 outer,
2477 anchor,
2478 placed,
2479 side,
2480 });
2481
2482 if let Some(existing) = self
2483 .overlay_placement
2484 .iter_mut()
2485 .rev()
2486 .find(|rec| match rec {
2487 OverlayPlacementDiagnosticsRecord::PlacedRect(r) => {
2488 r.overlay_root_name == name
2489 && r.anchor_element == anchor_element
2490 && r.content_element == content_element
2491 }
2492 _ => false,
2493 })
2494 {
2495 *existing = next;
2496 return;
2497 }
2498
2499 self.overlay_placement.push(next);
2500 }
2501
2502 #[cfg(feature = "diagnostics")]
2503 pub(crate) fn record_debug_root(
2504 &mut self,
2505 frame_id: FrameId,
2506 root: GlobalElementId,
2507 name: &str,
2508 ) {
2509 self.debug_identity.entries.insert(
2510 root,
2511 DebugIdentityEntry {
2512 parent: None,
2513 segment: DebugIdentitySegment::Root {
2514 name: StdArc::<str>::from(name),
2515 },
2516 last_seen_frame: frame_id,
2517 },
2518 );
2519 }
2520
2521 #[cfg(feature = "diagnostics")]
2522 #[allow(clippy::too_many_arguments)]
2523 pub(crate) fn record_debug_child(
2524 &mut self,
2525 frame_id: FrameId,
2526 parent: GlobalElementId,
2527 child: GlobalElementId,
2528 file: &'static str,
2529 line: u32,
2530 column: u32,
2531 key_hash: Option<u64>,
2532 name: Option<&str>,
2533 slot: u64,
2534 ) {
2535 self.debug_identity.entries.insert(
2536 child,
2537 DebugIdentityEntry {
2538 parent: Some(parent),
2539 segment: DebugIdentitySegment::Child {
2540 file,
2541 line,
2542 column,
2543 key_hash,
2544 name: name.map(StdArc::<str>::from),
2545 slot,
2546 },
2547 last_seen_frame: frame_id,
2548 },
2549 );
2550 }
2551
2552 #[cfg(feature = "diagnostics")]
2553 pub(crate) fn touch_debug_identity_for_element(
2554 &mut self,
2555 frame_id: FrameId,
2556 element: GlobalElementId,
2557 ) {
2558 let mut cur = element;
2568 let mut guard = 0usize;
2569 while guard < 256 {
2570 guard += 1;
2571 let Some(entry) = self.debug_identity.entries.get_mut(&cur) else {
2572 break;
2573 };
2574 if entry.last_seen_frame == frame_id {
2575 break;
2576 }
2577 entry.last_seen_frame = frame_id;
2578 let Some(parent) = entry.parent else {
2579 break;
2580 };
2581 cur = parent;
2582 }
2583 }
2584
2585 #[cfg(feature = "diagnostics")]
2586 pub(crate) fn debug_path_for_element(&self, element: GlobalElementId) -> Option<String> {
2587 let mut segments: Vec<String> = Vec::new();
2588 let mut cur = element;
2589 let mut guard = 0usize;
2590 while guard < 256 {
2591 guard += 1;
2592 let entry = self.debug_identity.entries.get(&cur)?;
2593 segments.push(entry.segment.format());
2594 if let Some(parent) = entry.parent {
2595 cur = parent;
2596 continue;
2597 }
2598 break;
2599 }
2600 segments.reverse();
2601 Some(format!("{} ({:#x})", segments.join("."), element.0))
2602 }
2603}
2604
2605#[must_use]
2606pub struct ContinuousFrames {
2607 leases: Arc<AtomicUsize>,
2608 #[cfg(feature = "diagnostics")]
2609 owners: Option<(Arc<Mutex<HashMap<GlobalElementId, u32>>>, GlobalElementId)>,
2610}
2611
2612impl Drop for ContinuousFrames {
2613 fn drop(&mut self) {
2614 let _ = self
2615 .leases
2616 .fetch_update(Ordering::Relaxed, Ordering::Relaxed, |v| v.checked_sub(1));
2617 #[cfg(feature = "diagnostics")]
2618 if let Some((owners, owner)) = self.owners.as_ref() {
2619 let mut owners = owners.lock().unwrap_or_else(|err| err.into_inner());
2620 if let Some(count) = owners.get_mut(owner) {
2621 if *count <= 1 {
2622 owners.remove(owner);
2623 } else {
2624 *count -= 1;
2625 }
2626 }
2627 }
2628 }
2629}
2630
2631#[cfg(feature = "diagnostics")]
2632#[derive(Default)]
2633struct DebugIdentityRegistry {
2634 entries: HashMap<GlobalElementId, DebugIdentityEntry>,
2635}
2636
2637#[cfg(feature = "diagnostics")]
2638struct DebugIdentityEntry {
2639 parent: Option<GlobalElementId>,
2640 segment: DebugIdentitySegment,
2641 last_seen_frame: FrameId,
2642}
2643
2644#[cfg(feature = "diagnostics")]
2645enum DebugIdentitySegment {
2646 Root {
2647 name: StdArc<str>,
2648 },
2649 Child {
2650 file: &'static str,
2651 line: u32,
2652 column: u32,
2653 key_hash: Option<u64>,
2654 name: Option<StdArc<str>>,
2655 slot: u64,
2656 },
2657}
2658
2659#[cfg(feature = "diagnostics")]
2660impl DebugIdentitySegment {
2661 fn format(&self) -> String {
2662 match self {
2663 DebugIdentitySegment::Root { name } => format!("root[{name}]"),
2664 DebugIdentitySegment::Child {
2665 file,
2666 line,
2667 column,
2668 key_hash,
2669 name,
2670 slot,
2671 } => {
2672 if let Some(name) = name.as_deref() {
2673 format!("{file}:{line}:{column}[name={name}]")
2674 } else if let Some(k) = key_hash {
2675 format!("{file}:{line}:{column}[key={k:#x}]")
2676 } else {
2677 format!("{file}:{line}:{column}[slot={slot}]")
2678 }
2679 }
2680 }
2681 }
2682}
2683
2684#[cfg(test)]
2685mod tests {
2686 use super::*;
2687 use fret_core::{Point, Px, Size};
2688
2689 #[test]
2690 fn primary_pointer_type_defaults_to_unknown_until_observed() {
2691 let mut state = WindowElementState::default();
2692 assert_eq!(state.committed_primary_pointer_type(), PointerType::Unknown);
2693
2694 state.record_committed_primary_pointer_type(PointerType::Touch);
2695 assert_eq!(state.committed_primary_pointer_type(), PointerType::Touch);
2696 }
2697
2698 #[test]
2699 fn transient_events_survive_one_frame_and_clear_on_read() {
2700 let mut state = WindowElementState::default();
2701 let element = GlobalElementId(123);
2702 let key = 0xDEAD_BEEF;
2703
2704 state.prepare_for_frame(FrameId(1), 0);
2705 state.record_transient_event(element, key);
2706
2707 state.prepare_for_frame(FrameId(2), 0);
2710 assert!(state.take_transient_event(element, key));
2711 assert!(!state.take_transient_event(element, key));
2712 }
2713
2714 #[test]
2715 fn transient_events_prune_after_two_frames_if_not_consumed() {
2716 let mut state = WindowElementState::default();
2717 let element = GlobalElementId(123);
2718 let key = 0xDEAD_BEEF;
2719
2720 state.prepare_for_frame(FrameId(10), 0);
2721 state.record_transient_event(element, key);
2722
2723 state.prepare_for_frame(FrameId(11), 0);
2725 assert!(state.take_transient_event(element, key));
2726
2727 state.record_transient_event(element, key);
2729 state.prepare_for_frame(FrameId(12), 0);
2730 assert!(state.take_transient_event(element, key));
2731
2732 state.record_transient_event(element, key);
2733 state.prepare_for_frame(FrameId(13), 0);
2734 state.prepare_for_frame(FrameId(14), 0);
2735 assert!(!state.take_transient_event(element, key));
2736 }
2737
2738 #[test]
2739 #[should_panic(expected = "ownership root overwrite detected")]
2740 fn strict_ownership_panics_on_root_overwrite() {
2741 let mut state = WindowElementState::default();
2742 state.set_strict_ownership(true);
2743
2744 let element = GlobalElementId(123);
2745 state.set_node_entry(
2746 element,
2747 NodeEntry {
2748 node: NodeId::default(),
2749 last_seen_frame: FrameId(1),
2750 root: GlobalElementId(1),
2751 },
2752 );
2753 state.set_node_entry(
2754 element,
2755 NodeEntry {
2756 node: NodeId::default(),
2757 last_seen_frame: FrameId(1),
2758 root: GlobalElementId(2),
2759 },
2760 );
2761 }
2762
2763 #[test]
2764 fn selectable_text_span_bounds_can_be_read_by_element() {
2765 let window = AppWindowId::default();
2766 let element = GlobalElementId(123);
2767 let node = NodeId::default();
2768 let expected = crate::element::SelectableTextInteractiveSpanBounds {
2769 range: 1..4,
2770 tag: std::sync::Arc::<str>::from("link"),
2771 bounds_local: Rect::new(
2772 Point::new(Px(10.0), Px(20.0)),
2773 Size::new(Px(30.0), Px(12.0)),
2774 ),
2775 };
2776
2777 let mut runtime = ElementRuntime::new();
2778 runtime.prepare_window_for_frame(window, FrameId(1));
2779 {
2780 let state = runtime.for_window_mut(window);
2781 state.set_node_entry(
2782 element,
2783 NodeEntry {
2784 node,
2785 last_seen_frame: FrameId(1),
2786 root: GlobalElementId(1),
2787 },
2788 );
2789 state.with_state_mut(
2790 element,
2791 crate::element::SelectableTextState::default,
2792 |span_state| {
2793 span_state.interactive_span_bounds = vec![expected.clone()];
2794 },
2795 );
2796 }
2797
2798 assert_eq!(
2799 runtime.selectable_text_interactive_span_bounds_for_node(window, node),
2800 Some(vec![expected.clone()])
2801 );
2802 assert_eq!(
2803 runtime.selectable_text_interactive_span_bounds_for_element(window, element),
2804 Some(vec![expected])
2805 );
2806 }
2807
2808 #[test]
2809 fn primary_pointer_type_is_unknown_until_committed() {
2810 let state = WindowElementState::default();
2811 assert_eq!(state.committed_primary_pointer_type(), PointerType::Unknown);
2812 }
2813
2814 #[test]
2815 fn primary_pointer_type_revision_increments_on_change() {
2816 let mut state = WindowElementState::default();
2817 state.prepare_for_frame(FrameId(1), 0);
2818
2819 assert!(state.environment_revisions.is_empty());
2820 state.record_committed_primary_pointer_type(PointerType::Mouse);
2821 assert!(
2822 state
2823 .environment_changed_this_frame
2824 .contains(&EnvironmentQueryKey::PrimaryPointerType)
2825 );
2826 let first_revision = state
2827 .environment_revisions
2828 .get(&EnvironmentQueryKey::PrimaryPointerType)
2829 .copied()
2830 .unwrap();
2831
2832 state.record_committed_primary_pointer_type(PointerType::Mouse);
2834 assert_eq!(
2835 state
2836 .environment_revisions
2837 .get(&EnvironmentQueryKey::PrimaryPointerType)
2838 .copied()
2839 .unwrap(),
2840 first_revision
2841 );
2842
2843 state.record_committed_primary_pointer_type(PointerType::Touch);
2844 assert_eq!(
2845 state
2846 .environment_revisions
2847 .get(&EnvironmentQueryKey::PrimaryPointerType)
2848 .copied()
2849 .unwrap(),
2850 first_revision + 1
2851 );
2852 }
2853
2854 #[test]
2855 fn color_scheme_revision_increments_on_change() {
2856 let mut state = WindowElementState::default();
2857 state.prepare_for_frame(FrameId(1), 0);
2858
2859 assert!(state.environment_revisions.is_empty());
2860 state.set_committed_color_scheme(Some(ColorScheme::Light));
2861 assert!(
2862 state
2863 .environment_changed_this_frame
2864 .contains(&EnvironmentQueryKey::ColorScheme)
2865 );
2866 let first_revision = state
2867 .environment_revisions
2868 .get(&EnvironmentQueryKey::ColorScheme)
2869 .copied()
2870 .unwrap();
2871
2872 state.set_committed_color_scheme(Some(ColorScheme::Light));
2874 assert_eq!(
2875 state
2876 .environment_revisions
2877 .get(&EnvironmentQueryKey::ColorScheme)
2878 .copied()
2879 .unwrap(),
2880 first_revision
2881 );
2882
2883 state.set_committed_color_scheme(Some(ColorScheme::Dark));
2884 assert_eq!(
2885 state
2886 .environment_revisions
2887 .get(&EnvironmentQueryKey::ColorScheme)
2888 .copied()
2889 .unwrap(),
2890 first_revision + 1
2891 );
2892 }
2893
2894 #[test]
2895 fn prefers_contrast_revision_increments_on_change() {
2896 let mut state = WindowElementState::default();
2897 state.prepare_for_frame(FrameId(1), 0);
2898
2899 assert!(state.environment_revisions.is_empty());
2900 state.set_committed_contrast_preference(Some(ContrastPreference::NoPreference));
2901 assert!(
2902 state
2903 .environment_changed_this_frame
2904 .contains(&EnvironmentQueryKey::PrefersContrast)
2905 );
2906 let first_revision = state
2907 .environment_revisions
2908 .get(&EnvironmentQueryKey::PrefersContrast)
2909 .copied()
2910 .unwrap();
2911
2912 state.set_committed_contrast_preference(Some(ContrastPreference::NoPreference));
2914 assert_eq!(
2915 state
2916 .environment_revisions
2917 .get(&EnvironmentQueryKey::PrefersContrast)
2918 .copied()
2919 .unwrap(),
2920 first_revision
2921 );
2922
2923 state.set_committed_contrast_preference(Some(ContrastPreference::More));
2924 assert_eq!(
2925 state
2926 .environment_revisions
2927 .get(&EnvironmentQueryKey::PrefersContrast)
2928 .copied()
2929 .unwrap(),
2930 first_revision + 1
2931 );
2932 }
2933
2934 #[test]
2935 fn forced_colors_mode_revision_increments_on_change() {
2936 let mut state = WindowElementState::default();
2937 state.prepare_for_frame(FrameId(1), 0);
2938
2939 assert!(state.environment_revisions.is_empty());
2940 state.set_committed_forced_colors_mode(Some(ForcedColorsMode::None));
2941 assert!(
2942 state
2943 .environment_changed_this_frame
2944 .contains(&EnvironmentQueryKey::ForcedColorsMode)
2945 );
2946 let first_revision = state
2947 .environment_revisions
2948 .get(&EnvironmentQueryKey::ForcedColorsMode)
2949 .copied()
2950 .unwrap();
2951
2952 state.set_committed_forced_colors_mode(Some(ForcedColorsMode::None));
2954 assert_eq!(
2955 state
2956 .environment_revisions
2957 .get(&EnvironmentQueryKey::ForcedColorsMode)
2958 .copied()
2959 .unwrap(),
2960 first_revision
2961 );
2962
2963 state.set_committed_forced_colors_mode(Some(ForcedColorsMode::Active));
2964 assert_eq!(
2965 state
2966 .environment_revisions
2967 .get(&EnvironmentQueryKey::ForcedColorsMode)
2968 .copied()
2969 .unwrap(),
2970 first_revision + 1
2971 );
2972 }
2973
2974 #[test]
2975 fn safe_area_insets_is_none_until_committed() {
2976 let state = WindowElementState::default();
2977 assert_eq!(state.committed_safe_area_insets(), None);
2978 }
2979
2980 #[test]
2981 fn safe_area_insets_revision_increments_on_change() {
2982 let mut state = WindowElementState::default();
2983 state.prepare_for_frame(FrameId(1), 0);
2984
2985 assert!(state.environment_revisions.is_empty());
2986 state.record_committed_safe_area_insets(Some(Edges::all(fret_core::Px(4.0))));
2987 assert!(
2988 state
2989 .environment_changed_this_frame
2990 .contains(&EnvironmentQueryKey::SafeAreaInsets)
2991 );
2992 let first_revision = state
2993 .environment_revisions
2994 .get(&EnvironmentQueryKey::SafeAreaInsets)
2995 .copied()
2996 .unwrap();
2997
2998 state.record_committed_safe_area_insets(Some(Edges::all(fret_core::Px(4.0))));
3000 assert_eq!(
3001 state
3002 .environment_revisions
3003 .get(&EnvironmentQueryKey::SafeAreaInsets)
3004 .copied()
3005 .unwrap(),
3006 first_revision
3007 );
3008
3009 state.record_committed_safe_area_insets(None);
3010 assert_eq!(
3011 state
3012 .environment_revisions
3013 .get(&EnvironmentQueryKey::SafeAreaInsets)
3014 .copied()
3015 .unwrap(),
3016 first_revision + 1
3017 );
3018 }
3019
3020 #[test]
3021 fn occlusion_insets_is_none_until_committed() {
3022 let state = WindowElementState::default();
3023 assert_eq!(state.committed_occlusion_insets(), None);
3024 }
3025
3026 #[test]
3027 fn occlusion_insets_revision_increments_on_change() {
3028 let mut state = WindowElementState::default();
3029 state.prepare_for_frame(FrameId(1), 0);
3030
3031 assert!(state.environment_revisions.is_empty());
3032 state.record_committed_occlusion_insets(Some(Edges::all(fret_core::Px(16.0))));
3033 assert!(
3034 state
3035 .environment_changed_this_frame
3036 .contains(&EnvironmentQueryKey::OcclusionInsets)
3037 );
3038 let first_revision = state
3039 .environment_revisions
3040 .get(&EnvironmentQueryKey::OcclusionInsets)
3041 .copied()
3042 .unwrap();
3043
3044 state.record_committed_occlusion_insets(Some(Edges::all(fret_core::Px(16.0))));
3046 assert_eq!(
3047 state
3048 .environment_revisions
3049 .get(&EnvironmentQueryKey::OcclusionInsets)
3050 .copied()
3051 .unwrap(),
3052 first_revision
3053 );
3054
3055 state.record_committed_occlusion_insets(None);
3056 assert_eq!(
3057 state
3058 .environment_revisions
3059 .get(&EnvironmentQueryKey::OcclusionInsets)
3060 .copied()
3061 .unwrap(),
3062 first_revision + 1
3063 );
3064 }
3065}