Skip to content

Commit 5989a84

Browse files
villorUkoeHB
andauthored
Filter UI traversal to only Node and GhostNode (#15746)
# Objective With the warning removed in #15736, the rules for the UI tree changes. We no longer need to traverse non `Node`/`GhostNode` entities. ## Solution - Added a filter `Or<(With<Node>, With<GhostNode>)>` to the child traversal query so we don't unnecessarily traverse nodes that are not part of the UI tree (like text nodes). - Also moved the warning for NoUI->UI entities so it is actually triggered (see comments) ## Testing - Ran unit tests (still passing) - Ran the ghost_nodes and ui examples, still works and looks fine 👍 - Tested the warning by spawning a Node under an empty entity. --- --------- Co-authored-by: UkoeHB <[email protected]>
1 parent 0e30b68 commit 5989a84

File tree

3 files changed

+44
-18
lines changed

3 files changed

+44
-18
lines changed

crates/bevy_ui/src/ghost_hierarchy.rs

Lines changed: 27 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,12 @@ impl<'w, 's> UiRootNodes<'w, 's> {
4444
/// System param that gives access to UI children utilities, skipping over [`GhostNode`].
4545
#[derive(SystemParam)]
4646
pub struct UiChildren<'w, 's> {
47-
ui_children_query: Query<'w, 's, (Option<&'static Children>, Option<&'static GhostNode>)>,
47+
ui_children_query: Query<
48+
'w,
49+
's,
50+
(Option<&'static Children>, Has<GhostNode>),
51+
Or<(With<Node>, With<GhostNode>)>,
52+
>,
4853
changed_children_query: Query<'w, 's, Entity, Changed<Children>>,
4954
children_query: Query<'w, 's, &'static Children>,
5055
ghost_nodes_query: Query<'w, 's, Entity, With<GhostNode>>,
@@ -101,24 +106,35 @@ impl<'w, 's> UiChildren<'w, 's> {
101106
.iter_ghost_nodes(entity)
102107
.any(|entity| self.changed_children_query.contains(entity))
103108
}
109+
110+
/// Returns `true` if the given entity is either a [`Node`] or a [`GhostNode`].
111+
pub fn is_ui_node(&'s self, entity: Entity) -> bool {
112+
self.ui_children_query.contains(entity)
113+
}
104114
}
105115

106116
pub struct UiChildrenIter<'w, 's> {
107117
stack: SmallVec<[Entity; 8]>,
108-
query: &'s Query<'w, 's, (Option<&'static Children>, Option<&'static GhostNode>)>,
118+
query: &'s Query<
119+
'w,
120+
's,
121+
(Option<&'static Children>, Has<GhostNode>),
122+
Or<(With<Node>, With<GhostNode>)>,
123+
>,
109124
}
110125

111126
impl<'w, 's> Iterator for UiChildrenIter<'w, 's> {
112127
type Item = Entity;
113128
fn next(&mut self) -> Option<Self::Item> {
114129
loop {
115130
let entity = self.stack.pop()?;
116-
let (children, ghost_node) = self.query.get(entity).ok()?;
117-
if ghost_node.is_none() {
118-
return Some(entity);
119-
}
120-
if let Some(children) = children {
121-
self.stack.extend(children.iter().rev().copied());
131+
if let Ok((children, has_ghost_node)) = self.query.get(entity) {
132+
if !has_ghost_node {
133+
return Some(entity);
134+
}
135+
if let Some(children) = children {
136+
self.stack.extend(children.iter().rev().copied());
137+
}
122138
}
123139
}
124140
}
@@ -186,10 +202,12 @@ mod tests {
186202
let n9 = world.spawn((A(9), GhostNode)).id();
187203
let n10 = world.spawn((A(10), NodeBundle::default())).id();
188204

205+
let no_ui = world.spawn_empty().id();
206+
189207
world.entity_mut(n1).add_children(&[n2, n3, n4, n6]);
190208
world.entity_mut(n2).add_children(&[n5]);
191209

192-
world.entity_mut(n6).add_children(&[n7, n9]);
210+
world.entity_mut(n6).add_children(&[n7, no_ui, n9]);
193211
world.entity_mut(n7).add_children(&[n8]);
194212
world.entity_mut(n9).add_children(&[n10]);
195213

crates/bevy_ui/src/layout/mod.rs

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ use bevy_ecs::{
1111
system::{Commands, Local, Query, Res, ResMut, SystemParam},
1212
world::Ref,
1313
};
14-
use bevy_hierarchy::Children;
14+
use bevy_hierarchy::{Children, Parent};
1515
use bevy_math::{UVec2, Vec2};
1616
use bevy_render::camera::{Camera, NormalizedRenderTarget};
1717
use bevy_sprite::BorderRect;
@@ -114,7 +114,7 @@ pub fn ui_layout_system(
114114
),
115115
With<Node>,
116116
>,
117-
node_query: Query<Entity, With<Node>>,
117+
node_query: Query<(Entity, Option<Ref<Parent>>), With<Node>>,
118118
ui_children: UiChildren,
119119
mut removed_components: UiLayoutSystemRemovedComponentParam,
120120
mut node_transform_query: Query<(
@@ -249,7 +249,19 @@ pub fn ui_layout_system(
249249
ui_surface.try_remove_children(entity);
250250
}
251251

252-
node_query.iter().for_each(|entity| {
252+
node_query.iter().for_each(|(entity, maybe_parent)| {
253+
if let Some(parent) = maybe_parent {
254+
// Note: This does not cover the case where a parent's Node component was removed.
255+
// Users are responsible for fixing hierarchies if they do that (it is not recommended).
256+
// Detecting it here would be a permanent perf burden on the hot path.
257+
if parent.is_changed() && !ui_children.is_ui_node(parent.get()) {
258+
warn!(
259+
"Styled child ({entity}) in a non-UI entity hierarchy. You are using an entity \
260+
with UI components as a child of an entity without UI components, your UI layout may be broken."
261+
);
262+
}
263+
}
264+
253265
if ui_children.is_changed(entity) {
254266
ui_surface.update_children(entity, ui_children.iter_ui_children(entity));
255267
}
@@ -261,7 +273,7 @@ pub fn ui_layout_system(
261273
ui_surface.remove_entities(removed_components.removed_nodes.read());
262274

263275
// Re-sync changed children: avoid layout glitches caused by removed nodes that are still set as a child of another node
264-
node_query.iter().for_each(|entity| {
276+
node_query.iter().for_each(|(entity, _)| {
265277
if ui_children.is_changed(entity) {
266278
ui_surface.update_children(entity, ui_children.iter_ui_children(entity));
267279
}

crates/bevy_ui/src/layout/ui_surface.rs

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ use bevy_ecs::{
77
prelude::Resource,
88
};
99
use bevy_math::UVec2;
10-
use bevy_utils::{default, tracing::warn};
10+
use bevy_utils::default;
1111

1212
use crate::{
1313
layout::convert, LayoutContext, LayoutError, Measure, MeasureArgs, NodeMeasure, Style,
@@ -289,10 +289,6 @@ impl UiSurface {
289289
.layout(*taffy_node)
290290
.map_err(LayoutError::TaffyError)
291291
} else {
292-
warn!(
293-
"Styled child ({entity}) in a non-UI entity hierarchy. You are using an entity \
294-
with UI components as a child of an entity without UI components, your UI layout may be broken."
295-
);
296292
Err(LayoutError::InvalidHierarchy)
297293
}
298294
}

0 commit comments

Comments
 (0)