Simplified UI tree navigation without ghost_nodes (#17143)

# Objective

There is a large performance regression in the UI systems in 0.15
because the `UiChildren` and `UiRootRootNodes` system params (even with
`ghost_nodes` disabled) are really inefficient compared to regular
queries and can trigger a heap allocation with large numbers of
children.

## Solution

Replace the `UiChildren` and `UiRootRootNodes` system params with
simplified versions when the `ghost_nodes` feature is disabled.

## Testing

yellow this PR, red main

cargo run --example many_buttons --features "trace_tracy" --release

`ui_stack_system`
<img width="494" alt="stack"
src="https://github.com/user-attachments/assets/4a09485f-0ded-4e54-bd47-ffbce869051a"
/>

`ui_layout_system`
<img width="467" alt="unghosted"
src="https://github.com/user-attachments/assets/9d906b20-66b6-4257-9eef-578de1827628"
/>

`update_clipping_system`
<img width="454" alt="clipping"
src="https://github.com/user-attachments/assets/320b50e8-1a1d-423a-95a0-42799ae72fc5"
/>
This commit is contained in:
ickshonpe 2025-01-06 19:22:00 +00:00 committed by GitHub
parent 94b9fe384f
commit 17e3b850bd
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

View File

@ -1,14 +1,17 @@
//! This module contains [`GhostNode`] and utilities to flatten the UI hierarchy, traversing past ghost nodes.
use crate::Node;
use bevy_ecs::{prelude::*, system::SystemParam};
use bevy_hierarchy::{Children, HierarchyQueryExt, Parent};
use bevy_hierarchy::{Children, Parent};
use bevy_reflect::prelude::*;
use bevy_render::view::Visibility;
use bevy_transform::prelude::Transform;
use core::marker::PhantomData;
use smallvec::SmallVec;
use crate::Node;
#[cfg(feature = "ghost_nodes")]
use bevy_hierarchy::HierarchyQueryExt;
#[cfg(feature = "ghost_nodes")]
use smallvec::SmallVec;
/// Marker component for entities that should be ignored within UI hierarchies.
///
@ -40,6 +43,7 @@ impl GhostNode {
}
}
#[cfg(feature = "ghost_nodes")]
/// System param that allows iteration of all UI root nodes.
///
/// A UI root node is either a [`Node`] without a [`Parent`], or with only [`GhostNode`] ancestors.
@ -51,6 +55,10 @@ pub struct UiRootNodes<'w, 's> {
ui_children: UiChildren<'w, 's>,
}
#[cfg(not(feature = "ghost_nodes"))]
pub type UiRootNodes<'w, 's> = Query<'w, 's, Entity, (With<Node>, Without<Parent>)>;
#[cfg(feature = "ghost_nodes")]
impl<'w, 's> UiRootNodes<'w, 's> {
pub fn iter(&'s self) -> impl Iterator<Item = Entity> + 's {
self.root_node_query
@ -62,6 +70,7 @@ impl<'w, 's> UiRootNodes<'w, 's> {
}
}
#[cfg(feature = "ghost_nodes")]
/// System param that gives access to UI children utilities, skipping over [`GhostNode`].
#[derive(SystemParam)]
pub struct UiChildren<'w, 's> {
@ -77,6 +86,16 @@ pub struct UiChildren<'w, 's> {
parents_query: Query<'w, 's, &'static Parent>,
}
#[cfg(not(feature = "ghost_nodes"))]
/// System param that gives access to UI children utilities.
#[derive(SystemParam)]
pub struct UiChildren<'w, 's> {
ui_children_query: Query<'w, 's, Option<&'static Children>, With<Node>>,
changed_children_query: Query<'w, 's, Entity, Changed<Children>>,
parents_query: Query<'w, 's, &'static Parent>,
}
#[cfg(feature = "ghost_nodes")]
impl<'w, 's> UiChildren<'w, 's> {
/// Iterates the children of `entity`, skipping over [`GhostNode`].
///
@ -134,6 +153,40 @@ impl<'w, 's> UiChildren<'w, 's> {
}
}
#[cfg(not(feature = "ghost_nodes"))]
impl<'w, 's> UiChildren<'w, 's> {
/// Iterates the children of `entity`.
pub fn iter_ui_children(&'s self, entity: Entity) -> impl Iterator<Item = Entity> + 's {
self.ui_children_query
.get(entity)
.ok()
.flatten()
.map(|children| children.as_ref())
.unwrap_or(&[])
.iter()
.copied()
}
/// Returns the UI parent of the provided entity.
pub fn get_parent(&'s self, entity: Entity) -> Option<Entity> {
self.parents_query
.get(entity)
.ok()
.map(|parent| parent.entity())
}
/// Given an entity in the UI hierarchy, check if its set of children has changed, e.g if children has been added/removed or if the order has changed.
pub fn is_changed(&'s self, entity: Entity) -> bool {
self.changed_children_query.contains(entity)
}
/// Returns `true` if the given entity is either a [`Node`] or a [`GhostNode`].
pub fn is_ui_node(&'s self, entity: Entity) -> bool {
self.ui_children_query.contains(entity)
}
}
#[cfg(feature = "ghost_nodes")]
pub struct UiChildrenIter<'w, 's> {
stack: SmallVec<[Entity; 8]>,
query: &'s Query<
@ -144,6 +197,7 @@ pub struct UiChildrenIter<'w, 's> {
>,
}
#[cfg(feature = "ghost_nodes")]
impl<'w, 's> Iterator for UiChildrenIter<'w, 's> {
type Item = Entity;
fn next(&mut self) -> Option<Self::Item> {