Files
ElyOffice/vendor/gpui-pre/src/randomized_element_tree.rs
T
2026-10-11 00:25:10 -04:00

1746 lines
63 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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<RandomizedElementTreeTopology>,
element_count: RangeInclusive<usize>,
entity_density: RangeInclusive<f64>,
handler_density: RangeInclusive<f64>,
}
impl RandomizedElementTreeBounds {
/// Trees of any topology with `element_count` elements, no entities and no handlers.
pub fn new(element_count: RangeInclusive<usize>) -> 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<Item = RandomizedElementTreeTopology>,
) -> 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<f64>) -> 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<f64>) -> 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<f64>, 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<u64>,
}
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<RandomizedElementNode>,
next_element_id: u64,
paths_by_element_id: HashMap<u64, Vec<usize>>,
}
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::<Vec<_>>();
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::<Vec<_>>();
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<u64> {
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::<Vec<_>>();
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::<Vec<_>>();
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<u64>,
/// 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<u64>,
/// 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<u64>,
/// 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<RefCell<RandomizedElementTreeSnapshot>>,
entities: Rc<RefCell<RandomizedElementEntities>>,
work_counters: Rc<RefCell<RandomizedElementTreeWorkCounters>>,
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>) -> 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>,
) -> 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<Self>,
) -> 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<Self>,
) -> 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<RandomizedElementTreeMutation> {
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<AppliedRandomizedElementTreeMutation> {
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<Self>,
) -> 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<Self>) {
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::<Vec<_>>()
};
let entity_id_set = entity_ids.iter().copied().collect::<HashSet<_>>();
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::<Vec<_>>()
};
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<u64>, cx: &mut Context<Self>) {
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<Self>) -> 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<u64, Entity<RandomizedElementEntity>>;
struct RandomizedElementEntity {
element_id: u64,
snapshot: Rc<RefCell<RandomizedElementTreeSnapshot>>,
entities: Weak<RefCell<RandomizedElementEntities>>,
work_counters: Rc<RefCell<RandomizedElementTreeWorkCounters>>,
}
impl Render for RandomizedElementEntity {
fn render(&mut self, _window: &mut Window, _cx: &mut Context<Self>) -> 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<u64>,
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<RandomizedElementNode>,
}
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<RandomizedElementNode>,
parent_path: &mut Vec<usize>,
node: RandomizedElementNode,
) -> (usize, Vec<usize>) {
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<usize> {
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<Vec<usize>> {
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<Vec<usize>> {
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::<Vec<_>>();
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<Vec<usize>> {
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<Vec<usize>> {
let mut paths = Vec::new();
let mut stack = root_children
.iter()
.enumerate()
.rev()
.map(|(index, node)| (vec![index], node))
.collect::<Vec<_>>();
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<RandomizedElementNode>,
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<RandomizedElementNode>,
path: &[usize],
) -> Option<&'a mut Vec<RandomizedElementNode>> {
for index in path {
children = &mut children.get_mut(*index)?.children;
}
Some(children)
}
fn nearest_entity_id(root_children: &[RandomizedElementNode], path: &[usize]) -> Option<u64> {
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<Pixels> = size(px(1200.0), px(900.0));
#[derive(Debug, PartialEq)]
struct SceneSnapshot {
quads: Vec<QuadSnapshot>,
element_ids: BTreeSet<u64>,
}
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<ScaledPixels>,
content_mask: ContentMask<ScaledPixels>,
background: Background,
border_color: Hsla,
corner_radii: Corners<ScaledPixels>,
border_widths: Edges<ScaledPixels>,
}
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::<BTreeSet<_>>().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::<BTreeSet<_>>(),
initial_ids.iter().copied().collect()
);
tree.insert_child();
let inserted_ids = tree.snapshot().element_ids();
let inserted_id_set = inserted_ids.iter().copied().collect::<BTreeSet<_>>();
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::<BTreeSet<_>>().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<RandomizedElementTree>,
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::<BTreeSet<_>>();
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")
}
}