use crate::{ AnyElement, AppContext as _, Context, ElementId, Entity, Hsla, InteractiveElement, IntoElement, ParentElement, Render, StatefulInteractiveElement, Styled, Window, div, px, util::FluentBuilder, }; use collections::{HashMap, HashSet}; use rand::{Rng as _, SeedableRng as _, rngs::StdRng}; use std::{ cell::RefCell, ops::RangeInclusive, rc::{Rc, Weak}, }; /// Dispatched to no one: the action generated elements register handlers for. Not /// registered, so builds that enable `bench-support` through feature unification (such as /// the workspace's tests) don't gain an action namespace. #[derive(Clone, Default, PartialEq, crate::Action)] #[action(namespace = randomized_element_tree, no_json, no_register)] pub struct RandomizedElementTreeAction; /// The initial shape generated for a randomized element tree. #[derive(Clone, Copy, Debug, Eq, PartialEq)] pub enum RandomizedElementTreeTopology { /// Every generated element is a direct child of the root. Wide, /// Every generated element is the child of the previous element. Deep, /// Generated elements choose parents throughout the existing tree. Mixed, /// Generated elements usually extend one deep branch, with occasional root children. Narrow, } impl RandomizedElementTreeTopology { /// Every topology. pub const ALL: [Self; 4] = [Self::Wide, Self::Deep, Self::Mixed, Self::Narrow]; /// Whether the tree's depth grows with its element count. `Deep` nests every element /// under the previous one and `Narrow` nests four in five, so both recurse once per /// element through layout and paint; without gpui's `stacker` feature that overflows /// a 2 MB thread stack somewhere between 384 and 512 elements in a release build. pub fn is_tall(self) -> bool { matches!(self, Self::Deep | Self::Narrow) } fn from_seed(seed: u64) -> Self { match seed % 4 { 0 => Self::Wide, 1 => Self::Deep, 2 => Self::Mixed, _ => Self::Narrow, } } } /// Configuration for a reproducible randomized element-tree workload. #[derive(Clone, Copy, Debug, PartialEq)] pub struct RandomizedElementTreeConfig { seed: u64, element_count: usize, topology: RandomizedElementTreeTopology, entity_density: f64, handler_density: f64, } impl RandomizedElementTreeConfig { /// Creates an element-only workload whose topology is selected by `seed`. pub fn new(seed: u64, element_count: usize) -> Self { Self { seed, element_count, topology: RandomizedElementTreeTopology::from_seed(seed), entity_density: 0.0, handler_density: 0.0, } } /// Selects the initial tree topology independently of the RNG seed. pub fn with_topology(mut self, topology: RandomizedElementTreeTopology) -> Self { self.topology = topology; self } /// Sets the probability that each generated element is backed by a persistent entity. pub fn with_entity_density(mut self, density: f64) -> Self { self.entity_density = normalized_density(density); self } /// Sets the probability of each generated interaction feature on an element. pub fn with_handler_density(mut self, density: f64) -> Self { self.handler_density = normalized_density(density); self } /// Returns the seed used for generation and subsequent random mutations. pub fn seed(&self) -> u64 { self.seed } /// Returns the requested number of initial descendants. pub fn element_count(&self) -> usize { self.element_count } /// Returns the selected initial topology. pub fn topology(&self) -> RandomizedElementTreeTopology { self.topology } /// Returns the probability that a generated element is backed by an entity. pub fn entity_density(&self) -> f64 { self.entity_density } /// Returns the probability of each generated interaction feature. pub fn handler_density(&self) -> f64 { self.handler_density } } /// Bounds an RNG is drawn against to produce a [`RandomizedElementTreeConfig`]: a family /// of trees, such as "tall trees of 32 to 256 elements with up to half of them entities", /// from which one draw picks one member. Every field of the resulting config, and the /// tree built from it, follows from the RNG's state, so an RNG seeded by `#[gpui::bench]` /// or `#[gpui::test]` samples the family across seeds and any one seed reproduces exactly. #[derive(Clone, Debug, PartialEq)] pub struct RandomizedElementTreeBounds { topologies: Vec, element_count: RangeInclusive, entity_density: RangeInclusive, handler_density: RangeInclusive, } impl RandomizedElementTreeBounds { /// Trees of any topology with `element_count` elements, no entities and no handlers. pub fn new(element_count: RangeInclusive) -> Self { Self { topologies: RandomizedElementTreeTopology::ALL.to_vec(), element_count, entity_density: 0.0..=0.0, handler_density: 0.0..=0.0, } } /// Restricts the topologies a seed may pick. An empty list keeps them all. pub fn with_topologies( mut self, topologies: impl IntoIterator, ) -> Self { let topologies: Vec<_> = topologies.into_iter().collect(); if !topologies.is_empty() { self.topologies = topologies; } self } /// The range a seed draws the share of entity-backed elements from. pub fn with_entity_density(mut self, density: RangeInclusive) -> Self { self.entity_density = normalized_density(*density.start())..=normalized_density(*density.end()); self } /// The range a seed draws the per-feature handler probability from. pub fn with_handler_density(mut self, density: RangeInclusive) -> Self { self.handler_density = normalized_density(*density.start())..=normalized_density(*density.end()); self } /// Draws one config from these bounds. The config's own seed, which drives the tree's /// contents and mutation stream, is drawn from `rng` too, so all of it reproduces from /// the seed `rng` started from. pub fn sample(&self, rng: &mut StdRng) -> RandomizedElementTreeConfig { let topology = self.topologies[rng.random_range(0..self.topologies.len())]; let element_count = rng.random_range(self.element_count.clone()); RandomizedElementTreeConfig::new(rng.random(), element_count) .with_topology(topology) .with_entity_density(sample_range(&self.entity_density, rng)) .with_handler_density(sample_range(&self.handler_density, rng)) } } fn sample_range(range: &RangeInclusive, rng: &mut StdRng) -> f64 { if range.start() >= range.end() { *range.start() } else { rng.random_range(range.clone()) } } /// Work performed while converting randomized entities and elements into an element tree. #[derive(Clone, Debug, Default, Eq, PartialEq)] pub struct RandomizedElementTreeWorkCounters { root_render_count: usize, entity_render_count: usize, element_render_count: usize, handler_registration_count: usize, rendered_entity_ids: Vec, } impl RandomizedElementTreeWorkCounters { /// Returns how many times the persistent root's `Render` implementation ran. pub fn root_render_count(&self) -> usize { self.root_render_count } /// Returns how many randomized child entities rendered. pub fn entity_render_count(&self) -> usize { self.entity_render_count } /// Returns how many randomized descendant elements were reconstructed. pub fn element_render_count(&self) -> usize { self.element_render_count } /// Returns how many interaction handlers were registered while rendering. pub fn handler_registration_count(&self) -> usize { self.handler_registration_count } /// Returns stable IDs for the randomized child entities that rendered, in render order. pub fn rendered_entity_ids(&self) -> &[u64] { &self.rendered_entity_ids } fn reset(&mut self) { self.root_render_count = 0; self.entity_render_count = 0; self.element_render_count = 0; self.handler_registration_count = 0; self.rendered_entity_ids.clear(); } } /// A cloneable description of a randomized element tree's renderable state. #[derive(Clone, Debug, PartialEq)] pub struct RandomizedElementTreeSnapshot { root: RandomizedElementStyle, children: Vec, next_element_id: u64, paths_by_element_id: HashMap>, } impl RandomizedElementTreeSnapshot { /// Returns the number of randomized elements below the persistent root. pub fn descendant_count(&self) -> usize { let mut count = 0; let mut stack = self.children.iter().collect::>(); while let Some(node) = stack.pop() { count += 1; stack.extend(node.children.iter()); } count } /// Returns the deepest randomized element below the persistent root. /// /// A snapshot with no descendants has depth zero. pub fn max_depth(&self) -> usize { let mut max_depth = 0; let mut stack = self .children .iter() .map(|node| (node, 1)) .collect::>(); while let Some((node, depth)) = stack.pop() { max_depth = max_depth.max(depth); stack.extend(node.children.iter().map(|child| (child, depth + 1))); } max_depth } /// Returns the stable element IDs in depth-first order. pub fn element_ids(&self) -> Vec { node_paths(&self.children) .iter() .filter_map(|path| node_at_path(&self.children, path).map(|node| node.id)) .collect() } /// Returns the number of descendants backed by persistent child entities. pub fn entity_count(&self) -> usize { let mut count = 0; let mut stack = self.children.iter().collect::>(); while let Some(node) = stack.pop() { count += usize::from(node.is_entity); stack.extend(node.children.iter()); } count } /// Returns the number of interaction handlers in the generated tree. pub fn handler_count(&self) -> usize { let mut count = 0; let mut stack = self.children.iter().collect::>(); while let Some(node) = stack.pop() { count += node.features.handler_count(); stack.extend(node.children.iter()); } count } fn rebuild_element_paths(&mut self) { self.paths_by_element_id = node_paths(&self.children) .into_iter() .filter_map(|path| node_at_path(&self.children, &path).map(|node| (node.id, path))) .collect(); } fn node(&self, element_id: u64) -> Option<&RandomizedElementNode> { node_at_path(&self.children, self.paths_by_element_id.get(&element_id)?) } } /// A requested class of deterministic randomized-tree mutation. #[derive(Clone, Copy, Debug, Eq, PartialEq)] pub enum RandomizedElementTreeMutationKind { /// Change the root background color. RootColor, /// Change the root opacity. RootOpacity, /// Change the root border. RootBorder, /// Change the root width and height. RootBounds, /// Toggle clipping on the root. RootClipping, /// Toggle root visibility. RootVisibility, /// Change a seeded random descendant's colors. ChildColor, /// Change a seeded random descendant's opacity. ChildOpacity, /// Change a seeded random descendant's border. ChildBorder, /// Change a seeded random descendant's width and height. ChildBounds, /// Toggle clipping on a seeded random descendant. ChildClipping, /// Toggle visibility on a seeded random descendant. ChildVisibility, /// Reorder a seeded random descendant among its siblings. Reorder, /// Insert a generated descendant below a seeded random parent. Insert, /// Remove a seeded random descendant and its subtree. Remove, /// Remove a seeded random childless descendant. Unlike [`Self::Remove`] this changes /// the element count by exactly one, so it can alternate with [`Self::Insert`] in a /// long benchmark loop without the tree collapsing or growing. RemoveLeaf, } impl RandomizedElementTreeMutationKind { /// Every supported mutation kind, suitable for parameterized tests and benchmarks. pub const ALL: [Self; 16] = [ Self::RootColor, Self::RootOpacity, Self::RootBorder, Self::RootBounds, Self::RootClipping, Self::RootVisibility, Self::ChildColor, Self::ChildOpacity, Self::ChildBorder, Self::ChildBounds, Self::ChildClipping, Self::ChildVisibility, Self::Reorder, Self::Insert, Self::Remove, Self::RemoveLeaf, ]; } /// A deterministic mutation applied to a [`RandomizedElementTree`]. #[derive(Clone, Copy, Debug, Eq, PartialEq)] pub enum RandomizedElementTreeMutation { /// Changed the root background color. RootColor, /// Changed the root opacity. RootOpacity, /// Changed the root border. RootBorder, /// Changed the root width and height. RootBounds, /// Toggled clipping on the root. RootClipping, /// Toggled root visibility. RootVisibility, /// Changed a descendant's colors. ChildColor { /// The stable ID of the mutated element. element_id: u64, }, /// Changed a descendant's opacity. ChildOpacity { /// The stable ID of the mutated element. element_id: u64, }, /// Changed a descendant's border. ChildBorder { /// The stable ID of the mutated element. element_id: u64, }, /// Changed a descendant's width and height. ChildBounds { /// The stable ID of the mutated element. element_id: u64, }, /// Toggled clipping on a descendant. ChildClipping { /// The stable ID of the mutated element. element_id: u64, }, /// Toggled a descendant's visibility. ChildVisibility { /// The stable ID of the mutated element. element_id: u64, }, /// Moved an element to a different position among its siblings. Reordered { /// The stable ID of the moved element. element_id: u64, /// The stable ID of its parent, or `None` for the persistent root. parent_id: Option, /// The element's previous position. old_index: usize, /// The element's new position. new_index: usize, }, /// Inserted a newly generated element. Inserted { /// The stable ID of the inserted element. element_id: u64, /// The stable ID of its parent, or `None` for the persistent root. parent_id: Option, /// The position of the inserted element. index: usize, }, /// Removed an element and all of its descendants. Removed { /// The stable ID of the removed element. element_id: u64, /// The stable ID of its parent, or `None` for the persistent root. parent_id: Option, /// The former position of the removed element. index: usize, /// The total number of elements removed with the subtree. removed_element_count: usize, }, } /// Where the elements changed by one frame sit relative to each other. #[derive(Clone, Copy, Debug, Eq, PartialEq)] pub enum ChangeLocality { /// Inside one random entity's subtree, as a user action or one component's state /// change would be; the change reaches the rest of the tree only if the subtree has /// fewer elements than are changing (a common ancestor changing). Localized, /// Anywhere in the tree, uniformly: the worst case for a renderer that reuses clean /// subtrees, since every changed element may sit in a different one. Spread, } /// What [`RandomizedElementTree::recolor_children`] changed. #[derive(Clone, Copy, Debug, Eq, PartialEq)] pub struct RecoloredChildren { /// Descendants whose background changed. pub recolored: usize, /// Entity boundaries (or the root) notified, each once. pub notified: usize, } /// A persistent root view for randomized GPUI element-tree tests and benchmarks. pub struct RandomizedElementTree { snapshot: Rc>, entities: Rc>, work_counters: Rc>, config: RandomizedElementTreeConfig, rng: StdRng, } impl RandomizedElementTree { /// Generates a tree with `element_count` descendants and a mutation stream from `seed`. /// /// Different seeds deliberately produce wide, deep, mixed, and narrow initial topologies. pub fn new(seed: u64, element_count: usize) -> Self { Self::from_config(RandomizedElementTreeConfig::new(seed, element_count)) } /// Generates a configured tree and creates its persistent child entities. pub fn new_with_config(config: RandomizedElementTreeConfig, cx: &mut Context) -> Self { let mut tree = Self::from_config(config); tree.synchronize_entities(cx); tree } fn from_config(config: RandomizedElementTreeConfig) -> Self { let mut rng = StdRng::seed_from_u64(config.seed); let mut children = Vec::new(); let mut deepest_path = Vec::new(); for element_index in 0..config.element_count { let mut parent_path = match config.topology { RandomizedElementTreeTopology::Wide => Vec::new(), RandomizedElementTreeTopology::Deep => deepest_path.clone(), RandomizedElementTreeTopology::Mixed => random_parent_path(&children, &mut rng), RandomizedElementTreeTopology::Narrow => { if rng.random_bool(0.8) { deepest_path.clone() } else { Vec::new() } } }; let node = RandomizedElementNode::random( element_index as u64, config.entity_density, config.handler_density, &mut rng, ); let (_, inserted_path) = insert_node(&mut children, &mut parent_path, node); deepest_path = inserted_path; } let mut snapshot = RandomizedElementTreeSnapshot { root: RandomizedElementStyle::random_root(&mut rng), children, next_element_id: config.element_count as u64, paths_by_element_id: HashMap::default(), }; snapshot.rebuild_element_paths(); Self { snapshot: Rc::new(RefCell::new(snapshot)), entities: Rc::new(RefCell::new(HashMap::default())), work_counters: Rc::new(RefCell::new(RandomizedElementTreeWorkCounters::default())), config, rng, } } /// Reconstructs a tree from renderable state captured by [`Self::snapshot`]. pub fn from_snapshot(snapshot: RandomizedElementTreeSnapshot) -> Self { Self { config: RandomizedElementTreeConfig::new(0, snapshot.descendant_count()), snapshot: Rc::new(RefCell::new(snapshot)), entities: Rc::new(RefCell::new(HashMap::default())), work_counters: Rc::new(RefCell::new(RandomizedElementTreeWorkCounters::default())), rng: StdRng::seed_from_u64(0), } } /// Reconstructs a snapshot while preserving its randomized entity boundaries. pub fn from_snapshot_with_entities( snapshot: RandomizedElementTreeSnapshot, cx: &mut Context, ) -> Self { let mut tree = Self::from_snapshot(snapshot); tree.synchronize_entities(cx); tree } /// Captures all state that affects rendering and future element IDs. pub fn snapshot(&self) -> RandomizedElementTreeSnapshot { self.snapshot.borrow().clone() } /// Clears render work accumulated since construction or the previous reset. pub fn reset_work_counters(&mut self) { self.work_counters.borrow_mut().reset(); } /// Returns work accumulated since construction or the previous reset. pub fn work_counters(&self) -> RandomizedElementTreeWorkCounters { self.work_counters.borrow().clone() } /// Applies one seeded random style or structural mutation. /// /// This does not notify persistent child entities. Entity-aware callers should use /// [`Self::apply_random_mutation`] from an entity update instead. pub fn mutate(&mut self) -> RandomizedElementTreeMutation { let kind = RandomizedElementTreeMutationKind::ALL[self .rng .random_range(0..RandomizedElementTreeMutationKind::ALL.len())]; self.apply_mutation_without_notification(kind).mutation } /// Applies one seeded random mutation and notifies its nearest persistent entity boundary. pub fn apply_random_mutation( &mut self, cx: &mut Context, ) -> RandomizedElementTreeMutation { let kind = RandomizedElementTreeMutationKind::ALL[self .rng .random_range(0..RandomizedElementTreeMutationKind::ALL.len())]; self.apply_mutation(kind, cx) } /// Applies a selected mutation kind and notifies its nearest persistent entity boundary. pub fn apply_mutation( &mut self, kind: RandomizedElementTreeMutationKind, cx: &mut Context, ) -> RandomizedElementTreeMutation { let applied = self.apply_mutation_without_notification(kind); if applied.synchronize_entities { self.synchronize_entities(cx); } self.notify_owner(applied.owner_entity_id, cx); applied.mutation } /// Generates and inserts an element below a seeded random parent. /// /// This does not synchronize or notify persistent child entities. Entity-aware callers should /// use [`Self::apply_mutation`] with [`RandomizedElementTreeMutationKind::Insert`]. pub fn insert_child(&mut self) -> RandomizedElementTreeMutation { self.insert_child_internal().mutation } /// Removes a seeded random element and its subtree. /// /// This does not synchronize or notify persistent child entities. Entity-aware callers should /// use [`Self::apply_mutation`] with [`RandomizedElementTreeMutationKind::Remove`]. pub fn remove_child(&mut self) -> Option { self.remove_child_internal(false) .map(|applied| applied.mutation) } fn apply_mutation_without_notification( &mut self, kind: RandomizedElementTreeMutationKind, ) -> AppliedRandomizedElementTreeMutation { match kind { RandomizedElementTreeMutationKind::RootColor => { self.snapshot.borrow_mut().root.background = random_color(&mut self.rng); AppliedRandomizedElementTreeMutation::root(RandomizedElementTreeMutation::RootColor) } RandomizedElementTreeMutationKind::RootOpacity => { self.snapshot.borrow_mut().root.opacity = random_opacity(&mut self.rng); AppliedRandomizedElementTreeMutation::root( RandomizedElementTreeMutation::RootOpacity, ) } RandomizedElementTreeMutationKind::RootBorder => { self.snapshot .borrow_mut() .root .randomize_border(&mut self.rng); AppliedRandomizedElementTreeMutation::root( RandomizedElementTreeMutation::RootBorder, ) } RandomizedElementTreeMutationKind::RootBounds => { let mut snapshot = self.snapshot.borrow_mut(); snapshot.root.width = self.rng.random_range(520.0..980.0); snapshot.root.height = self.rng.random_range(360.0..760.0); AppliedRandomizedElementTreeMutation::root( RandomizedElementTreeMutation::RootBounds, ) } RandomizedElementTreeMutationKind::RootClipping => { let mut snapshot = self.snapshot.borrow_mut(); snapshot.root.clipped = !snapshot.root.clipped; AppliedRandomizedElementTreeMutation::root( RandomizedElementTreeMutation::RootClipping, ) } RandomizedElementTreeMutationKind::RootVisibility => { let mut snapshot = self.snapshot.borrow_mut(); snapshot.root.visible = !snapshot.root.visible; AppliedRandomizedElementTreeMutation::root( RandomizedElementTreeMutation::RootVisibility, ) } RandomizedElementTreeMutationKind::ChildColor => { self.mutate_random_child(RandomizedChildMutation::Color) } RandomizedElementTreeMutationKind::ChildOpacity => { self.mutate_random_child(RandomizedChildMutation::Opacity) } RandomizedElementTreeMutationKind::ChildBorder => { self.mutate_random_child(RandomizedChildMutation::Border) } RandomizedElementTreeMutationKind::ChildBounds => { self.mutate_random_child(RandomizedChildMutation::Bounds) } RandomizedElementTreeMutationKind::ChildClipping => { self.mutate_random_child(RandomizedChildMutation::Clipping) } RandomizedElementTreeMutationKind::ChildVisibility => { self.mutate_random_child(RandomizedChildMutation::Visibility) } RandomizedElementTreeMutationKind::Reorder => self.reorder_child(), RandomizedElementTreeMutationKind::Insert => self.insert_child_internal(), RandomizedElementTreeMutationKind::Remove => self .remove_child_internal(false) .unwrap_or_else(|| self.insert_child_internal()), RandomizedElementTreeMutationKind::RemoveLeaf => self .remove_child_internal(true) .unwrap_or_else(|| self.insert_child_internal()), } } fn insert_child_internal(&mut self) -> AppliedRandomizedElementTreeMutation { let mut snapshot = self.snapshot.borrow_mut(); let element_id = snapshot.next_element_id; snapshot.next_element_id += 1; let mut parent_path = random_parent_path(&snapshot.children, &mut self.rng); let parent_id = node_at_path(&snapshot.children, &parent_path).map(|node| node.id); let owner_entity_id = nearest_entity_id(&snapshot.children, &parent_path); let node = RandomizedElementNode::random( element_id, self.config.entity_density, self.config.handler_density, &mut self.rng, ); let (index, _) = insert_node(&mut snapshot.children, &mut parent_path, node); snapshot.rebuild_element_paths(); AppliedRandomizedElementTreeMutation { mutation: RandomizedElementTreeMutation::Inserted { element_id, parent_id, index, }, owner_entity_id, synchronize_entities: true, } } fn remove_child_internal( &mut self, leaves_only: bool, ) -> Option { let mut snapshot = self.snapshot.borrow_mut(); let path = if leaves_only { random_leaf_path(&snapshot.children, &mut self.rng)? } else { random_node_path(&snapshot.children, &mut self.rng)? }; let (index, parent_path) = path.split_last()?; let parent_id = node_at_path(&snapshot.children, parent_path).map(|node| node.id); let owner_entity_id = nearest_entity_id(&snapshot.children, parent_path); let siblings = children_at_path_mut(&mut snapshot.children, parent_path)?; if *index >= siblings.len() { return None; } let removed = siblings.remove(*index); let removed_element_count = removed.element_count(); let element_id = removed.id; snapshot.rebuild_element_paths(); Some(AppliedRandomizedElementTreeMutation { mutation: RandomizedElementTreeMutation::Removed { element_id, parent_id, index: *index, removed_element_count, }, owner_entity_id, synchronize_entities: true, }) } /// Recolors `count` distinct seeded random descendants in one pass and notifies each /// affected entity boundary once (the root, for descendants owned by no entity). /// Returns how many descendants changed and how many boundaries were notified. /// /// This is the "share of the scene changing per frame" knob: `count` calls to /// [`Self::apply_mutation`] would each walk the tree and notify separately, costing /// more than the frame being measured once `count` is a fair share of the tree. pub fn recolor_children( &mut self, count: usize, locality: ChangeLocality, cx: &mut Context, ) -> RecoloredChildren { let owners = { let mut snapshot = self.snapshot.borrow_mut(); let mut paths = node_paths(&snapshot.children); let count = count.min(paths.len()); if locality == ChangeLocality::Localized { // Order the paths so one random entity's subtree comes first: the change // stays inside that entity unless there is more of it than the subtree. let entity_paths: Vec<_> = paths .iter() .filter(|path| { node_at_path(&snapshot.children, path).is_some_and(|node| node.is_entity) }) .cloned() .collect(); if let Some(entity_path) = entity_paths .get(self.rng.random_range(0..entity_paths.len().max(1))) .cloned() { let (inside, outside): (Vec<_>, Vec<_>) = paths .into_iter() .partition(|path| path.starts_with(&entity_path)); paths = inside; paths.extend(outside); } } // A partial Fisher–Yates shuffle over the first `count` slots: for `Spread`, a // uniform sample of the tree; for `Localized`, a uniform sample of the chosen // subtree, spilling into the rest only once the subtree is exhausted. let sample_from = match locality { ChangeLocality::Spread => paths.len(), ChangeLocality::Localized => count, }; for index in 0..count { let other = self.rng.random_range(index..sample_from.max(index + 1)); paths.swap(index, other); } let mut owners = HashSet::default(); for path in &paths[..count] { owners.insert(nearest_entity_id(&snapshot.children, path)); if let Some(node) = node_at_path_mut(&mut snapshot.children, path) { node.style.background = random_color(&mut self.rng); } } owners }; let notified = owners.len(); let recolored = count.min(self.snapshot.borrow().descendant_count()); for owner in owners { self.notify_owner(owner, cx); } RecoloredChildren { recolored, notified, } } fn mutate_random_child( &mut self, mutation: RandomizedChildMutation, ) -> AppliedRandomizedElementTreeMutation { let mut snapshot = self.snapshot.borrow_mut(); let Some(path) = random_node_path(&snapshot.children, &mut self.rng) else { drop(snapshot); return self.insert_child_internal(); }; let owner_entity_id = nearest_entity_id(&snapshot.children, &path); let Some(node) = node_at_path_mut(&mut snapshot.children, &path) else { drop(snapshot); return self.insert_child_internal(); }; let element_id = node.id; let mutation = match mutation { RandomizedChildMutation::Color => { node.style.background = random_color(&mut self.rng); RandomizedElementTreeMutation::ChildColor { element_id } } RandomizedChildMutation::Opacity => { node.style.opacity = random_opacity(&mut self.rng); RandomizedElementTreeMutation::ChildOpacity { element_id } } RandomizedChildMutation::Border => { node.style.randomize_border(&mut self.rng); RandomizedElementTreeMutation::ChildBorder { element_id } } RandomizedChildMutation::Bounds => { node.style.width = self.rng.random_range(36.0..220.0); node.style.height = self.rng.random_range(28.0..180.0); RandomizedElementTreeMutation::ChildBounds { element_id } } RandomizedChildMutation::Clipping => { node.style.clipped = !node.style.clipped; RandomizedElementTreeMutation::ChildClipping { element_id } } RandomizedChildMutation::Visibility => { node.style.visible = !node.style.visible; RandomizedElementTreeMutation::ChildVisibility { element_id } } }; AppliedRandomizedElementTreeMutation { mutation, owner_entity_id, synchronize_entities: false, } } fn reorder_child(&mut self) -> AppliedRandomizedElementTreeMutation { let mut snapshot = self.snapshot.borrow_mut(); let parent_paths = parent_paths_with_multiple_children(&snapshot.children); if parent_paths.is_empty() { drop(snapshot); return self.insert_child_internal(); } let parent_path_index = self.rng.random_range(0..parent_paths.len()); let Some(parent_path) = parent_paths.get(parent_path_index) else { drop(snapshot); return self.insert_child_internal(); }; let parent_id = node_at_path(&snapshot.children, parent_path).map(|node| node.id); let owner_entity_id = nearest_entity_id(&snapshot.children, parent_path); let Some(child_count) = children_at_path(&snapshot.children, parent_path).map(<[RandomizedElementNode]>::len) else { drop(snapshot); return self.insert_child_internal(); }; if child_count < 2 { drop(snapshot); return self.insert_child_internal(); } let old_index = self.rng.random_range(0..child_count); let mut new_index = self.rng.random_range(0..child_count - 1); if new_index >= old_index { new_index += 1; } let Some(siblings) = children_at_path_mut(&mut snapshot.children, parent_path) else { drop(snapshot); return self.insert_child_internal(); }; let child = siblings.remove(old_index); let element_id = child.id; siblings.insert(new_index, child); snapshot.rebuild_element_paths(); AppliedRandomizedElementTreeMutation { mutation: RandomizedElementTreeMutation::Reordered { element_id, parent_id, old_index, new_index, }, owner_entity_id, synchronize_entities: false, } } fn synchronize_entities(&mut self, cx: &mut Context) { let entity_ids = { let snapshot = self.snapshot.borrow(); snapshot .element_ids() .into_iter() .filter(|element_id| { snapshot .node(*element_id) .is_some_and(|node| node.is_entity) }) .collect::>() }; let entity_id_set = entity_ids.iter().copied().collect::>(); self.entities .borrow_mut() .retain(|element_id, _| entity_id_set.contains(element_id)); let missing_entity_ids = { let entities = self.entities.borrow(); entity_ids .into_iter() .filter(|element_id| !entities.contains_key(element_id)) .collect::>() }; let weak_entities = Rc::downgrade(&self.entities); for element_id in missing_entity_ids { let entity = cx.new(|_| RandomizedElementEntity { element_id, snapshot: self.snapshot.clone(), entities: weak_entities.clone(), work_counters: self.work_counters.clone(), }); self.entities.borrow_mut().insert(element_id, entity); } let entity_count = self.entities.borrow().len(); let mut work_counters = self.work_counters.borrow_mut(); let additional_capacity = entity_count.saturating_sub(work_counters.rendered_entity_ids.len()); work_counters .rendered_entity_ids .reserve(additional_capacity); } fn notify_owner(&self, owner_entity_id: Option, cx: &mut Context) { let owner = owner_entity_id.and_then(|element_id| self.entities.borrow().get(&element_id).cloned()); if let Some(owner) = owner { owner.update(cx, |_, cx| cx.notify()); } else { cx.notify(); } } } impl Render for RandomizedElementTree { fn render(&mut self, _window: &mut Window, _cx: &mut Context) -> impl IntoElement { let snapshot = self.snapshot.borrow(); let root = &snapshot.root; let entities = self.entities.borrow(); let mut work_counters = self.work_counters.borrow_mut(); work_counters.root_render_count += 1; div() .flex() .when(root.vertical, |element| element.flex_col()) .when(root.wrap, |element| element.flex_wrap()) .gap(px(root.gap)) .p(px(root.padding)) .w(px(root.width)) .h(px(root.height)) .bg(root.background) .border(px(root.border_width)) .border_color(root.border_color) .rounded(px(root.corner_radius)) .opacity(root.opacity) .when(root.clipped, |element| element.overflow_hidden()) .when(!root.visible, |element| element.invisible()) .children( snapshot .children .iter() .map(|node| render_node(node, &entities, &mut work_counters)), ) } } type RandomizedElementEntities = HashMap>; struct RandomizedElementEntity { element_id: u64, snapshot: Rc>, entities: Weak>, work_counters: Rc>, } impl Render for RandomizedElementEntity { fn render(&mut self, _window: &mut Window, _cx: &mut Context) -> impl IntoElement { let snapshot = self.snapshot.borrow(); let entities = self.entities.upgrade(); let entities = entities.as_ref().map(|entities| entities.borrow()); let mut work_counters = self.work_counters.borrow_mut(); work_counters.entity_render_count += 1; work_counters.rendered_entity_ids.push(self.element_id); snapshot .node(self.element_id) .map(|node| render_node_body(node, entities.as_deref(), &mut work_counters)) .unwrap_or_else(|| div().into_any_element()) } } struct AppliedRandomizedElementTreeMutation { mutation: RandomizedElementTreeMutation, owner_entity_id: Option, synchronize_entities: bool, } impl AppliedRandomizedElementTreeMutation { fn root(mutation: RandomizedElementTreeMutation) -> Self { Self { mutation, owner_entity_id: None, synchronize_entities: false, } } } #[derive(Clone, Debug, PartialEq)] struct RandomizedElementNode { id: u64, style: RandomizedElementStyle, is_entity: bool, features: RandomizedElementFeatures, children: Vec, } impl RandomizedElementNode { fn random(id: u64, entity_density: f64, handler_density: f64, rng: &mut StdRng) -> Self { Self { id, style: RandomizedElementStyle::random_child(rng), is_entity: rng.random_bool(entity_density), features: RandomizedElementFeatures::random(handler_density, rng), children: Vec::new(), } } fn element_count(&self) -> usize { let mut count = 0; let mut stack = vec![self]; while let Some(node) = stack.pop() { count += 1; stack.extend(node.children.iter()); } count } } #[derive(Clone, Debug, PartialEq)] struct RandomizedElementFeatures { action_handler: bool, click_handler: bool, key_context: bool, } impl RandomizedElementFeatures { fn random(handler_density: f64, rng: &mut StdRng) -> Self { Self { action_handler: rng.random_bool(handler_density), click_handler: rng.random_bool(handler_density), key_context: rng.random_bool(handler_density), } } fn handler_count(&self) -> usize { usize::from(self.action_handler) + usize::from(self.click_handler) } } #[derive(Clone, Debug, PartialEq)] struct RandomizedElementStyle { background: Hsla, border_color: Hsla, opacity: f32, border_width: f32, corner_radius: f32, width: f32, height: f32, padding: f32, gap: f32, clipped: bool, visible: bool, vertical: bool, wrap: bool, } impl RandomizedElementStyle { fn random_root(rng: &mut StdRng) -> Self { Self { background: random_color(rng), border_color: random_color(rng), opacity: random_opacity(rng), border_width: rng.random_range(0.0..4.0), corner_radius: rng.random_range(0.0..18.0), width: rng.random_range(520.0..980.0), height: rng.random_range(360.0..760.0), padding: rng.random_range(4.0..18.0), gap: rng.random_range(2.0..14.0), clipped: rng.random_bool(0.5), visible: true, vertical: rng.random_bool(0.5), wrap: rng.random_bool(0.7), } } fn random_child(rng: &mut StdRng) -> Self { Self { background: random_color(rng), border_color: random_color(rng), opacity: random_opacity(rng), border_width: rng.random_range(0.0..4.0), corner_radius: rng.random_range(0.0..14.0), width: rng.random_range(36.0..220.0), height: rng.random_range(28.0..180.0), padding: rng.random_range(1.0..10.0), gap: rng.random_range(1.0..8.0), clipped: rng.random_bool(0.3), visible: rng.random_bool(0.9), vertical: rng.random_bool(0.5), wrap: rng.random_bool(0.4), } } fn randomize_border(&mut self, rng: &mut StdRng) { self.border_color = random_color(rng); self.border_width = rng.random_range(0.0..5.0); self.corner_radius = rng.random_range(0.0..18.0); } } #[derive(Clone, Copy)] enum RandomizedChildMutation { Color, Opacity, Border, Bounds, Clipping, Visibility, } #[cfg_attr(feature = "stacker", stacksafe::stacksafe)] fn render_node( node: &RandomizedElementNode, entities: &RandomizedElementEntities, work_counters: &mut RandomizedElementTreeWorkCounters, ) -> AnyElement { if node.is_entity && let Some(entity) = entities.get(&node.id) { return entity.clone().into_any_element(); } render_node_body(node, Some(entities), work_counters) } #[cfg_attr(feature = "stacker", stacksafe::stacksafe)] fn render_node_body( node: &RandomizedElementNode, entities: Option<&RandomizedElementEntities>, work_counters: &mut RandomizedElementTreeWorkCounters, ) -> AnyElement { work_counters.element_render_count += 1; work_counters.handler_registration_count += node.features.handler_count(); let style = &node.style; div() .id(ElementId::NamedInteger( "randomized-element".into(), node.id, )) .flex() .when(style.vertical, |element| element.flex_col()) .when(style.wrap, |element| element.flex_wrap()) .gap(px(style.gap)) .p(px(style.padding)) .w(px(style.width)) .h(px(style.height)) .bg(style.background) .border(px(style.border_width)) .border_color(style.border_color) .rounded(px(style.corner_radius)) .opacity(style.opacity) .when(style.clipped, |element| element.overflow_hidden()) .when(!style.visible, |element| element.invisible()) .when(node.features.action_handler, |element| { element.on_action(|_: &RandomizedElementTreeAction, _window, _cx| {}) }) .when(node.features.click_handler, |element| { element.on_click(|_event, _window, _cx| {}) }) .when(node.features.key_context, |element| { element.key_context("RandomizedElementTree") }) .children(node.children.iter().map(|child| { if let Some(entities) = entities { render_node(child, entities, work_counters) } else { render_node_body(child, None, work_counters) } })) .into_any_element() } fn insert_node( root_children: &mut Vec, parent_path: &mut Vec, node: RandomizedElementNode, ) -> (usize, Vec) { let Some(children) = children_at_path_mut(root_children, parent_path) else { parent_path.clear(); let index = root_children.len(); root_children.push(node); return (index, vec![index]); }; let index = children.len(); children.push(node); let mut inserted_path = parent_path.clone(); inserted_path.push(index); (index, inserted_path) } fn random_parent_path(root_children: &[RandomizedElementNode], rng: &mut StdRng) -> Vec { let paths = node_paths(root_children); let index = rng.random_range(0..=paths.len()); paths.get(index).cloned().unwrap_or_default() } fn random_node_path( root_children: &[RandomizedElementNode], rng: &mut StdRng, ) -> Option> { let paths = node_paths(root_children); if paths.is_empty() { None } else { paths.get(rng.random_range(0..paths.len())).cloned() } } fn random_leaf_path( root_children: &[RandomizedElementNode], rng: &mut StdRng, ) -> Option> { let leaves = node_paths(root_children) .into_iter() .filter(|path| { node_at_path(root_children, path).is_some_and(|node| node.children.is_empty()) }) .collect::>(); if leaves.is_empty() { None } else { leaves.get(rng.random_range(0..leaves.len())).cloned() } } fn parent_paths_with_multiple_children(root_children: &[RandomizedElementNode]) -> Vec> { let mut parent_paths = Vec::new(); if root_children.len() >= 2 { parent_paths.push(Vec::new()); } parent_paths.extend(node_paths(root_children).into_iter().filter(|path| { node_at_path(root_children, path).is_some_and(|node| node.children.len() >= 2) })); parent_paths } fn node_paths(root_children: &[RandomizedElementNode]) -> Vec> { let mut paths = Vec::new(); let mut stack = root_children .iter() .enumerate() .rev() .map(|(index, node)| (vec![index], node)) .collect::>(); while let Some((path, node)) = stack.pop() { for (index, child) in node.children.iter().enumerate().rev() { let mut child_path = path.clone(); child_path.push(index); stack.push((child_path, child)); } paths.push(path); } paths } fn node_at_path<'a>( root_children: &'a [RandomizedElementNode], path: &[usize], ) -> Option<&'a RandomizedElementNode> { let (index, parent_path) = path.split_last()?; children_at_path(root_children, parent_path)?.get(*index) } fn node_at_path_mut<'a>( root_children: &'a mut Vec, path: &[usize], ) -> Option<&'a mut RandomizedElementNode> { let (index, parent_path) = path.split_last()?; children_at_path_mut(root_children, parent_path)?.get_mut(*index) } fn children_at_path<'a>( mut children: &'a [RandomizedElementNode], path: &[usize], ) -> Option<&'a [RandomizedElementNode]> { for index in path { children = &children.get(*index)?.children; } Some(children) } fn children_at_path_mut<'a>( mut children: &'a mut Vec, path: &[usize], ) -> Option<&'a mut Vec> { for index in path { children = &mut children.get_mut(*index)?.children; } Some(children) } fn nearest_entity_id(root_children: &[RandomizedElementNode], path: &[usize]) -> Option { let mut children = root_children; let mut nearest_entity_id = None; for index in path { let node = children.get(*index)?; if node.is_entity { nearest_entity_id = Some(node.id); } children = &node.children; } nearest_entity_id } fn normalized_density(density: f64) -> f64 { if density.is_nan() { 0.0 } else { density.clamp(0.0, 1.0) } } fn random_color(rng: &mut StdRng) -> Hsla { Hsla { h: rng.random_range(0.0..1.0), s: rng.random_range(0.25..0.9), l: rng.random_range(0.18..0.82), a: 1.0, } } fn random_opacity(rng: &mut StdRng) -> f32 { rng.random_range(0.25..1.0) } #[cfg(test)] mod tests { use super::*; use crate::{ AnyWindowHandle, Background, BorderStyle, Bounds, ContentMask, Corners, Edges, Pixels, ScaledPixels, Size, TestAppContext, size, }; use proptest::prelude::*; use std::collections::BTreeSet; const ELEMENT_COUNT: usize = 64; const MUTATION_COUNT: usize = 32; const WINDOW_SIZE: Size = size(px(1200.0), px(900.0)); #[derive(Debug, PartialEq)] struct SceneSnapshot { quads: Vec, element_ids: BTreeSet, } impl SceneSnapshot { fn from_window(window: &Window) -> Self { Self { quads: window .rendered_frame .scene .quads .iter() .map(QuadSnapshot::from) .collect(), element_ids: window .rendered_frame .element_states .keys() .filter_map(|(global_id, _)| { let ElementId::NamedInteger(name, element_id) = global_id.0.last()? else { return None; }; (name.as_ref() == "randomized-element").then_some(*element_id) }) .collect(), } } } #[derive(Debug, PartialEq)] struct QuadSnapshot { order: u32, border_style: BorderStyle, bounds: Bounds, content_mask: ContentMask, background: Background, border_color: Hsla, corner_radii: Corners, border_widths: Edges, } impl From<&crate::Quad> for QuadSnapshot { fn from(quad: &crate::Quad) -> Self { Self { order: quad.order, border_style: quad.border_style, bounds: quad.bounds, content_mask: quad.content_mask, background: quad.background, border_color: quad.border_color, corner_radii: quad.corner_radii, border_widths: quad.border_widths, } } } #[gpui::test] fn localized_recoloring_notifies_one_entity_until_its_subtree_is_exhausted( cx: &mut TestAppContext, ) { // Wide: every element is a root child, so every entity's subtree is exactly one // element and localized change of k elements must touch k boundaries only when it // spills; a mixed tree has larger subtrees to stay inside. let config = RandomizedElementTreeConfig::new(11, 200) .with_topology(RandomizedElementTreeTopology::Mixed) .with_entity_density(0.1); let window = cx.open_window(WINDOW_SIZE, move |_, cx| { RandomizedElementTree::new_with_config(config, cx) }); let root = window.root(cx).expect("window has a root"); let (localized, spread) = root.update(cx, |tree, cx| { let localized = tree.recolor_children(20, ChangeLocality::Localized, cx); let spread = tree.recolor_children(20, ChangeLocality::Spread, cx); (localized, spread) }); assert_eq!(localized.recolored, 20); assert_eq!(spread.recolored, 20); assert!( localized.notified <= spread.notified, "localized change touches no more boundaries than spread change: {localized:?} vs {spread:?}" ); assert!(localized.notified >= 1); } #[test] fn bounds_sample_within_range_and_reproduce_from_the_seed() { let bounds = RandomizedElementTreeBounds::new(32..=256) .with_topologies([ RandomizedElementTreeTopology::Deep, RandomizedElementTreeTopology::Narrow, ]) .with_entity_density(0.1..=0.5) .with_handler_density(0.25..=0.25); let mut topologies = BTreeSet::new(); let sample = |seed| bounds.sample(&mut StdRng::seed_from_u64(seed)); for seed in 0..64 { let config = sample(seed); assert!((32..=256).contains(&config.element_count()), "{config:?}"); assert!(config.topology().is_tall(), "{config:?}"); assert!((0.1..=0.5).contains(&config.entity_density()), "{config:?}"); assert_eq!(config.handler_density(), 0.25); assert_eq!(sample(seed), config, "a seed draws the same config twice"); topologies.insert(format!("{:?}", config.topology())); } assert_eq!(topologies.len(), 2, "64 seeds pick both allowed topologies"); assert_ne!(sample(0), sample(1)); } #[test] fn generated_topologies_include_wide_deep_narrow_and_empty_trees() { let wide = RandomizedElementTree::new(0, ELEMENT_COUNT).snapshot(); assert_eq!(wide.children.len(), ELEMENT_COUNT); assert_eq!(wide.max_depth(), 1); let deep = RandomizedElementTree::new(1, ELEMENT_COUNT).snapshot(); assert_eq!(deep.children.len(), 1); assert_eq!(deep.max_depth(), ELEMENT_COUNT); let narrow = RandomizedElementTree::new(3, ELEMENT_COUNT).snapshot(); assert_eq!(narrow.descendant_count(), ELEMENT_COUNT); assert!(narrow.max_depth() > 1); let empty = RandomizedElementTree::new(2, 0).snapshot(); assert_eq!(empty.descendant_count(), 0); assert_eq!(empty.max_depth(), 0); } #[test] fn randomized_element_ids_are_unique_and_stable() { let mut tree = RandomizedElementTree::new(0, ELEMENT_COUNT); let initial_ids = tree.snapshot().element_ids(); assert_eq!(initial_ids.len(), ELEMENT_COUNT); assert_eq!( initial_ids.iter().copied().collect::>().len(), ELEMENT_COUNT ); for mutation in [ RandomizedChildMutation::Color, RandomizedChildMutation::Opacity, RandomizedChildMutation::Border, RandomizedChildMutation::Bounds, RandomizedChildMutation::Clipping, RandomizedChildMutation::Visibility, ] { tree.mutate_random_child(mutation); assert_eq!(tree.snapshot().element_ids(), initial_ids); } tree.reorder_child(); let reordered_ids = tree.snapshot().element_ids(); assert_eq!( reordered_ids.iter().copied().collect::>(), initial_ids.iter().copied().collect() ); tree.insert_child(); let inserted_ids = tree.snapshot().element_ids(); let inserted_id_set = inserted_ids.iter().copied().collect::>(); assert_eq!(inserted_ids.len(), ELEMENT_COUNT + 1); assert!(initial_ids.iter().all(|id| inserted_id_set.contains(id))); tree.remove_child(); let remaining_ids = tree.snapshot().element_ids(); assert_eq!( remaining_ids.iter().copied().collect::>().len(), remaining_ids.len() ); assert!(remaining_ids.iter().all(|id| inserted_id_set.contains(id))); } #[gpui::test] fn randomized_entities_and_work_counters_support_incremental_benchmarks( cx: &mut TestAppContext, ) { let config = RandomizedElementTreeConfig::new(0, 16) .with_topology(RandomizedElementTreeTopology::Wide) .with_entity_density(1.0) .with_handler_density(1.0); let window = cx.open_window(WINDOW_SIZE, move |_, cx| { RandomizedElementTree::new_with_config(config, cx) }); let root = window .root(cx) .expect("randomized tree window should have a root"); let window = AnyWindowHandle::from(window); draw_window(window, cx); let initial_counters = root.read_with(cx, |tree, _| tree.work_counters()); assert!(initial_counters.root_render_count() >= 1); assert!(initial_counters.entity_render_count() >= 16); assert!(initial_counters.element_render_count() >= 16); assert!(initial_counters.handler_registration_count() >= 32); let mutation = root.update(cx, |tree, cx| { tree.reset_work_counters(); tree.apply_mutation(RandomizedElementTreeMutationKind::ChildColor, cx) }); let RandomizedElementTreeMutation::ChildColor { element_id } = mutation else { panic!("a nonempty tree should apply the requested child-color mutation"); }; draw_window(window, cx); let incremental_counters = root.read_with(cx, |tree, _| tree.work_counters()); assert!(incremental_counters.root_render_count() >= 1); assert!(incremental_counters.entity_render_count() >= 1); assert!(incremental_counters.element_render_count() >= 1); assert!(incremental_counters.handler_registration_count() >= 2); assert!( incremental_counters .rendered_entity_ids() .contains(&element_id) ); cx.update_window(window, |_, window, _| window.remove_window()) .expect("randomized tree window should remain open until the case completes"); } #[gpui::property_test(config = ProptestConfig { cases: 4, ..Default::default() })] fn randomized_element_tree_matches_full_render(cx: &mut TestAppContext, seed: u64) { for topology in 0..4 { let topology_seed = (seed & !3) | topology; let config = RandomizedElementTreeConfig::new(topology_seed, ELEMENT_COUNT) .with_entity_density(0.2) .with_handler_density(0.2); run_render_equivalence_case(cx, config); } run_render_equivalence_case( cx, RandomizedElementTreeConfig::new(seed, 0) .with_entity_density(0.2) .with_handler_density(0.2), ); } fn run_render_equivalence_case(cx: &mut TestAppContext, config: RandomizedElementTreeConfig) { let persistent_window = cx.open_window(WINDOW_SIZE, move |_, cx| { RandomizedElementTree::new_with_config(config, cx) }); let persistent_root = persistent_window .root(cx) .expect("randomized tree window should have a root"); let persistent_window = AnyWindowHandle::from(persistent_window); assert_matches_reference(&persistent_root, persistent_window, cx, config, None); for mutation_index in 0..MUTATION_COUNT { let mutation = persistent_root.update(cx, |tree, cx| match mutation_index { 0 => tree.apply_mutation(RandomizedElementTreeMutationKind::Insert, cx), 1 => tree.apply_mutation(RandomizedElementTreeMutationKind::Remove, cx), _ => tree.apply_random_mutation(cx), }); assert_matches_reference( &persistent_root, persistent_window, cx, config, Some((mutation_index, mutation)), ); } cx.update_window(persistent_window, |_, window, _| window.remove_window()) .expect("persistent window should remain open until the case completes"); } fn assert_matches_reference( persistent_root: &crate::Entity, persistent_window: AnyWindowHandle, cx: &mut TestAppContext, config: RandomizedElementTreeConfig, mutation: Option<(usize, RandomizedElementTreeMutation)>, ) { let snapshot = persistent_root.read_with(cx, |tree, _| tree.snapshot()); let expected_element_ids = snapshot.element_ids().into_iter().collect::>(); let persistent_scene = draw_window(persistent_window, cx); assert_eq!( persistent_scene.element_ids, expected_element_ids, "persistent frame element IDs diverged from the model for config {config:?}, after \ mutation {mutation:?}" ); let reference_scene = draw_fresh_reference(snapshot, cx); assert_eq!( persistent_scene, reference_scene, "rendered scene diverged for config {config:?}, after mutation {mutation:?}" ); } fn draw_fresh_reference( snapshot: RandomizedElementTreeSnapshot, cx: &mut TestAppContext, ) -> SceneSnapshot { let reference_window = cx.open_window(WINDOW_SIZE, move |_, cx| { RandomizedElementTree::from_snapshot_with_entities(snapshot, cx) }); let reference_window = AnyWindowHandle::from(reference_window); let scene = draw_window(reference_window, cx); cx.update_window(reference_window, |_, window, _| window.remove_window()) .expect("reference window should remain open until its frame is captured"); scene } fn draw_window(window: AnyWindowHandle, cx: &mut TestAppContext) -> SceneSnapshot { cx.update_window(window, |_, window, cx| { let arena_clear_needed = window.draw(cx); let scene = SceneSnapshot::from_window(window); arena_clear_needed.clear(cx); scene }) .expect("randomized element tree window should remain open while drawing") } }