use std::any::{Any, TypeId};
use std::collections::{HashMap, HashSet};
#[cfg(feature = "diagnostics")]
use std::sync::Mutex;
use std::sync::{
Arc,
atomic::{AtomicUsize, Ordering},
};
use fret_core::{
AppWindowId, Color, ColorScheme, ContrastPreference, Edges, ForcedColorsMode, NodeId,
PointerType, Rect,
};
use fret_runtime::{FrameId, ModelId, TimerToken};
#[cfg(feature = "diagnostics")]
use slotmap::Key as _;
#[cfg(feature = "diagnostics")]
use std::sync::Arc as StdArc;
use crate::element::AnyElement;
#[cfg(feature = "diagnostics")]
use crate::overlay_placement::{AnchoredPanelLayoutTrace, Side};
use crate::widget::Invalidation;
use super::GlobalElementId;
use super::hash::stable_hash;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub(crate) enum TimerTarget {
Element(GlobalElementId),
Node(NodeId),
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub(crate) enum EnvironmentQueryKey {
ViewportSize,
ScaleFactor,
ColorScheme,
PrefersReducedMotion,
TextScaleFactor,
PrefersReducedTransparency,
AccentColor,
PrefersContrast,
ForcedColorsMode,
PrimaryPointerType,
SafeAreaInsets,
OcclusionInsets,
}
#[cfg(feature = "diagnostics")]
#[derive(Debug, Clone)]
pub struct WindowElementDiagnosticsSnapshot {
pub focused_element: Option<GlobalElementId>,
pub focused_element_node: Option<NodeId>,
pub focused_element_bounds: Option<Rect>,
pub focused_element_visual_bounds: Option<Rect>,
pub active_text_selection: Option<(GlobalElementId, GlobalElementId)>,
pub hovered_pressable: Option<GlobalElementId>,
pub hovered_pressable_node: Option<NodeId>,
pub hovered_pressable_bounds: Option<Rect>,
pub hovered_pressable_visual_bounds: Option<Rect>,
pub pressed_pressable: Option<GlobalElementId>,
pub pressed_pressable_node: Option<NodeId>,
pub pressed_pressable_bounds: Option<Rect>,
pub pressed_pressable_visual_bounds: Option<Rect>,
pub hovered_hover_region: Option<GlobalElementId>,
pub wants_continuous_frames: bool,
pub continuous_frame_leases: Vec<ContinuousFrameLeaseDiagnosticsSnapshot>,
pub animation_frame_request_roots: Vec<AnimationFrameRequestRootDiagnosticsSnapshot>,
pub observed_models: Vec<(GlobalElementId, Vec<(u64, Invalidation)>)>,
pub observed_globals: Vec<(GlobalElementId, Vec<(String, Invalidation)>)>,
pub observed_layout_queries: Vec<ElementObservedLayoutQueriesDiagnosticsSnapshot>,
pub layout_query_regions: Vec<LayoutQueryRegionDiagnosticsSnapshot>,
pub environment: EnvironmentQueryDiagnosticsSnapshot,
pub observed_environment: Vec<ElementObservedEnvironmentDiagnosticsSnapshot>,
pub view_cache_reuse_roots: Vec<GlobalElementId>,
pub view_cache_reuse_root_element_counts: Vec<(GlobalElementId, u32)>,
pub view_cache_reuse_root_element_samples: Vec<ViewCacheReuseRootElementsSample>,
pub rendered_state_entries: u64,
pub next_state_entries: u64,
pub lag_state_frames: u64,
pub lag_state_entries_total: u64,
pub state_entries_total: u64,
pub nodes_count: u64,
pub bounds_entries_total: u64,
pub timer_targets_count: u64,
pub transient_events_count: u64,
pub view_cache_state_key_roots_count: u64,
pub view_cache_state_key_entries_total: u64,
pub view_cache_element_roots_count: u64,
pub view_cache_element_entries_total: u64,
pub view_cache_key_mismatch_roots_count: u64,
pub scratch_element_children_vec_pool_len: u64,
pub scratch_element_children_vec_pool_capacity_total: u64,
pub scratch_element_children_vec_pool_bytes_estimate_total: u64,
pub retained_keep_alive_roots_len: u32,
pub retained_keep_alive_roots_head: Vec<NodeId>,
pub retained_keep_alive_roots_tail: Vec<NodeId>,
pub node_entry_root_overwrites: Vec<NodeEntryRootOverwrite>,
pub overlay_placement: Vec<OverlayPlacementDiagnosticsRecord>,
}
#[cfg(feature = "diagnostics")]
#[derive(Debug, Clone)]
pub struct ContinuousFrameLeaseDiagnosticsSnapshot {
pub element: GlobalElementId,
pub count: u32,
pub debug_path: Option<String>,
}
#[cfg(feature = "diagnostics")]
#[derive(Debug, Clone)]
pub struct AnimationFrameRequestRootDiagnosticsSnapshot {
pub element: GlobalElementId,
pub debug_path: Option<String>,
}
#[cfg(feature = "diagnostics")]
#[allow(clippy::large_enum_variant)]
#[derive(Debug, Clone)]
pub enum OverlayPlacementDiagnosticsRecord {
AnchoredPanel(OverlayAnchoredPanelPlacementDiagnosticsRecord),
PlacedRect(OverlayPlacedRectDiagnosticsRecord),
}
#[cfg(feature = "diagnostics")]
#[derive(Debug, Clone)]
pub struct OverlayAnchoredPanelPlacementDiagnosticsRecord {
pub frame_id: FrameId,
pub overlay_root_name: Option<StdArc<str>>,
pub anchor_element: Option<GlobalElementId>,
pub content_element: Option<GlobalElementId>,
pub trace: AnchoredPanelLayoutTrace,
}
#[cfg(feature = "diagnostics")]
#[derive(Debug, Clone)]
pub struct OverlayPlacedRectDiagnosticsRecord {
pub frame_id: FrameId,
pub overlay_root_name: Option<StdArc<str>>,
pub anchor_element: Option<GlobalElementId>,
pub content_element: Option<GlobalElementId>,
pub outer: Rect,
pub anchor: Rect,
pub placed: Rect,
pub side: Option<Side>,
}
#[cfg(feature = "diagnostics")]
#[derive(Debug, Clone)]
pub struct LayoutQueryRegionDiagnosticsSnapshot {
pub region: GlobalElementId,
pub name: Option<StdArc<str>>,
pub revision: u64,
pub changed_this_frame: bool,
pub committed_bounds: Option<Rect>,
pub current_bounds: Option<Rect>,
}
#[cfg(feature = "diagnostics")]
#[derive(Debug, Clone)]
pub struct ElementObservedLayoutQueriesDiagnosticsSnapshot {
pub element: GlobalElementId,
pub deps_fingerprint: u64,
pub regions: Vec<ObservedLayoutQueryRegionDiagnosticsSnapshot>,
}
#[cfg(feature = "diagnostics")]
#[derive(Debug, Clone)]
pub struct ObservedLayoutQueryRegionDiagnosticsSnapshot {
pub region: GlobalElementId,
pub invalidation: Invalidation,
pub region_revision: u64,
pub region_changed_this_frame: bool,
pub region_name: Option<StdArc<str>>,
pub region_committed_bounds: Option<Rect>,
}
#[cfg(feature = "diagnostics")]
#[derive(Debug, Clone)]
pub struct EnvironmentQueryDiagnosticsSnapshot {
pub viewport_bounds: Rect,
pub scale_factor: f32,
pub color_scheme: Option<ColorScheme>,
pub prefers_reduced_motion: Option<bool>,
pub text_scale_factor: Option<f32>,
pub prefers_reduced_transparency: Option<bool>,
pub accent_color: Option<Color>,
pub contrast_preference: Option<ContrastPreference>,
pub forced_colors_mode: Option<ForcedColorsMode>,
pub primary_pointer_type: PointerType,
pub safe_area_insets: Option<Edges>,
pub occlusion_insets: Option<Edges>,
}
#[cfg(feature = "diagnostics")]
#[derive(Debug, Clone)]
pub struct ElementObservedEnvironmentDiagnosticsSnapshot {
pub element: GlobalElementId,
pub deps_fingerprint: u64,
pub keys: Vec<ObservedEnvironmentKeyDiagnosticsSnapshot>,
}
#[cfg(feature = "diagnostics")]
#[derive(Debug, Clone)]
pub struct ObservedEnvironmentKeyDiagnosticsSnapshot {
pub key: StdArc<str>,
pub invalidation: Invalidation,
pub key_revision: u64,
pub key_changed_this_frame: bool,
}
#[cfg(feature = "diagnostics")]
#[derive(Debug, Clone)]
pub struct ViewCacheReuseRootElementsSample {
pub root: GlobalElementId,
pub node: Option<NodeId>,
pub elements_len: u32,
pub elements_head: Vec<GlobalElementId>,
pub elements_tail: Vec<GlobalElementId>,
}
#[derive(Default)]
pub struct ElementRuntime {
windows: HashMap<AppWindowId, WindowElementState>,
gc_lag_frames: u64,
}
impl ElementRuntime {
pub fn new() -> Self {
Self {
windows: HashMap::new(),
gc_lag_frames: 2,
}
}
pub fn gc_lag_frames(&self) -> u64 {
self.gc_lag_frames
}
pub fn set_gc_lag_frames(&mut self, frames: u64) {
self.gc_lag_frames = frames;
}
pub fn set_window_prefers_reduced_motion(
&mut self,
window: AppWindowId,
prefers_reduced_motion: Option<bool>,
) {
self.for_window_mut(window)
.set_committed_prefers_reduced_motion(prefers_reduced_motion);
}
pub fn set_window_color_scheme(&mut self, window: AppWindowId, scheme: Option<ColorScheme>) {
self.for_window_mut(window)
.set_committed_color_scheme(scheme);
}
pub fn set_window_contrast_preference(
&mut self,
window: AppWindowId,
value: Option<ContrastPreference>,
) {
self.for_window_mut(window)
.set_committed_contrast_preference(value);
}
pub fn set_window_forced_colors_mode(
&mut self,
window: AppWindowId,
value: Option<ForcedColorsMode>,
) {
self.for_window_mut(window)
.set_committed_forced_colors_mode(value);
}
pub fn set_window_primary_pointer_type(
&mut self,
window: AppWindowId,
pointer_type: PointerType,
) {
self.for_window_mut(window)
.record_committed_primary_pointer_type(pointer_type);
}
pub fn set_window_safe_area_insets(&mut self, window: AppWindowId, insets: Option<Edges>) {
self.for_window_mut(window)
.record_committed_safe_area_insets(insets);
}
pub fn set_window_occlusion_insets(&mut self, window: AppWindowId, insets: Option<Edges>) {
self.for_window_mut(window)
.record_committed_occlusion_insets(insets);
}
pub fn for_window_mut(&mut self, window: AppWindowId) -> &mut WindowElementState {
self.windows.entry(window).or_default()
}
pub(crate) fn for_window(&self, window: AppWindowId) -> Option<&WindowElementState> {
self.windows.get(&window)
}
pub fn prepare_window_for_frame(&mut self, window: AppWindowId, frame_id: FrameId) {
let lag = self.gc_lag_frames;
self.for_window_mut(window).prepare_for_frame(frame_id, lag);
}
#[cfg(feature = "diagnostics")]
pub fn diagnostics_snapshot(
&self,
window: AppWindowId,
) -> Option<WindowElementDiagnosticsSnapshot> {
let state = self.windows.get(&window)?;
Some(state.diagnostics_snapshot())
}
#[cfg(feature = "diagnostics")]
pub fn element_for_node(&self, window: AppWindowId, node: NodeId) -> Option<GlobalElementId> {
let state = self.windows.get(&window)?;
state.element_for_node(node)
}
#[cfg(feature = "diagnostics")]
pub fn node_for_element(
&self,
window: AppWindowId,
element: GlobalElementId,
) -> Option<NodeId> {
let state = self.windows.get(&window)?;
state.node_entry(element).map(|e| e.node)
}
#[cfg(any(test, feature = "diagnostics"))]
pub fn selectable_text_interactive_span_bounds_for_node(
&self,
window: AppWindowId,
node: NodeId,
) -> Option<Vec<crate::element::SelectableTextInteractiveSpanBounds>> {
let state = self.windows.get(&window)?;
let element = state.element_for_node(node)?;
self.selectable_text_interactive_span_bounds_for_element(window, element)
}
#[cfg(any(test, feature = "diagnostics"))]
pub fn selectable_text_interactive_span_bounds_for_element(
&self,
window: AppWindowId,
element: GlobalElementId,
) -> Option<Vec<crate::element::SelectableTextInteractiveSpanBounds>> {
let state = self.windows.get(&window)?;
let key = (element, TypeId::of::<crate::element::SelectableTextState>());
let boxed = state
.next_state
.get(&key)
.or_else(|| state.rendered_state.get(&key))
.or_else(|| state.lag_state.iter().rev().find_map(|m| m.get(&key)))?;
let state = boxed.downcast_ref::<crate::element::SelectableTextState>()?;
Some(state.interactive_span_bounds.clone())
}
#[cfg(feature = "diagnostics")]
pub fn debug_path_for_element(
&self,
window: AppWindowId,
element: GlobalElementId,
) -> Option<String> {
let state = self.windows.get(&window)?;
state.debug_path_for_element(element)
}
}
#[derive(Default)]
pub struct WindowElementState {
pub(super) rendered_state: HashMap<(GlobalElementId, TypeId), Box<dyn Any>>,
pub(super) next_state: HashMap<(GlobalElementId, TypeId), Box<dyn Any>>,
pub(super) lag_state: Vec<HashMap<(GlobalElementId, TypeId), Box<dyn Any>>>,
pub(super) view_cache_state_keys_rendered:
HashMap<GlobalElementId, Vec<(GlobalElementId, TypeId)>>,
pub(super) view_cache_state_keys_next: HashMap<GlobalElementId, Vec<(GlobalElementId, TypeId)>>,
pub(super) view_cache_authoring_identities_rendered:
HashMap<GlobalElementId, Vec<GlobalElementId>>,
pub(super) view_cache_authoring_identities_next: HashMap<GlobalElementId, Vec<GlobalElementId>>,
view_cache_keys_rendered: HashMap<GlobalElementId, u64>,
view_cache_keys_next: HashMap<GlobalElementId, u64>,
view_cache_key_mismatch_roots: HashSet<GlobalElementId>,
pub(super) view_cache_elements_rendered: HashMap<GlobalElementId, Vec<GlobalElementId>>,
pub(super) view_cache_elements_next: HashMap<GlobalElementId, Vec<GlobalElementId>>,
pub(super) view_cache_reuse_roots: HashSet<GlobalElementId>,
view_cache_last_reused_frame: HashMap<GlobalElementId, FrameId>,
view_cache_transitioned_reuse_roots: HashSet<GlobalElementId>,
view_cache_stack: Vec<GlobalElementId>,
raf_notify_roots: HashSet<GlobalElementId>,
#[cfg(feature = "diagnostics")]
raf_notify_roots_debug: HashSet<GlobalElementId>,
pub(super) pending_retained_virtual_list_reconciles:
HashMap<GlobalElementId, crate::tree::UiDebugRetainedVirtualListReconcileKind>,
retained_virtual_list_keep_alive_roots: HashSet<NodeId>,
prepared_frame: FrameId,
#[cfg(any(test, feature = "diagnostics"))]
strict_ownership: bool,
pub(super) prev_unkeyed_fingerprints: HashMap<u64, Vec<u64>>,
pub(super) cur_unkeyed_fingerprints: HashMap<u64, Vec<u64>>,
pub(super) observed_models_rendered: HashMap<GlobalElementId, Vec<(ModelId, Invalidation)>>,
pub(super) observed_models_next: HashMap<GlobalElementId, Vec<(ModelId, Invalidation)>>,
pub(super) observed_globals_rendered: HashMap<GlobalElementId, Vec<(TypeId, Invalidation)>>,
pub(super) observed_globals_next: HashMap<GlobalElementId, Vec<(TypeId, Invalidation)>>,
pub(super) observed_layout_queries_rendered:
HashMap<GlobalElementId, Vec<(GlobalElementId, Invalidation)>>,
pub(super) observed_layout_queries_next:
HashMap<GlobalElementId, Vec<(GlobalElementId, Invalidation)>>,
pub(super) observed_environment_rendered:
HashMap<GlobalElementId, Vec<(EnvironmentQueryKey, Invalidation)>>,
pub(super) observed_environment_next:
HashMap<GlobalElementId, Vec<(EnvironmentQueryKey, Invalidation)>>,
layout_query_region_revisions: HashMap<GlobalElementId, u64>,
layout_query_regions_changed_this_frame: HashSet<GlobalElementId>,
environment_revisions: HashMap<EnvironmentQueryKey, u64>,
environment_changed_this_frame: HashSet<EnvironmentQueryKey>,
pub(super) timer_targets: HashMap<TimerToken, TimerTarget>,
scratch_view_cache_keep_alive_elements: HashSet<GlobalElementId>,
scratch_view_cache_keep_alive_visited_roots: HashSet<GlobalElementId>,
scratch_view_cache_keep_alive_stack: Vec<GlobalElementId>,
scratch_element_children_vec_pool: Vec<Vec<AnyElement>>,
element_children_vec_pool_reuses: u32,
element_children_vec_pool_misses: u32,
transient_events: HashMap<(GlobalElementId, u64), FrameId>,
authoring_identities_current_frame: HashSet<GlobalElementId>,
nodes: HashMap<GlobalElementId, NodeEntry>,
root_bounds: HashMap<GlobalElementId, Rect>,
prev_bounds: HashMap<GlobalElementId, Rect>,
cur_bounds: HashMap<GlobalElementId, Rect>,
prev_visual_bounds: HashMap<GlobalElementId, Rect>,
cur_visual_bounds: HashMap<GlobalElementId, Rect>,
committed_viewport_bounds: Rect,
committed_scale_factor: f32,
committed_color_scheme: Option<ColorScheme>,
committed_prefers_reduced_motion: Option<bool>,
committed_text_scale_factor: Option<f32>,
committed_prefers_reduced_transparency: Option<bool>,
committed_accent_color: Option<Color>,
committed_contrast_preference: Option<ContrastPreference>,
committed_forced_colors_mode: Option<ForcedColorsMode>,
committed_primary_pointer_type: Option<PointerType>,
committed_safe_area_insets: Option<Edges>,
committed_occlusion_insets: Option<Edges>,
pub(super) focused_element: Option<GlobalElementId>,
pub(super) active_text_selection: Option<ActiveTextSelection>,
pub(super) hovered_pressable: Option<GlobalElementId>,
pub(super) hovered_pressable_node: Option<NodeId>,
pub(super) hovered_pressable_raw: Option<GlobalElementId>,
pub(super) hovered_pressable_raw_node: Option<NodeId>,
pub(super) hovered_pressable_raw_below_barrier: Option<GlobalElementId>,
pub(super) hovered_pressable_raw_below_barrier_node: Option<NodeId>,
pub(super) pressed_pressable: Option<GlobalElementId>,
pub(super) pressed_pressable_node: Option<NodeId>,
pub(super) hovered_hover_region: Option<GlobalElementId>,
pub(super) hovered_hover_region_node: Option<NodeId>,
continuous_frames: Arc<AtomicUsize>,
#[cfg(feature = "diagnostics")]
continuous_frame_owners: Arc<Mutex<HashMap<GlobalElementId, u32>>>,
#[cfg(feature = "diagnostics")]
debug_identity: DebugIdentityRegistry,
#[cfg(feature = "diagnostics")]
debug_node_entry_root_overwrites: Vec<NodeEntryRootOverwrite>,
#[cfg(feature = "diagnostics")]
overlay_placement: Vec<OverlayPlacementDiagnosticsRecord>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) struct ActiveTextSelection {
pub root: GlobalElementId,
pub element: GlobalElementId,
pub node: NodeId,
}
#[derive(Debug, Clone, Copy)]
pub(crate) struct NodeEntry {
pub node: NodeId,
pub last_seen_frame: FrameId,
pub root: GlobalElementId,
}
#[cfg(feature = "diagnostics")]
#[derive(Debug, Clone, Copy)]
pub struct NodeEntryRootOverwrite {
pub frame_id: FrameId,
pub element: GlobalElementId,
pub old_root: GlobalElementId,
pub new_root: GlobalElementId,
pub old_node: NodeId,
pub new_node: NodeId,
pub file: &'static str,
pub line: u32,
pub column: u32,
}
#[derive(Debug, Default, Clone)]
pub(crate) struct LayoutQueryRegionMarker {
pub name: Option<Arc<str>>,
}
impl WindowElementState {
pub(crate) fn element_children_vec_pool_reuses(&self) -> u32 {
self.element_children_vec_pool_reuses
}
pub(crate) fn element_children_vec_pool_misses(&self) -> u32 {
self.element_children_vec_pool_misses
}
pub(crate) fn take_scratch_element_children_vec(
&mut self,
min_capacity: usize,
) -> Vec<AnyElement> {
if let Some(mut out) = self.scratch_element_children_vec_pool.pop() {
self.element_children_vec_pool_reuses =
self.element_children_vec_pool_reuses.saturating_add(1);
out.clear();
if out.capacity() < min_capacity {
out.reserve(min_capacity - out.capacity());
}
return out;
}
self.element_children_vec_pool_misses =
self.element_children_vec_pool_misses.saturating_add(1);
Vec::with_capacity(min_capacity)
}
pub(crate) fn restore_scratch_element_children_vec(&mut self, mut scratch: Vec<AnyElement>) {
const POOL_MAX: usize = 2048;
const MAX_RETAIN_BYTES: usize = 64 * 1024;
if self.scratch_element_children_vec_pool.len() >= POOL_MAX {
return;
}
let bytes = scratch
.capacity()
.saturating_mul(std::mem::size_of::<AnyElement>());
if bytes > MAX_RETAIN_BYTES {
return;
}
scratch.clear();
self.scratch_element_children_vec_pool.push(scratch);
}
pub(crate) fn take_scratch_view_cache_keep_alive_elements(
&mut self,
) -> HashSet<GlobalElementId> {
std::mem::take(&mut self.scratch_view_cache_keep_alive_elements)
}
pub(crate) fn restore_scratch_view_cache_keep_alive_elements(
&mut self,
scratch: HashSet<GlobalElementId>,
) {
self.scratch_view_cache_keep_alive_elements = scratch;
}
pub(crate) fn take_scratch_view_cache_keep_alive_visited_roots(
&mut self,
) -> HashSet<GlobalElementId> {
std::mem::take(&mut self.scratch_view_cache_keep_alive_visited_roots)
}
pub(crate) fn restore_scratch_view_cache_keep_alive_visited_roots(
&mut self,
scratch: HashSet<GlobalElementId>,
) {
self.scratch_view_cache_keep_alive_visited_roots = scratch;
}
pub(crate) fn take_scratch_view_cache_keep_alive_stack(&mut self) -> Vec<GlobalElementId> {
std::mem::take(&mut self.scratch_view_cache_keep_alive_stack)
}
pub(crate) fn restore_scratch_view_cache_keep_alive_stack(
&mut self,
scratch: Vec<GlobalElementId>,
) {
self.scratch_view_cache_keep_alive_stack = scratch;
}
#[cfg(any(test, feature = "diagnostics"))]
#[allow(dead_code)]
pub(crate) fn set_strict_ownership(&mut self, strict: bool) {
self.strict_ownership = strict;
}
fn prepare_for_frame(&mut self, frame_id: FrameId, lag_frames: u64) {
if self.prepared_frame == frame_id {
return;
}
self.prepared_frame = frame_id;
self.advance_element_state_buffers(lag_frames);
let cutoff = frame_id.0.saturating_sub(1);
self.transient_events
.retain(|_, recorded| recorded.0 >= cutoff);
self.raf_notify_roots.clear();
#[cfg(feature = "diagnostics")]
self.raf_notify_roots_debug.clear();
self.view_cache_key_mismatch_roots.clear();
self.element_children_vec_pool_reuses = 0;
self.element_children_vec_pool_misses = 0;
std::mem::swap(
&mut self.view_cache_keys_rendered,
&mut self.view_cache_keys_next,
);
self.view_cache_keys_next.clear();
std::mem::swap(
&mut self.view_cache_state_keys_rendered,
&mut self.view_cache_state_keys_next,
);
self.view_cache_state_keys_next.clear();
std::mem::swap(
&mut self.view_cache_authoring_identities_rendered,
&mut self.view_cache_authoring_identities_next,
);
self.view_cache_authoring_identities_next.clear();
std::mem::swap(
&mut self.view_cache_elements_rendered,
&mut self.view_cache_elements_next,
);
self.view_cache_elements_next.clear();
self.view_cache_reuse_roots.clear();
self.view_cache_transitioned_reuse_roots.clear();
self.view_cache_stack.clear();
std::mem::swap(
&mut self.prev_unkeyed_fingerprints,
&mut self.cur_unkeyed_fingerprints,
);
self.cur_unkeyed_fingerprints.clear();
std::mem::swap(
&mut self.observed_models_rendered,
&mut self.observed_models_next,
);
self.observed_models_next.clear();
std::mem::swap(
&mut self.observed_globals_rendered,
&mut self.observed_globals_next,
);
self.observed_globals_next.clear();
std::mem::swap(
&mut self.observed_layout_queries_rendered,
&mut self.observed_layout_queries_next,
);
self.observed_layout_queries_next.clear();
self.layout_query_regions_changed_this_frame.clear();
std::mem::swap(
&mut self.observed_environment_rendered,
&mut self.observed_environment_next,
);
self.observed_environment_next.clear();
self.environment_changed_this_frame.clear();
if !self.cur_bounds.is_empty() {
self.prev_bounds.reserve(self.cur_bounds.len());
self.prev_bounds.extend(self.cur_bounds.drain());
}
self.cur_bounds.clear();
if !self.cur_visual_bounds.is_empty() {
self.prev_visual_bounds
.reserve(self.cur_visual_bounds.len());
self.prev_visual_bounds
.extend(self.cur_visual_bounds.drain());
}
self.cur_visual_bounds.clear();
self.focused_element = None;
self.authoring_identities_current_frame.clear();
#[cfg(feature = "diagnostics")]
{
let cutoff = frame_id.0.saturating_sub(lag_frames);
self.debug_identity
.entries
.retain(|_, v| v.last_seen_frame.0 >= cutoff);
self.debug_node_entry_root_overwrites
.retain(|r| r.frame_id.0 >= cutoff);
const KEEP_FRAMES: u64 = 120;
let cutoff = frame_id.0.saturating_sub(KEEP_FRAMES);
self.overlay_placement.retain(|rec| match rec {
OverlayPlacementDiagnosticsRecord::AnchoredPanel(r) => r.frame_id.0 >= cutoff,
OverlayPlacementDiagnosticsRecord::PlacedRect(r) => r.frame_id.0 >= cutoff,
});
const MAX_RECORDS: usize = 512;
if self.overlay_placement.len() > MAX_RECORDS {
let extra = self.overlay_placement.len() - MAX_RECORDS;
self.overlay_placement.drain(0..extra);
}
}
}
pub(crate) fn retained_virtual_list_keep_alive_roots(
&self,
) -> impl Iterator<Item = NodeId> + '_ {
self.retained_virtual_list_keep_alive_roots.iter().copied()
}
pub(crate) fn add_retained_virtual_list_keep_alive_root(&mut self, node: NodeId) {
self.retained_virtual_list_keep_alive_roots.insert(node);
}
pub(crate) fn remove_retained_virtual_list_keep_alive_root(&mut self, node: NodeId) {
self.retained_virtual_list_keep_alive_roots.remove(&node);
}
pub(crate) fn retain_retained_virtual_list_keep_alive_roots(
&mut self,
mut f: impl FnMut(NodeId) -> bool,
) {
self.retained_virtual_list_keep_alive_roots
.retain(|node| f(*node));
}
pub(crate) fn record_transient_event(&mut self, element: GlobalElementId, key: u64) {
self.transient_events
.insert((element, key), self.prepared_frame);
}
pub(crate) fn mark_authoring_identity_seen(&mut self, element: GlobalElementId) {
self.authoring_identities_current_frame.insert(element);
for &root in &self.view_cache_stack {
self.view_cache_authoring_identities_next
.entry(root)
.or_default()
.push(element);
}
#[cfg(feature = "diagnostics")]
self.touch_debug_identity_for_element(self.prepared_frame, element);
}
pub(crate) fn element_identity_is_live_in_current_frame(
&self,
element: GlobalElementId,
) -> bool {
self.authoring_identities_current_frame.contains(&element)
|| self
.node_entry(element)
.is_some_and(|entry| entry.last_seen_frame == self.prepared_frame)
}
pub(crate) fn take_transient_event(&mut self, element: GlobalElementId, key: u64) -> bool {
self.transient_events.remove(&(element, key)).is_some()
}
fn advance_element_state_buffers(&mut self, lag_frames: u64) {
if lag_frames == 0 {
self.lag_state.clear();
} else {
self.lag_state
.push(std::mem::take(&mut self.rendered_state));
let max = lag_frames as usize;
if self.lag_state.len() > max {
let drain = self.lag_state.len() - max;
self.lag_state.drain(0..drain);
}
}
self.rendered_state = std::mem::take(&mut self.next_state);
self.next_state.clear();
}
pub(super) fn state_any_ref(&self, key: &(GlobalElementId, TypeId)) -> Option<&dyn Any> {
if let Some(v) = self.next_state.get(key) {
return Some(&**v);
}
if let Some(v) = self.rendered_state.get(key) {
return Some(&**v);
}
for map in self.lag_state.iter().rev() {
if let Some(v) = map.get(key) {
return Some(&**v);
}
}
None
}
pub(crate) fn has_state<S: Any>(&self, element: GlobalElementId) -> bool {
let key = (element, TypeId::of::<S>());
self.state_any_ref(&key).is_some()
}
pub(crate) fn layout_query_region_revision(&self, element: GlobalElementId) -> u64 {
self.layout_query_region_revisions
.get(&element)
.copied()
.unwrap_or(0)
}
pub(crate) fn layout_query_deps_fingerprint_rendered(&self, root: GlobalElementId) -> u64 {
self.layout_query_deps_fingerprint_from_list(
self.observed_layout_queries_rendered.get(&root),
)
}
pub(crate) fn layout_query_deps_fingerprint_next(&self, root: GlobalElementId) -> u64 {
self.layout_query_deps_fingerprint_from_list(self.observed_layout_queries_next.get(&root))
}
fn layout_query_deps_fingerprint_from_list(
&self,
deps: Option<&Vec<(GlobalElementId, Invalidation)>>,
) -> u64 {
let Some(deps) = deps else {
return 0;
};
if deps.is_empty() {
return 0;
}
let mut entries: Vec<(u64, u64, u8)> = deps
.iter()
.map(|(region, inv)| {
(
region.0,
self.layout_query_region_revision(*region),
*inv as u8,
)
})
.collect();
entries.sort_by(|a, b| a.0.cmp(&b.0).then(a.2.cmp(&b.2)));
stable_hash(&entries)
}
pub(crate) fn environment_deps_fingerprint_rendered(&self, root: GlobalElementId) -> u64 {
self.environment_deps_fingerprint_from_list(self.observed_environment_rendered.get(&root))
}
pub(crate) fn environment_deps_fingerprint_next(&self, root: GlobalElementId) -> u64 {
self.environment_deps_fingerprint_from_list(self.observed_environment_next.get(&root))
}
fn environment_revision(&self, key: EnvironmentQueryKey) -> u64 {
self.environment_revisions.get(&key).copied().unwrap_or(0)
}
fn environment_deps_fingerprint_from_list(
&self,
deps: Option<&Vec<(EnvironmentQueryKey, Invalidation)>>,
) -> u64 {
let Some(deps) = deps else {
return 0;
};
if deps.is_empty() {
return 0;
}
let key_id = |k: EnvironmentQueryKey| match k {
EnvironmentQueryKey::ViewportSize => 0u8,
EnvironmentQueryKey::ScaleFactor => 1u8,
EnvironmentQueryKey::ColorScheme => 2u8,
EnvironmentQueryKey::PrefersReducedMotion => 3u8,
EnvironmentQueryKey::PrimaryPointerType => 4u8,
EnvironmentQueryKey::SafeAreaInsets => 5u8,
EnvironmentQueryKey::OcclusionInsets => 6u8,
EnvironmentQueryKey::PrefersContrast => 7u8,
EnvironmentQueryKey::ForcedColorsMode => 8u8,
EnvironmentQueryKey::TextScaleFactor => 9u8,
EnvironmentQueryKey::PrefersReducedTransparency => 10u8,
EnvironmentQueryKey::AccentColor => 11u8,
};
let mut entries: Vec<(u8, u64, u8)> = deps
.iter()
.map(|(key, inv)| (key_id(*key), self.environment_revision(*key), *inv as u8))
.collect();
entries.sort_by(|a, b| a.0.cmp(&b.0).then(a.2.cmp(&b.2)));
stable_hash(&entries)
}
pub(super) fn take_state_box(
&mut self,
key: &(GlobalElementId, TypeId),
) -> Option<Box<dyn Any>> {
if let Some(v) = self.next_state.remove(key) {
return Some(v);
}
if let Some(v) = self.rendered_state.remove(key) {
return Some(v);
}
for map in self.lag_state.iter_mut().rev() {
if let Some(v) = map.remove(key) {
return Some(v);
}
}
None
}
pub(super) fn insert_state_box(&mut self, key: (GlobalElementId, TypeId), value: Box<dyn Any>) {
self.next_state.insert(key, value);
}
pub(crate) fn with_state_mut<S: Any, R>(
&mut self,
element: GlobalElementId,
init: impl FnOnce() -> S,
f: impl FnOnce(&mut S) -> R,
) -> R {
struct StateBoxGuard<'a> {
window_state: &'a mut WindowElementState,
key: (GlobalElementId, TypeId),
value: Option<Box<dyn Any>>,
}
impl Drop for StateBoxGuard<'_> {
fn drop(&mut self) {
let Some(value) = self.value.take() else {
return;
};
self.window_state.insert_state_box(self.key, value);
}
}
let key = (element, TypeId::of::<S>());
self.record_state_key_access(key);
let mut init = Some(init);
let value = self.take_state_box(&key).unwrap_or_else(|| {
let init = init.take().expect("init is available");
Box::new(init())
});
let mut guard = StateBoxGuard {
window_state: self,
key,
value: Some(value),
};
let state = match guard
.value
.as_deref_mut()
.expect("guard has a value")
.downcast_mut::<S>()
{
Some(state) => state,
None => {
if crate::strict_runtime::strict_runtime_enabled() {
panic!(
"element state type mismatch: element={:?}, expected={}",
element,
std::any::type_name::<S>()
);
}
#[cfg(debug_assertions)]
{
eprintln!(
"element state type mismatch: element={:?}, expected={}; dropping corrupt state and re-initializing",
element,
std::any::type_name::<S>()
);
}
let init = init.take().expect("init is available");
guard.value = Some(Box::new(init()));
guard
.value
.as_deref_mut()
.expect("guard has a value")
.downcast_mut::<S>()
.expect("re-initialized state has the expected type")
}
};
f(state)
}
pub(crate) fn try_with_state_mut<S: Any, R>(
&mut self,
element: GlobalElementId,
f: impl FnOnce(&mut S) -> R,
) -> Option<R> {
struct StateBoxGuard<'a> {
window_state: &'a mut WindowElementState,
key: (GlobalElementId, TypeId),
value: Option<Box<dyn Any>>,
}
impl Drop for StateBoxGuard<'_> {
fn drop(&mut self) {
let Some(value) = self.value.take() else {
return;
};
self.window_state.insert_state_box(self.key, value);
}
}
let key = (element, TypeId::of::<S>());
self.record_state_key_access(key);
let value = self.take_state_box(&key)?;
let mut guard = StateBoxGuard {
window_state: self,
key,
value: Some(value),
};
let state = match guard
.value
.as_deref_mut()
.expect("guard has a value")
.downcast_mut::<S>()
{
Some(state) => state,
None => {
if crate::strict_runtime::strict_runtime_enabled() {
panic!(
"element state type mismatch: element={:?}, expected={}",
element,
std::any::type_name::<S>()
);
}
#[cfg(debug_assertions)]
{
eprintln!(
"element state type mismatch: element={:?}, expected={}; dropping corrupt state",
element,
std::any::type_name::<S>()
);
}
guard.value = None;
return None;
}
};
Some(f(state))
}
pub(crate) fn mark_retained_virtual_list_needs_reconcile(
&mut self,
element: GlobalElementId,
kind: crate::tree::UiDebugRetainedVirtualListReconcileKind,
) {
self.pending_retained_virtual_list_reconciles
.entry(element)
.and_modify(|existing| {
if *existing == crate::tree::UiDebugRetainedVirtualListReconcileKind::Prefetch
&& kind == crate::tree::UiDebugRetainedVirtualListReconcileKind::Escape
{
*existing = kind;
}
})
.or_insert(kind);
}
pub(crate) fn take_retained_virtual_list_reconciles(
&mut self,
) -> Vec<(
GlobalElementId,
crate::tree::UiDebugRetainedVirtualListReconcileKind,
)> {
self.pending_retained_virtual_list_reconciles
.drain()
.collect()
}
pub(super) fn record_state_key_access(&mut self, key: (GlobalElementId, TypeId)) {
if self.view_cache_stack.is_empty() {
return;
};
for &root in &self.view_cache_stack {
self.view_cache_state_keys_next
.entry(root)
.or_default()
.push(key);
}
}
pub(super) fn begin_view_cache_scope(&mut self, root: GlobalElementId) {
self.view_cache_stack.push(root);
self.view_cache_state_keys_next.remove(&root);
self.view_cache_authoring_identities_next.remove(&root);
self.view_cache_elements_next.remove(&root);
}
pub(super) fn end_view_cache_scope(&mut self, root: GlobalElementId) {
let popped = self.view_cache_stack.pop();
debug_assert_eq!(popped, Some(root));
if let Some(keys) = self.view_cache_state_keys_next.get_mut(&root) {
let mut seen: HashSet<(GlobalElementId, TypeId)> = HashSet::with_capacity(keys.len());
keys.retain(|&key| seen.insert(key));
}
if let Some(identities) = self.view_cache_authoring_identities_next.get_mut(&root) {
let mut seen: HashSet<GlobalElementId> = HashSet::with_capacity(identities.len());
identities.retain(|&id| seen.insert(id));
}
if let Some(elements) = self.view_cache_elements_next.get_mut(&root) {
let mut seen: HashSet<GlobalElementId> = HashSet::with_capacity(elements.len());
elements.retain(|&id| seen.insert(id));
}
}
pub(crate) fn touch_observed_models_for_element_if_recorded(
&mut self,
element: GlobalElementId,
) {
if self.observed_models_next.contains_key(&element) {
return;
}
let Some(list) = self.observed_models_rendered.get(&element) else {
return;
};
self.observed_models_next.insert(element, list.clone());
}
pub(crate) fn touch_observed_globals_for_element_if_recorded(
&mut self,
element: GlobalElementId,
) {
if self.observed_globals_next.contains_key(&element) {
return;
}
let Some(list) = self.observed_globals_rendered.get(&element) else {
return;
};
self.observed_globals_next.insert(element, list.clone());
}
pub(crate) fn touch_observed_layout_queries_for_element_if_recorded(
&mut self,
element: GlobalElementId,
) {
if self.observed_layout_queries_next.contains_key(&element) {
return;
}
let Some(list) = self.observed_layout_queries_rendered.get(&element) else {
return;
};
self.observed_layout_queries_next
.insert(element, list.clone());
}
pub(crate) fn touch_observed_environment_for_element_if_recorded(
&mut self,
element: GlobalElementId,
) {
if self.observed_environment_next.contains_key(&element) {
return;
}
let Some(list) = self.observed_environment_rendered.get(&element) else {
return;
};
self.observed_environment_next.insert(element, list.clone());
}
pub(crate) fn mark_view_cache_reuse_root(&mut self, root: GlobalElementId) {
self.view_cache_reuse_roots.insert(root);
}
pub(crate) fn record_view_cache_reuse_frame(
&mut self,
root: GlobalElementId,
frame_id: FrameId,
) -> bool {
let transitioned = self
.view_cache_last_reused_frame
.get(&root)
.is_none_or(|last| last.0.saturating_add(1) < frame_id.0);
self.view_cache_last_reused_frame.insert(root, frame_id);
if transitioned {
self.view_cache_transitioned_reuse_roots.insert(root);
}
transitioned
}
pub(crate) fn view_cache_transitioned_reuse_roots(
&self,
) -> impl Iterator<Item = GlobalElementId> + '_ {
self.view_cache_transitioned_reuse_roots.iter().copied()
}
pub(crate) fn should_reuse_view_cache_root(&self, root: GlobalElementId) -> bool {
self.view_cache_reuse_roots.contains(&root)
}
pub(crate) fn view_cache_reuse_roots(&self) -> impl Iterator<Item = GlobalElementId> + '_ {
self.view_cache_reuse_roots.iter().copied()
}
pub(crate) fn current_view_cache_root(&self) -> Option<GlobalElementId> {
self.view_cache_stack.last().copied()
}
pub(crate) fn request_notify_for_animation_frame(&mut self, root: GlobalElementId) {
self.raf_notify_roots.insert(root);
#[cfg(feature = "diagnostics")]
self.raf_notify_roots_debug.insert(root);
}
pub(crate) fn take_notify_for_animation_frame(&mut self) -> Vec<GlobalElementId> {
if self.raf_notify_roots.is_empty() {
return Vec::new();
}
let out: Vec<GlobalElementId> = self.raf_notify_roots.iter().copied().collect();
self.raf_notify_roots.clear();
out
}
pub(crate) fn view_cache_key_matches_and_touch(
&mut self,
root: GlobalElementId,
key: u64,
) -> bool {
if self.view_cache_keys_next.contains_key(&root) {
return self.view_cache_keys_next.get(&root).copied() == Some(key);
}
let Some(prev) = self.view_cache_keys_rendered.get(&root).copied() else {
return false;
};
if prev != key {
return false;
}
self.view_cache_keys_next.insert(root, key);
true
}
pub(crate) fn set_view_cache_key(&mut self, root: GlobalElementId, key: u64) {
self.view_cache_keys_next.insert(root, key);
}
pub(crate) fn record_view_cache_key_mismatch(&mut self, root: GlobalElementId) {
self.view_cache_key_mismatch_roots.insert(root);
}
pub(crate) fn view_cache_key_mismatch(&self, root: GlobalElementId) -> bool {
self.view_cache_key_mismatch_roots.contains(&root)
}
pub(super) fn touch_state_key(&mut self, key: (GlobalElementId, TypeId)) {
let Some(value) = self.take_state_box(&key) else {
return;
};
self.insert_state_box(key, value);
}
pub(crate) fn touch_view_cache_state_keys_if_recorded(&mut self, root: GlobalElementId) {
let Some(keys) = self.view_cache_state_keys_rendered.get(&root).cloned() else {
return;
};
for &key in &keys {
self.touch_state_key(key);
}
self.view_cache_state_keys_next.insert(root, keys);
}
pub(crate) fn touch_view_cache_authoring_identities_if_recorded(
&mut self,
root: GlobalElementId,
) {
let Some(identities) = self
.view_cache_authoring_identities_rendered
.get(&root)
.cloned()
else {
return;
};
for &identity in &identities {
self.authoring_identities_current_frame.insert(identity);
#[cfg(feature = "diagnostics")]
self.touch_debug_identity_for_element(self.prepared_frame, identity);
}
self.view_cache_authoring_identities_next
.insert(root, identities);
}
pub(crate) fn touch_view_cache_action_hook_state_for_subtree_elements(
&mut self,
root: GlobalElementId,
) {
let Some(elements) = self.view_cache_elements_for_root(root) else {
return;
};
if elements.is_empty() {
return;
}
let elements = elements.to_vec();
for element in elements {
self.touch_state_key((element, TypeId::of::<crate::action::PressableActionHooks>()));
self.touch_state_key((
element,
TypeId::of::<crate::action::PressableHoverActionHooks>(),
));
self.touch_state_key((
element,
TypeId::of::<crate::action::DismissibleActionHooks>(),
));
self.touch_state_key((element, TypeId::of::<crate::action::PointerActionHooks>()));
self.touch_state_key((element, TypeId::of::<crate::action::KeyActionHooks>()));
self.touch_state_key((element, TypeId::of::<crate::action::ActionRouteHooks>()));
self.touch_state_key((element, TypeId::of::<crate::action::CommandActionHooks>()));
self.touch_state_key((
element,
TypeId::of::<crate::action::CommandAvailabilityActionHooks>(),
));
self.touch_state_key((element, TypeId::of::<crate::action::TimerActionHooks>()));
self.touch_state_key((element, TypeId::of::<crate::action::RovingActionHooks>()));
self.touch_state_key((element, TypeId::of::<crate::element::SelectableTextState>()));
self.touch_state_key((
element,
TypeId::of::<crate::action::SelectableTextActionHooks>(),
));
self.touch_state_key((
element,
TypeId::of::<crate::action::TextInputRegionActionHooks>(),
));
self.touch_state_key((
element,
TypeId::of::<crate::action::InternalDragActionHooks>(),
));
self.touch_state_key((
element,
TypeId::of::<crate::action::ExternalDragActionHooks>(),
));
}
}
pub(crate) fn record_view_cache_subtree_elements(
&mut self,
root: GlobalElementId,
elements: Vec<GlobalElementId>,
) {
self.view_cache_elements_next.insert(root, elements);
}
pub(crate) fn forget_view_cache_subtree_elements(&mut self, root: GlobalElementId) {
self.view_cache_authoring_identities_rendered.remove(&root);
self.view_cache_authoring_identities_next.remove(&root);
self.view_cache_elements_rendered.remove(&root);
self.view_cache_elements_next.remove(&root);
}
pub(crate) fn touch_view_cache_subtree_elements_if_recorded(
&mut self,
root: GlobalElementId,
frame_id: FrameId,
root_id: GlobalElementId,
mut resolve_live_attached_node: impl FnMut(GlobalElementId, Option<NodeId>) -> Option<NodeId>,
) -> bool {
if self.view_cache_elements_next.contains_key(&root) {
return true;
}
let Some(elements) = self.view_cache_elements_rendered.get(&root).cloned() else {
return false;
};
let mut authoritative_nodes: Vec<(GlobalElementId, NodeId, GlobalElementId)> =
Vec::with_capacity(elements.len());
for &element in &elements {
let seeded_entry = self.nodes.get(&element).copied();
let Some(node) =
resolve_live_attached_node(element, seeded_entry.map(|entry| entry.node))
else {
return false;
};
let owner_root = seeded_entry.map(|entry| entry.root).unwrap_or(root_id);
authoritative_nodes.push((element, node, owner_root));
}
self.view_cache_elements_next.insert(root, elements);
for (element, node, owner_root) in authoritative_nodes {
if owner_root == root_id {
}
self.nodes.insert(
element,
NodeEntry {
node,
last_seen_frame: frame_id,
root: owner_root,
},
);
#[cfg(feature = "diagnostics")]
self.touch_debug_identity_for_element(frame_id, element);
}
true
}
pub(crate) fn view_cache_elements_for_root(
&self,
root: GlobalElementId,
) -> Option<&[GlobalElementId]> {
if let Some(v) = self.view_cache_elements_next.get(&root) {
return Some(v.as_slice());
}
self.view_cache_elements_rendered
.get(&root)
.map(|v| v.as_slice())
}
pub(crate) fn active_text_selection(&self) -> Option<ActiveTextSelection> {
self.active_text_selection
}
pub(crate) fn set_active_text_selection(&mut self, selection: Option<ActiveTextSelection>) {
self.active_text_selection = selection;
}
pub(crate) fn node_entry(&self, id: GlobalElementId) -> Option<NodeEntry> {
self.nodes.get(&id).copied()
}
pub(crate) fn for_each_observed_model_for_invalidation(
&self,
frame_id: FrameId,
mut f: impl FnMut(GlobalElementId, &Vec<(ModelId, Invalidation)>),
) {
if self.prepared_frame != frame_id {
for (&element, observations) in &self.observed_models_next {
f(element, observations);
}
return;
}
for (&element, observations) in &self.observed_models_rendered {
if self.observed_models_next.contains_key(&element) {
continue;
}
f(element, observations);
}
for (&element, observations) in &self.observed_models_next {
f(element, observations);
}
}
pub(crate) fn for_each_observed_global_for_invalidation(
&self,
frame_id: FrameId,
mut f: impl FnMut(GlobalElementId, &Vec<(TypeId, Invalidation)>),
) {
if self.prepared_frame != frame_id {
for (&element, observations) in &self.observed_globals_next {
f(element, observations);
}
return;
}
for (&element, observations) in &self.observed_globals_rendered {
if self.observed_globals_next.contains_key(&element) {
continue;
}
f(element, observations);
}
for (&element, observations) in &self.observed_globals_next {
f(element, observations);
}
}
pub(crate) fn element_for_node(&self, node: NodeId) -> Option<GlobalElementId> {
self.nodes
.iter()
.find_map(|(&element, entry)| (entry.node == node).then_some(element))
}
#[track_caller]
pub(crate) fn set_node_entry(&mut self, id: GlobalElementId, entry: NodeEntry) {
#[cfg(feature = "diagnostics")]
if let Some(prev) = self.nodes.get(&id)
&& prev.root != entry.root
{
let location = std::panic::Location::caller();
self.debug_node_entry_root_overwrites
.push(NodeEntryRootOverwrite {
frame_id: entry.last_seen_frame,
element: id,
old_root: prev.root,
new_root: entry.root,
old_node: prev.node,
new_node: entry.node,
file: location.file(),
line: location.line(),
column: location.column(),
});
const MAX_RECORDS: usize = 256;
if self.debug_node_entry_root_overwrites.len() > MAX_RECORDS {
let drain = self.debug_node_entry_root_overwrites.len() - MAX_RECORDS;
self.debug_node_entry_root_overwrites.drain(0..drain);
}
}
#[cfg(any(test, feature = "diagnostics"))]
if self.strict_ownership
&& let Some(prev) = self.nodes.get(&id)
{
assert_eq!(
prev.root, entry.root,
"ownership root overwrite detected for element {id:?}: old_root={:?} new_root={:?} (cross-root reparenting must be explicit; see ADR 0176)",
prev.root, entry.root
);
}
self.nodes.insert(id, entry);
}
pub(crate) fn retain_nodes(&mut self, f: impl FnMut(&GlobalElementId, &mut NodeEntry) -> bool) {
self.nodes.retain(f);
if let Some(selection) = self.active_text_selection
&& !self.nodes.contains_key(&selection.element)
{
self.active_text_selection = None;
}
}
pub(crate) fn clear_stale_interaction_targets_for_frame(&mut self, frame_id: FrameId) {
let is_live_this_frame = |id: GlobalElementId| {
self.nodes
.get(&id)
.is_some_and(|entry| entry.last_seen_frame == frame_id)
};
if let Some(id) = self.hovered_pressable
&& !is_live_this_frame(id)
{
self.hovered_pressable = None;
self.hovered_pressable_node = None;
}
if let Some(id) = self.hovered_pressable_raw
&& !is_live_this_frame(id)
{
self.hovered_pressable_raw = None;
self.hovered_pressable_raw_node = None;
}
if let Some(id) = self.hovered_pressable_raw_below_barrier
&& !is_live_this_frame(id)
{
self.hovered_pressable_raw_below_barrier = None;
self.hovered_pressable_raw_below_barrier_node = None;
}
if let Some(id) = self.pressed_pressable
&& !is_live_this_frame(id)
{
self.pressed_pressable = None;
self.pressed_pressable_node = None;
}
if let Some(id) = self.hovered_hover_region
&& !is_live_this_frame(id)
{
self.hovered_hover_region = None;
self.hovered_hover_region_node = None;
}
}
pub(crate) fn sync_interaction_target_nodes(
&mut self,
mut resolve: impl FnMut(GlobalElementId, Option<NodeId>) -> Option<NodeId>,
) {
let hovered_pressable = self.hovered_pressable;
self.hovered_pressable_node = hovered_pressable.and_then(|element| {
resolve(
element,
self.hovered_pressable_node
.or_else(|| self.node_entry(element).map(|entry| entry.node)),
)
});
let hovered_pressable_raw = self.hovered_pressable_raw;
self.hovered_pressable_raw_node = hovered_pressable_raw.and_then(|element| {
resolve(
element,
self.hovered_pressable_raw_node
.or_else(|| self.node_entry(element).map(|entry| entry.node)),
)
});
let hovered_pressable_raw_below_barrier = self.hovered_pressable_raw_below_barrier;
self.hovered_pressable_raw_below_barrier_node = hovered_pressable_raw_below_barrier
.and_then(|element| {
resolve(
element,
self.hovered_pressable_raw_below_barrier_node
.or_else(|| self.node_entry(element).map(|entry| entry.node)),
)
});
let pressed_pressable = self.pressed_pressable;
self.pressed_pressable_node = pressed_pressable.and_then(|element| {
resolve(
element,
self.pressed_pressable_node
.or_else(|| self.node_entry(element).map(|entry| entry.node)),
)
});
let hovered_hover_region = self.hovered_hover_region;
self.hovered_hover_region_node = hovered_hover_region.and_then(|element| {
resolve(
element,
self.hovered_hover_region_node
.or_else(|| self.node_entry(element).map(|entry| entry.node)),
)
});
}
pub(crate) fn sync_active_text_selection_node(
&mut self,
mut resolve: impl FnMut(GlobalElementId, Option<NodeId>) -> Option<NodeId>,
) {
let Some(mut selection) = self.active_text_selection else {
return;
};
let Some(node) = resolve(
selection.element,
Some(selection.node)
.or_else(|| self.node_entry(selection.element).map(|entry| entry.node)),
) else {
self.active_text_selection = None;
return;
};
selection.node = node;
if let Some(root) = self.node_entry(selection.element).map(|entry| entry.root) {
selection.root = root;
}
self.active_text_selection = Some(selection);
}
pub(crate) fn set_root_bounds(&mut self, root: GlobalElementId, bounds: Rect) {
self.root_bounds.insert(root, bounds);
}
pub(crate) fn root_bounds(&self, root: GlobalElementId) -> Option<Rect> {
self.root_bounds.get(&root).copied()
}
pub(crate) fn record_bounds(&mut self, element: GlobalElementId, bounds: Rect) {
self.cur_bounds.insert(element, bounds);
if !self.has_state::<LayoutQueryRegionMarker>(element) {
return;
}
if self
.layout_query_regions_changed_this_frame
.contains(&element)
{
return;
}
const EPS_PX: f32 = 0.5;
let changed = match self.prev_bounds.get(&element).copied() {
None => true,
Some(prev) => {
(prev.size.width.0 - bounds.size.width.0).abs() > EPS_PX
|| (prev.size.height.0 - bounds.size.height.0).abs() > EPS_PX
}
};
if changed {
self.layout_query_region_revisions
.entry(element)
.and_modify(|v| *v = v.saturating_add(1))
.or_insert(1);
self.layout_query_regions_changed_this_frame.insert(element);
}
}
pub(crate) fn element_nodes_copy_into(&self, out: &mut Vec<(GlobalElementId, NodeId)>) {
out.clear();
if out.capacity() < self.nodes.len() {
out.reserve(self.nodes.len() - out.capacity());
}
out.extend(
self.nodes
.iter()
.map(|(&element, entry)| (element, entry.node)),
);
}
pub(crate) fn last_bounds(&self, element: GlobalElementId) -> Option<Rect> {
self.prev_bounds.get(&element).copied()
}
pub(crate) fn current_bounds(&self, element: GlobalElementId) -> Option<Rect> {
self.cur_bounds
.get(&element)
.copied()
.or_else(|| self.prev_bounds.get(&element).copied())
}
pub(crate) fn record_visual_bounds(&mut self, element: GlobalElementId, bounds: Rect) {
self.cur_visual_bounds.insert(element, bounds);
}
pub(crate) fn last_visual_bounds(&self, element: GlobalElementId) -> Option<Rect> {
self.prev_visual_bounds.get(&element).copied()
}
pub(crate) fn current_visual_bounds(&self, element: GlobalElementId) -> Option<Rect> {
self.cur_visual_bounds
.get(&element)
.copied()
.or_else(|| self.prev_visual_bounds.get(&element).copied())
}
pub(crate) fn committed_viewport_bounds(&self) -> Rect {
self.committed_viewport_bounds
}
pub(crate) fn committed_scale_factor(&self) -> f32 {
self.committed_scale_factor
}
pub(crate) fn committed_color_scheme(&self) -> Option<ColorScheme> {
self.committed_color_scheme
}
pub(crate) fn committed_prefers_reduced_motion(&self) -> Option<bool> {
self.committed_prefers_reduced_motion
}
pub(crate) fn committed_text_scale_factor(&self) -> Option<f32> {
self.committed_text_scale_factor
}
pub(crate) fn committed_prefers_reduced_transparency(&self) -> Option<bool> {
self.committed_prefers_reduced_transparency
}
pub(crate) fn committed_accent_color(&self) -> Option<Color> {
self.committed_accent_color
}
pub(crate) fn committed_contrast_preference(&self) -> Option<ContrastPreference> {
self.committed_contrast_preference
}
pub(crate) fn committed_forced_colors_mode(&self) -> Option<ForcedColorsMode> {
self.committed_forced_colors_mode
}
pub(crate) fn committed_primary_pointer_type(&self) -> PointerType {
self.committed_primary_pointer_type
.unwrap_or(PointerType::Unknown)
}
pub(crate) fn committed_safe_area_insets(&self) -> Option<Edges> {
self.committed_safe_area_insets
}
pub(crate) fn committed_occlusion_insets(&self) -> Option<Edges> {
self.committed_occlusion_insets
}
pub(crate) fn occlusion_insets_changed_this_frame(&self) -> bool {
self.environment_changed_this_frame
.contains(&EnvironmentQueryKey::OcclusionInsets)
}
pub(crate) fn set_committed_prefers_reduced_motion(&mut self, value: Option<bool>) {
if self.committed_prefers_reduced_motion == value {
return;
}
self.committed_prefers_reduced_motion = value;
self.environment_revisions
.entry(EnvironmentQueryKey::PrefersReducedMotion)
.and_modify(|v| *v = v.saturating_add(1))
.or_insert(1);
self.environment_changed_this_frame
.insert(EnvironmentQueryKey::PrefersReducedMotion);
}
#[allow(dead_code)]
pub(crate) fn set_committed_text_scale_factor(&mut self, value: Option<f32>) {
let changed = match (self.committed_text_scale_factor, value) {
(Some(a), Some(b)) => (a - b).abs() > 0.0001,
(None, None) => false,
_ => true,
};
if !changed {
return;
}
self.committed_text_scale_factor = value;
self.environment_revisions
.entry(EnvironmentQueryKey::TextScaleFactor)
.and_modify(|v| *v = v.saturating_add(1))
.or_insert(1);
self.environment_changed_this_frame
.insert(EnvironmentQueryKey::TextScaleFactor);
}
#[allow(dead_code)]
pub(crate) fn set_committed_prefers_reduced_transparency(&mut self, value: Option<bool>) {
if self.committed_prefers_reduced_transparency == value {
return;
}
self.committed_prefers_reduced_transparency = value;
self.environment_revisions
.entry(EnvironmentQueryKey::PrefersReducedTransparency)
.and_modify(|v| *v = v.saturating_add(1))
.or_insert(1);
self.environment_changed_this_frame
.insert(EnvironmentQueryKey::PrefersReducedTransparency);
}
#[allow(dead_code)]
pub(crate) fn set_committed_accent_color(&mut self, value: Option<Color>) {
if self.committed_accent_color == value {
return;
}
self.committed_accent_color = value;
self.environment_revisions
.entry(EnvironmentQueryKey::AccentColor)
.and_modify(|v| *v = v.saturating_add(1))
.or_insert(1);
self.environment_changed_this_frame
.insert(EnvironmentQueryKey::AccentColor);
}
pub(crate) fn set_committed_color_scheme(&mut self, value: Option<ColorScheme>) {
if self.committed_color_scheme == value {
return;
}
self.committed_color_scheme = value;
self.environment_revisions
.entry(EnvironmentQueryKey::ColorScheme)
.and_modify(|v| *v = v.saturating_add(1))
.or_insert(1);
self.environment_changed_this_frame
.insert(EnvironmentQueryKey::ColorScheme);
}
pub(crate) fn set_committed_contrast_preference(&mut self, value: Option<ContrastPreference>) {
if self.committed_contrast_preference == value {
return;
}
self.committed_contrast_preference = value;
self.environment_revisions
.entry(EnvironmentQueryKey::PrefersContrast)
.and_modify(|v| *v = v.saturating_add(1))
.or_insert(1);
self.environment_changed_this_frame
.insert(EnvironmentQueryKey::PrefersContrast);
}
pub(crate) fn set_committed_forced_colors_mode(&mut self, value: Option<ForcedColorsMode>) {
if self.committed_forced_colors_mode == value {
return;
}
self.committed_forced_colors_mode = value;
self.environment_revisions
.entry(EnvironmentQueryKey::ForcedColorsMode)
.and_modify(|v| *v = v.saturating_add(1))
.or_insert(1);
self.environment_changed_this_frame
.insert(EnvironmentQueryKey::ForcedColorsMode);
}
pub(crate) fn record_committed_primary_pointer_type(&mut self, pointer_type: PointerType) {
if self.committed_primary_pointer_type == Some(pointer_type) {
return;
}
self.committed_primary_pointer_type = Some(pointer_type);
self.environment_revisions
.entry(EnvironmentQueryKey::PrimaryPointerType)
.and_modify(|v| *v = v.saturating_add(1))
.or_insert(1);
self.environment_changed_this_frame
.insert(EnvironmentQueryKey::PrimaryPointerType);
}
pub(crate) fn record_committed_safe_area_insets(&mut self, insets: Option<Edges>) {
if self.committed_safe_area_insets == insets {
return;
}
self.committed_safe_area_insets = insets;
self.environment_revisions
.entry(EnvironmentQueryKey::SafeAreaInsets)
.and_modify(|v| *v = v.saturating_add(1))
.or_insert(1);
self.environment_changed_this_frame
.insert(EnvironmentQueryKey::SafeAreaInsets);
}
pub(crate) fn record_committed_occlusion_insets(&mut self, insets: Option<Edges>) {
if self.committed_occlusion_insets == insets {
return;
}
self.committed_occlusion_insets = insets;
self.environment_revisions
.entry(EnvironmentQueryKey::OcclusionInsets)
.and_modify(|v| *v = v.saturating_add(1))
.or_insert(1);
self.environment_changed_this_frame
.insert(EnvironmentQueryKey::OcclusionInsets);
}
pub(crate) fn record_committed_viewport_bounds(&mut self, bounds: Rect) {
const EPS_PX: f32 = 0.5;
let prev = self.committed_viewport_bounds.size;
let next = bounds.size;
let changed = (prev.width.0 - next.width.0).abs() > EPS_PX
|| (prev.height.0 - next.height.0).abs() > EPS_PX;
self.committed_viewport_bounds = bounds;
if !changed {
return;
}
self.environment_revisions
.entry(EnvironmentQueryKey::ViewportSize)
.and_modify(|v| *v = v.saturating_add(1))
.or_insert(1);
self.environment_changed_this_frame
.insert(EnvironmentQueryKey::ViewportSize);
}
pub(crate) fn record_committed_scale_factor(&mut self, scale_factor: f32) {
if (self.committed_scale_factor - scale_factor).abs() < 0.0001 {
return;
}
self.committed_scale_factor = scale_factor;
self.environment_revisions
.entry(EnvironmentQueryKey::ScaleFactor)
.and_modify(|v| *v = v.saturating_add(1))
.or_insert(1);
self.environment_changed_this_frame
.insert(EnvironmentQueryKey::ScaleFactor);
}
pub(crate) fn wants_continuous_frames(&self) -> bool {
self.continuous_frames.load(Ordering::Relaxed) > 0
}
pub(crate) fn begin_continuous_frames(
&self,
owner: Option<GlobalElementId>,
) -> ContinuousFrames {
self.continuous_frames.fetch_add(1, Ordering::Relaxed);
#[cfg(not(feature = "diagnostics"))]
let _ = owner;
#[cfg(feature = "diagnostics")]
if let Some(owner) = owner {
let mut owners = self
.continuous_frame_owners
.lock()
.unwrap_or_else(|err| err.into_inner());
owners
.entry(owner)
.and_modify(|count| *count = count.saturating_add(1))
.or_insert(1);
}
ContinuousFrames {
leases: self.continuous_frames.clone(),
#[cfg(feature = "diagnostics")]
owners: owner.map(|owner| (self.continuous_frame_owners.clone(), owner)),
}
}
#[cfg(feature = "diagnostics")]
fn diagnostics_snapshot(&self) -> WindowElementDiagnosticsSnapshot {
let invalidation_sort_key = |inv: Invalidation| match inv {
Invalidation::Layout => 0u8,
Invalidation::Paint => 1u8,
Invalidation::HitTest => 2u8,
Invalidation::HitTestOnly => 3u8,
};
let env_key_label = |key: EnvironmentQueryKey| match key {
EnvironmentQueryKey::ViewportSize => "viewport_size",
EnvironmentQueryKey::ScaleFactor => "scale_factor",
EnvironmentQueryKey::ColorScheme => "color_scheme",
EnvironmentQueryKey::PrefersReducedMotion => "prefers_reduced_motion",
EnvironmentQueryKey::TextScaleFactor => "text_scale_factor",
EnvironmentQueryKey::PrefersReducedTransparency => "prefers_reduced_transparency",
EnvironmentQueryKey::AccentColor => "accent_color",
EnvironmentQueryKey::PrefersContrast => "prefers_contrast",
EnvironmentQueryKey::ForcedColorsMode => "forced_colors_mode",
EnvironmentQueryKey::PrimaryPointerType => "primary_pointer_type",
EnvironmentQueryKey::SafeAreaInsets => "safe_area_insets",
EnvironmentQueryKey::OcclusionInsets => "occlusion_insets",
};
let bounds_for = |element: Option<GlobalElementId>| {
element.and_then(|id| {
self.prev_bounds
.get(&id)
.copied()
.or_else(|| self.cur_bounds.get(&id).copied())
})
};
let visual_bounds_for = |element: Option<GlobalElementId>| {
element.and_then(|id| {
self.prev_visual_bounds
.get(&id)
.copied()
.or_else(|| self.cur_visual_bounds.get(&id).copied())
.or_else(|| {
self.prev_bounds
.get(&id)
.copied()
.or_else(|| self.cur_bounds.get(&id).copied())
})
})
};
let node_for = |element: Option<GlobalElementId>| {
element.and_then(|id| self.node_entry(id).map(|e| e.node))
};
let layout_query_region_name_for = |element: GlobalElementId| -> Option<StdArc<str>> {
let key = (element, TypeId::of::<LayoutQueryRegionMarker>());
self.state_any_ref(&key)
.and_then(|any| any.downcast_ref::<LayoutQueryRegionMarker>())
.and_then(|marker| marker.name.clone())
};
let mut layout_query_regions: Vec<LayoutQueryRegionDiagnosticsSnapshot> = self
.nodes
.iter()
.filter(|(_, entry)| entry.last_seen_frame == self.prepared_frame)
.map(|(&element, _)| element)
.filter(|&element| self.has_state::<LayoutQueryRegionMarker>(element))
.map(|element| {
let committed_bounds = self.prev_bounds.get(&element).copied();
let current_bounds = self.cur_bounds.get(&element).copied().or(committed_bounds);
LayoutQueryRegionDiagnosticsSnapshot {
region: element,
name: layout_query_region_name_for(element),
revision: self.layout_query_region_revision(element),
changed_this_frame: self
.layout_query_regions_changed_this_frame
.contains(&element),
committed_bounds,
current_bounds,
}
})
.collect();
layout_query_regions.sort_by_key(|r| r.region.0);
let mut observed_layout_queries: Vec<ElementObservedLayoutQueriesDiagnosticsSnapshot> =
self.observed_layout_queries_next
.iter()
.map(|(element, list)| {
let deps_fingerprint = self.layout_query_deps_fingerprint_from_list(Some(list));
let mut regions: Vec<ObservedLayoutQueryRegionDiagnosticsSnapshot> = list
.iter()
.map(
|(region, inv)| ObservedLayoutQueryRegionDiagnosticsSnapshot {
region: *region,
invalidation: *inv,
region_revision: self.layout_query_region_revision(*region),
region_changed_this_frame: self
.layout_query_regions_changed_this_frame
.contains(region),
region_name: layout_query_region_name_for(*region),
region_committed_bounds: self.prev_bounds.get(region).copied(),
},
)
.collect();
regions.sort_by(|a, b| {
a.region.0.cmp(&b.region.0).then_with(|| {
let key = |inv: Invalidation| match inv {
Invalidation::Layout => 0u8,
Invalidation::Paint => 1u8,
Invalidation::HitTest => 2u8,
Invalidation::HitTestOnly => 3u8,
};
key(a.invalidation).cmp(&key(b.invalidation))
})
});
ElementObservedLayoutQueriesDiagnosticsSnapshot {
element: *element,
deps_fingerprint,
regions,
}
})
.collect();
observed_layout_queries.sort_by_key(|e| e.element.0);
let mut observed_environment: Vec<ElementObservedEnvironmentDiagnosticsSnapshot> = self
.observed_environment_next
.iter()
.map(|(element, list)| {
let deps_fingerprint = self.environment_deps_fingerprint_from_list(Some(list));
let mut keys: Vec<ObservedEnvironmentKeyDiagnosticsSnapshot> = list
.iter()
.map(|(key, inv)| ObservedEnvironmentKeyDiagnosticsSnapshot {
key: StdArc::<str>::from(env_key_label(*key)),
invalidation: *inv,
key_revision: self.environment_revision(*key),
key_changed_this_frame: self.environment_changed_this_frame.contains(key),
})
.collect();
keys.sort_by(|a, b| {
a.key.as_ref().cmp(b.key.as_ref()).then_with(|| {
invalidation_sort_key(a.invalidation)
.cmp(&invalidation_sort_key(b.invalidation))
})
});
ElementObservedEnvironmentDiagnosticsSnapshot {
element: *element,
deps_fingerprint,
keys,
}
})
.collect();
observed_environment.sort_by_key(|e| e.element.0);
let environment = EnvironmentQueryDiagnosticsSnapshot {
viewport_bounds: self.committed_viewport_bounds,
scale_factor: self.committed_scale_factor,
color_scheme: self.committed_color_scheme,
prefers_reduced_motion: self.committed_prefers_reduced_motion,
text_scale_factor: self.committed_text_scale_factor,
prefers_reduced_transparency: self.committed_prefers_reduced_transparency,
accent_color: self.committed_accent_color,
contrast_preference: self.committed_contrast_preference,
forced_colors_mode: self.committed_forced_colors_mode,
primary_pointer_type: self.committed_primary_pointer_type(),
safe_area_insets: self.committed_safe_area_insets,
occlusion_insets: self.committed_occlusion_insets,
};
let mut view_cache_reuse_roots: Vec<GlobalElementId> =
self.view_cache_reuse_roots.iter().copied().collect();
view_cache_reuse_roots.sort_by_key(|id| id.0);
let view_cache_reuse_root_element_counts: Vec<(GlobalElementId, u32)> =
view_cache_reuse_roots
.iter()
.map(|root| {
let count = self
.view_cache_elements_rendered
.get(root)
.map(|v| v.len())
.unwrap_or(0);
(*root, count.min(u32::MAX as usize) as u32)
})
.collect();
const ELEMENT_SAMPLE: usize = 16;
let view_cache_reuse_root_element_samples: Vec<ViewCacheReuseRootElementsSample> =
view_cache_reuse_roots
.iter()
.map(|&root| {
let elements = self.view_cache_elements_for_root(root).unwrap_or(&[]);
let elements_len = elements.len().min(u32::MAX as usize) as u32;
let elements_head: Vec<GlobalElementId> =
elements.iter().take(ELEMENT_SAMPLE).copied().collect();
let elements_tail: Vec<GlobalElementId> = if elements.len() > ELEMENT_SAMPLE {
elements
.iter()
.skip(elements.len().saturating_sub(ELEMENT_SAMPLE))
.copied()
.collect()
} else {
Vec::new()
};
ViewCacheReuseRootElementsSample {
root,
node: self.node_entry(root).map(|e| e.node),
elements_len,
elements_head,
elements_tail,
}
})
.collect();
let mut continuous_frame_leases: Vec<ContinuousFrameLeaseDiagnosticsSnapshot> = {
let owners = self
.continuous_frame_owners
.lock()
.unwrap_or_else(|err| err.into_inner());
owners
.iter()
.map(
|(&element, &count)| ContinuousFrameLeaseDiagnosticsSnapshot {
element,
count,
debug_path: self.debug_path_for_element(element),
},
)
.collect()
};
continuous_frame_leases.sort_by(|a, b| {
b.count
.cmp(&a.count)
.then_with(|| a.element.0.cmp(&b.element.0))
});
let mut animation_frame_request_roots: Vec<AnimationFrameRequestRootDiagnosticsSnapshot> =
self.raf_notify_roots_debug
.iter()
.copied()
.map(|element| AnimationFrameRequestRootDiagnosticsSnapshot {
element,
debug_path: self.debug_path_for_element(element),
})
.collect();
animation_frame_request_roots.sort_by_key(|entry| entry.element.0);
let rendered_state_entries = self.rendered_state.len() as u64;
let next_state_entries = self.next_state.len() as u64;
let lag_state_frames = self.lag_state.len() as u64;
let lag_state_entries_total = self
.lag_state
.iter()
.fold(0u64, |acc, state| acc.saturating_add(state.len() as u64));
let state_entries_total = rendered_state_entries
.saturating_add(next_state_entries)
.saturating_add(lag_state_entries_total);
let nodes_count = self.nodes.len() as u64;
let bounds_entries_total = (self.root_bounds.len() as u64)
.saturating_add(self.prev_bounds.len() as u64)
.saturating_add(self.cur_bounds.len() as u64)
.saturating_add(self.prev_visual_bounds.len() as u64)
.saturating_add(self.cur_visual_bounds.len() as u64);
let timer_targets_count = self.timer_targets.len() as u64;
let transient_events_count = self.transient_events.len() as u64;
let view_cache_state_key_roots_count = (self.view_cache_state_keys_rendered.len() as u64)
.saturating_add(self.view_cache_state_keys_next.len() as u64);
let view_cache_state_key_entries_total = self
.view_cache_state_keys_rendered
.values()
.fold(0u64, |acc, keys| acc.saturating_add(keys.len() as u64))
.saturating_add(
self.view_cache_state_keys_next
.values()
.fold(0u64, |acc, keys| acc.saturating_add(keys.len() as u64)),
);
let view_cache_element_roots_count = (self.view_cache_elements_rendered.len() as u64)
.saturating_add(self.view_cache_elements_next.len() as u64);
let view_cache_element_entries_total = self
.view_cache_elements_rendered
.values()
.fold(0u64, |acc, elements| {
acc.saturating_add(elements.len() as u64)
})
.saturating_add(
self.view_cache_elements_next
.values()
.fold(0u64, |acc, elements| {
acc.saturating_add(elements.len() as u64)
}),
);
let view_cache_key_mismatch_roots_count = self.view_cache_key_mismatch_roots.len() as u64;
let scratch_element_children_vec_pool_len =
self.scratch_element_children_vec_pool.len() as u64;
let scratch_element_children_vec_pool_capacity_total = self
.scratch_element_children_vec_pool
.iter()
.fold(0u64, |acc, vec| acc.saturating_add(vec.capacity() as u64));
let scratch_element_children_vec_pool_bytes_estimate_total = self
.scratch_element_children_vec_pool
.iter()
.fold(0u64, |acc, vec| {
acc.saturating_add(
(vec.capacity() as u64)
.saturating_mul(std::mem::size_of::<AnyElement>() as u64),
)
});
const KEEP_ALIVE_ROOT_SAMPLE: usize = 16;
let mut retained_keep_alive_roots: Vec<NodeId> = self
.retained_virtual_list_keep_alive_roots
.iter()
.copied()
.collect();
retained_keep_alive_roots.sort_by_key(|n| n.data().as_ffi());
let retained_keep_alive_roots_len =
retained_keep_alive_roots.len().min(u32::MAX as usize) as u32;
let retained_keep_alive_roots_head: Vec<NodeId> = retained_keep_alive_roots
.iter()
.take(KEEP_ALIVE_ROOT_SAMPLE)
.copied()
.collect();
let retained_keep_alive_roots_tail: Vec<NodeId> =
if retained_keep_alive_roots.len() > KEEP_ALIVE_ROOT_SAMPLE {
retained_keep_alive_roots
.iter()
.skip(
retained_keep_alive_roots
.len()
.saturating_sub(KEEP_ALIVE_ROOT_SAMPLE),
)
.copied()
.collect()
} else {
Vec::new()
};
WindowElementDiagnosticsSnapshot {
focused_element: self.focused_element,
focused_element_node: node_for(self.focused_element),
focused_element_bounds: bounds_for(self.focused_element),
focused_element_visual_bounds: visual_bounds_for(self.focused_element),
active_text_selection: self
.active_text_selection
.map(|sel| (sel.root, sel.element)),
hovered_pressable: self.hovered_pressable,
hovered_pressable_node: self.hovered_pressable_node,
hovered_pressable_bounds: bounds_for(self.hovered_pressable),
hovered_pressable_visual_bounds: visual_bounds_for(self.hovered_pressable),
pressed_pressable: self.pressed_pressable,
pressed_pressable_node: self.pressed_pressable_node,
pressed_pressable_bounds: bounds_for(self.pressed_pressable),
pressed_pressable_visual_bounds: visual_bounds_for(self.pressed_pressable),
hovered_hover_region: self.hovered_hover_region,
wants_continuous_frames: self.wants_continuous_frames(),
continuous_frame_leases,
animation_frame_request_roots,
observed_models: self
.observed_models_next
.iter()
.map(|(element, list)| {
(
*element,
list.iter()
.map(|(model, inv)| (model.data().as_ffi(), *inv))
.collect(),
)
})
.collect(),
observed_globals: self
.observed_globals_next
.iter()
.map(|(element, list)| {
(
*element,
list.iter()
.map(|(ty, inv)| (format!("{ty:?}"), *inv))
.collect(),
)
})
.collect(),
observed_layout_queries,
layout_query_regions,
environment,
observed_environment,
view_cache_reuse_roots,
view_cache_reuse_root_element_counts,
view_cache_reuse_root_element_samples,
rendered_state_entries,
next_state_entries,
lag_state_frames,
lag_state_entries_total,
state_entries_total,
nodes_count,
bounds_entries_total,
timer_targets_count,
transient_events_count,
view_cache_state_key_roots_count,
view_cache_state_key_entries_total,
view_cache_element_roots_count,
view_cache_element_entries_total,
view_cache_key_mismatch_roots_count,
scratch_element_children_vec_pool_len,
scratch_element_children_vec_pool_capacity_total,
scratch_element_children_vec_pool_bytes_estimate_total,
retained_keep_alive_roots_len,
retained_keep_alive_roots_head,
retained_keep_alive_roots_tail,
node_entry_root_overwrites: self.debug_node_entry_root_overwrites.clone(),
overlay_placement: self.overlay_placement.clone(),
}
}
#[cfg(feature = "diagnostics")]
pub(crate) fn record_overlay_placement_anchored_panel(
&mut self,
frame_id: FrameId,
overlay_root_name: Option<StdArc<str>>,
anchor_element: Option<GlobalElementId>,
content_element: Option<GlobalElementId>,
trace: AnchoredPanelLayoutTrace,
) {
let name = overlay_root_name.clone();
let next = OverlayPlacementDiagnosticsRecord::AnchoredPanel(
OverlayAnchoredPanelPlacementDiagnosticsRecord {
frame_id,
overlay_root_name,
anchor_element,
content_element,
trace,
},
);
if let Some(existing) = self
.overlay_placement
.iter_mut()
.rev()
.find(|rec| match rec {
OverlayPlacementDiagnosticsRecord::AnchoredPanel(r) => {
r.overlay_root_name == name
&& r.anchor_element == anchor_element
&& r.content_element == content_element
}
_ => false,
})
{
*existing = next;
return;
}
self.overlay_placement.push(next);
}
#[cfg(feature = "diagnostics")]
#[allow(clippy::too_many_arguments)]
pub(crate) fn record_overlay_placement_placed_rect(
&mut self,
frame_id: FrameId,
overlay_root_name: Option<StdArc<str>>,
anchor_element: Option<GlobalElementId>,
content_element: Option<GlobalElementId>,
outer: Rect,
anchor: Rect,
placed: Rect,
side: Option<Side>,
) {
let name = overlay_root_name.clone();
let next =
OverlayPlacementDiagnosticsRecord::PlacedRect(OverlayPlacedRectDiagnosticsRecord {
frame_id,
overlay_root_name,
anchor_element,
content_element,
outer,
anchor,
placed,
side,
});
if let Some(existing) = self
.overlay_placement
.iter_mut()
.rev()
.find(|rec| match rec {
OverlayPlacementDiagnosticsRecord::PlacedRect(r) => {
r.overlay_root_name == name
&& r.anchor_element == anchor_element
&& r.content_element == content_element
}
_ => false,
})
{
*existing = next;
return;
}
self.overlay_placement.push(next);
}
#[cfg(feature = "diagnostics")]
pub(crate) fn record_debug_root(
&mut self,
frame_id: FrameId,
root: GlobalElementId,
name: &str,
) {
self.debug_identity.entries.insert(
root,
DebugIdentityEntry {
parent: None,
segment: DebugIdentitySegment::Root {
name: StdArc::<str>::from(name),
},
last_seen_frame: frame_id,
},
);
}
#[cfg(feature = "diagnostics")]
#[allow(clippy::too_many_arguments)]
pub(crate) fn record_debug_child(
&mut self,
frame_id: FrameId,
parent: GlobalElementId,
child: GlobalElementId,
file: &'static str,
line: u32,
column: u32,
key_hash: Option<u64>,
name: Option<&str>,
slot: u64,
) {
self.debug_identity.entries.insert(
child,
DebugIdentityEntry {
parent: Some(parent),
segment: DebugIdentitySegment::Child {
file,
line,
column,
key_hash,
name: name.map(StdArc::<str>::from),
slot,
},
last_seen_frame: frame_id,
},
);
}
#[cfg(feature = "diagnostics")]
pub(crate) fn touch_debug_identity_for_element(
&mut self,
frame_id: FrameId,
element: GlobalElementId,
) {
let mut cur = element;
let mut guard = 0usize;
while guard < 256 {
guard += 1;
let Some(entry) = self.debug_identity.entries.get_mut(&cur) else {
break;
};
if entry.last_seen_frame == frame_id {
break;
}
entry.last_seen_frame = frame_id;
let Some(parent) = entry.parent else {
break;
};
cur = parent;
}
}
#[cfg(feature = "diagnostics")]
pub(crate) fn debug_path_for_element(&self, element: GlobalElementId) -> Option<String> {
let mut segments: Vec<String> = Vec::new();
let mut cur = element;
let mut guard = 0usize;
while guard < 256 {
guard += 1;
let entry = self.debug_identity.entries.get(&cur)?;
segments.push(entry.segment.format());
if let Some(parent) = entry.parent {
cur = parent;
continue;
}
break;
}
segments.reverse();
Some(format!("{} ({:#x})", segments.join("."), element.0))
}
}
#[must_use]
pub struct ContinuousFrames {
leases: Arc<AtomicUsize>,
#[cfg(feature = "diagnostics")]
owners: Option<(Arc<Mutex<HashMap<GlobalElementId, u32>>>, GlobalElementId)>,
}
impl Drop for ContinuousFrames {
fn drop(&mut self) {
let _ = self
.leases
.fetch_update(Ordering::Relaxed, Ordering::Relaxed, |v| v.checked_sub(1));
#[cfg(feature = "diagnostics")]
if let Some((owners, owner)) = self.owners.as_ref() {
let mut owners = owners.lock().unwrap_or_else(|err| err.into_inner());
if let Some(count) = owners.get_mut(owner) {
if *count <= 1 {
owners.remove(owner);
} else {
*count -= 1;
}
}
}
}
}
#[cfg(feature = "diagnostics")]
#[derive(Default)]
struct DebugIdentityRegistry {
entries: HashMap<GlobalElementId, DebugIdentityEntry>,
}
#[cfg(feature = "diagnostics")]
struct DebugIdentityEntry {
parent: Option<GlobalElementId>,
segment: DebugIdentitySegment,
last_seen_frame: FrameId,
}
#[cfg(feature = "diagnostics")]
enum DebugIdentitySegment {
Root {
name: StdArc<str>,
},
Child {
file: &'static str,
line: u32,
column: u32,
key_hash: Option<u64>,
name: Option<StdArc<str>>,
slot: u64,
},
}
#[cfg(feature = "diagnostics")]
impl DebugIdentitySegment {
fn format(&self) -> String {
match self {
DebugIdentitySegment::Root { name } => format!("root[{name}]"),
DebugIdentitySegment::Child {
file,
line,
column,
key_hash,
name,
slot,
} => {
if let Some(name) = name.as_deref() {
format!("{file}:{line}:{column}[name={name}]")
} else if let Some(k) = key_hash {
format!("{file}:{line}:{column}[key={k:#x}]")
} else {
format!("{file}:{line}:{column}[slot={slot}]")
}
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use fret_core::{Point, Px, Size};
#[test]
fn primary_pointer_type_defaults_to_unknown_until_observed() {
let mut state = WindowElementState::default();
assert_eq!(state.committed_primary_pointer_type(), PointerType::Unknown);
state.record_committed_primary_pointer_type(PointerType::Touch);
assert_eq!(state.committed_primary_pointer_type(), PointerType::Touch);
}
#[test]
fn transient_events_survive_one_frame_and_clear_on_read() {
let mut state = WindowElementState::default();
let element = GlobalElementId(123);
let key = 0xDEAD_BEEF;
state.prepare_for_frame(FrameId(1), 0);
state.record_transient_event(element, key);
state.prepare_for_frame(FrameId(2), 0);
assert!(state.take_transient_event(element, key));
assert!(!state.take_transient_event(element, key));
}
#[test]
fn transient_events_prune_after_two_frames_if_not_consumed() {
let mut state = WindowElementState::default();
let element = GlobalElementId(123);
let key = 0xDEAD_BEEF;
state.prepare_for_frame(FrameId(10), 0);
state.record_transient_event(element, key);
state.prepare_for_frame(FrameId(11), 0);
assert!(state.take_transient_event(element, key));
state.record_transient_event(element, key);
state.prepare_for_frame(FrameId(12), 0);
assert!(state.take_transient_event(element, key));
state.record_transient_event(element, key);
state.prepare_for_frame(FrameId(13), 0);
state.prepare_for_frame(FrameId(14), 0);
assert!(!state.take_transient_event(element, key));
}
#[test]
#[should_panic(expected = "ownership root overwrite detected")]
fn strict_ownership_panics_on_root_overwrite() {
let mut state = WindowElementState::default();
state.set_strict_ownership(true);
let element = GlobalElementId(123);
state.set_node_entry(
element,
NodeEntry {
node: NodeId::default(),
last_seen_frame: FrameId(1),
root: GlobalElementId(1),
},
);
state.set_node_entry(
element,
NodeEntry {
node: NodeId::default(),
last_seen_frame: FrameId(1),
root: GlobalElementId(2),
},
);
}
#[test]
fn selectable_text_span_bounds_can_be_read_by_element() {
let window = AppWindowId::default();
let element = GlobalElementId(123);
let node = NodeId::default();
let expected = crate::element::SelectableTextInteractiveSpanBounds {
range: 1..4,
tag: std::sync::Arc::<str>::from("link"),
bounds_local: Rect::new(
Point::new(Px(10.0), Px(20.0)),
Size::new(Px(30.0), Px(12.0)),
),
};
let mut runtime = ElementRuntime::new();
runtime.prepare_window_for_frame(window, FrameId(1));
{
let state = runtime.for_window_mut(window);
state.set_node_entry(
element,
NodeEntry {
node,
last_seen_frame: FrameId(1),
root: GlobalElementId(1),
},
);
state.with_state_mut(
element,
crate::element::SelectableTextState::default,
|span_state| {
span_state.interactive_span_bounds = vec![expected.clone()];
},
);
}
assert_eq!(
runtime.selectable_text_interactive_span_bounds_for_node(window, node),
Some(vec![expected.clone()])
);
assert_eq!(
runtime.selectable_text_interactive_span_bounds_for_element(window, element),
Some(vec![expected])
);
}
#[test]
fn primary_pointer_type_is_unknown_until_committed() {
let state = WindowElementState::default();
assert_eq!(state.committed_primary_pointer_type(), PointerType::Unknown);
}
#[test]
fn primary_pointer_type_revision_increments_on_change() {
let mut state = WindowElementState::default();
state.prepare_for_frame(FrameId(1), 0);
assert!(state.environment_revisions.is_empty());
state.record_committed_primary_pointer_type(PointerType::Mouse);
assert!(
state
.environment_changed_this_frame
.contains(&EnvironmentQueryKey::PrimaryPointerType)
);
let first_revision = state
.environment_revisions
.get(&EnvironmentQueryKey::PrimaryPointerType)
.copied()
.unwrap();
state.record_committed_primary_pointer_type(PointerType::Mouse);
assert_eq!(
state
.environment_revisions
.get(&EnvironmentQueryKey::PrimaryPointerType)
.copied()
.unwrap(),
first_revision
);
state.record_committed_primary_pointer_type(PointerType::Touch);
assert_eq!(
state
.environment_revisions
.get(&EnvironmentQueryKey::PrimaryPointerType)
.copied()
.unwrap(),
first_revision + 1
);
}
#[test]
fn color_scheme_revision_increments_on_change() {
let mut state = WindowElementState::default();
state.prepare_for_frame(FrameId(1), 0);
assert!(state.environment_revisions.is_empty());
state.set_committed_color_scheme(Some(ColorScheme::Light));
assert!(
state
.environment_changed_this_frame
.contains(&EnvironmentQueryKey::ColorScheme)
);
let first_revision = state
.environment_revisions
.get(&EnvironmentQueryKey::ColorScheme)
.copied()
.unwrap();
state.set_committed_color_scheme(Some(ColorScheme::Light));
assert_eq!(
state
.environment_revisions
.get(&EnvironmentQueryKey::ColorScheme)
.copied()
.unwrap(),
first_revision
);
state.set_committed_color_scheme(Some(ColorScheme::Dark));
assert_eq!(
state
.environment_revisions
.get(&EnvironmentQueryKey::ColorScheme)
.copied()
.unwrap(),
first_revision + 1
);
}
#[test]
fn prefers_contrast_revision_increments_on_change() {
let mut state = WindowElementState::default();
state.prepare_for_frame(FrameId(1), 0);
assert!(state.environment_revisions.is_empty());
state.set_committed_contrast_preference(Some(ContrastPreference::NoPreference));
assert!(
state
.environment_changed_this_frame
.contains(&EnvironmentQueryKey::PrefersContrast)
);
let first_revision = state
.environment_revisions
.get(&EnvironmentQueryKey::PrefersContrast)
.copied()
.unwrap();
state.set_committed_contrast_preference(Some(ContrastPreference::NoPreference));
assert_eq!(
state
.environment_revisions
.get(&EnvironmentQueryKey::PrefersContrast)
.copied()
.unwrap(),
first_revision
);
state.set_committed_contrast_preference(Some(ContrastPreference::More));
assert_eq!(
state
.environment_revisions
.get(&EnvironmentQueryKey::PrefersContrast)
.copied()
.unwrap(),
first_revision + 1
);
}
#[test]
fn forced_colors_mode_revision_increments_on_change() {
let mut state = WindowElementState::default();
state.prepare_for_frame(FrameId(1), 0);
assert!(state.environment_revisions.is_empty());
state.set_committed_forced_colors_mode(Some(ForcedColorsMode::None));
assert!(
state
.environment_changed_this_frame
.contains(&EnvironmentQueryKey::ForcedColorsMode)
);
let first_revision = state
.environment_revisions
.get(&EnvironmentQueryKey::ForcedColorsMode)
.copied()
.unwrap();
state.set_committed_forced_colors_mode(Some(ForcedColorsMode::None));
assert_eq!(
state
.environment_revisions
.get(&EnvironmentQueryKey::ForcedColorsMode)
.copied()
.unwrap(),
first_revision
);
state.set_committed_forced_colors_mode(Some(ForcedColorsMode::Active));
assert_eq!(
state
.environment_revisions
.get(&EnvironmentQueryKey::ForcedColorsMode)
.copied()
.unwrap(),
first_revision + 1
);
}
#[test]
fn safe_area_insets_is_none_until_committed() {
let state = WindowElementState::default();
assert_eq!(state.committed_safe_area_insets(), None);
}
#[test]
fn safe_area_insets_revision_increments_on_change() {
let mut state = WindowElementState::default();
state.prepare_for_frame(FrameId(1), 0);
assert!(state.environment_revisions.is_empty());
state.record_committed_safe_area_insets(Some(Edges::all(fret_core::Px(4.0))));
assert!(
state
.environment_changed_this_frame
.contains(&EnvironmentQueryKey::SafeAreaInsets)
);
let first_revision = state
.environment_revisions
.get(&EnvironmentQueryKey::SafeAreaInsets)
.copied()
.unwrap();
state.record_committed_safe_area_insets(Some(Edges::all(fret_core::Px(4.0))));
assert_eq!(
state
.environment_revisions
.get(&EnvironmentQueryKey::SafeAreaInsets)
.copied()
.unwrap(),
first_revision
);
state.record_committed_safe_area_insets(None);
assert_eq!(
state
.environment_revisions
.get(&EnvironmentQueryKey::SafeAreaInsets)
.copied()
.unwrap(),
first_revision + 1
);
}
#[test]
fn occlusion_insets_is_none_until_committed() {
let state = WindowElementState::default();
assert_eq!(state.committed_occlusion_insets(), None);
}
#[test]
fn occlusion_insets_revision_increments_on_change() {
let mut state = WindowElementState::default();
state.prepare_for_frame(FrameId(1), 0);
assert!(state.environment_revisions.is_empty());
state.record_committed_occlusion_insets(Some(Edges::all(fret_core::Px(16.0))));
assert!(
state
.environment_changed_this_frame
.contains(&EnvironmentQueryKey::OcclusionInsets)
);
let first_revision = state
.environment_revisions
.get(&EnvironmentQueryKey::OcclusionInsets)
.copied()
.unwrap();
state.record_committed_occlusion_insets(Some(Edges::all(fret_core::Px(16.0))));
assert_eq!(
state
.environment_revisions
.get(&EnvironmentQueryKey::OcclusionInsets)
.copied()
.unwrap(),
first_revision
);
state.record_committed_occlusion_insets(None);
assert_eq!(
state
.environment_revisions
.get(&EnvironmentQueryKey::OcclusionInsets)
.copied()
.unwrap(),
first_revision + 1
);
}
}