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:
parent
94b9fe384f
commit
17e3b850bd
@ -1,14 +1,17 @@
|
|||||||
//! This module contains [`GhostNode`] and utilities to flatten the UI hierarchy, traversing past ghost nodes.
|
//! 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_ecs::{prelude::*, system::SystemParam};
|
||||||
use bevy_hierarchy::{Children, HierarchyQueryExt, Parent};
|
use bevy_hierarchy::{Children, Parent};
|
||||||
use bevy_reflect::prelude::*;
|
use bevy_reflect::prelude::*;
|
||||||
use bevy_render::view::Visibility;
|
use bevy_render::view::Visibility;
|
||||||
use bevy_transform::prelude::Transform;
|
use bevy_transform::prelude::Transform;
|
||||||
use core::marker::PhantomData;
|
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.
|
/// 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.
|
/// 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.
|
/// 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>,
|
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> {
|
impl<'w, 's> UiRootNodes<'w, 's> {
|
||||||
pub fn iter(&'s self) -> impl Iterator<Item = Entity> + 's {
|
pub fn iter(&'s self) -> impl Iterator<Item = Entity> + 's {
|
||||||
self.root_node_query
|
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`].
|
/// System param that gives access to UI children utilities, skipping over [`GhostNode`].
|
||||||
#[derive(SystemParam)]
|
#[derive(SystemParam)]
|
||||||
pub struct UiChildren<'w, 's> {
|
pub struct UiChildren<'w, 's> {
|
||||||
@ -77,6 +86,16 @@ pub struct UiChildren<'w, 's> {
|
|||||||
parents_query: Query<'w, 's, &'static Parent>,
|
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> {
|
impl<'w, 's> UiChildren<'w, 's> {
|
||||||
/// Iterates the children of `entity`, skipping over [`GhostNode`].
|
/// 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> {
|
pub struct UiChildrenIter<'w, 's> {
|
||||||
stack: SmallVec<[Entity; 8]>,
|
stack: SmallVec<[Entity; 8]>,
|
||||||
query: &'s Query<
|
query: &'s Query<
|
||||||
@ -144,6 +197,7 @@ pub struct UiChildrenIter<'w, 's> {
|
|||||||
>,
|
>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "ghost_nodes")]
|
||||||
impl<'w, 's> Iterator for UiChildrenIter<'w, 's> {
|
impl<'w, 's> Iterator for UiChildrenIter<'w, 's> {
|
||||||
type Item = Entity;
|
type Item = Entity;
|
||||||
fn next(&mut self) -> Option<Self::Item> {
|
fn next(&mut self) -> Option<Self::Item> {
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user