Skip to content

Commit f576aca

Browse files
ickshonpevillor
andauthored
UI layout taffy sync fix (#20270)
# Objective #20266 only fixed half the problem as it still left the UI node's parent's children out of sync. ## Solution The Taffy children of a UI node entity need to be updated not just on `Node` being added but also on `Node` being added to any of its children. ## Testing Added a test `node_addition_should_sync_parent_and_children` that fails on main but passes with this PR. --------- Co-authored-by: Viktor Gustavsson <villor94@gmail.com>
1 parent e76a4ed commit f576aca

File tree

1 file changed

+54
-31
lines changed
  • crates/bevy_ui/src/layout

1 file changed

+54
-31
lines changed

crates/bevy_ui/src/layout/mod.rs

Lines changed: 54 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -7,17 +7,16 @@ use crate::{
77
use bevy_ecs::{
88
change_detection::{DetectChanges, DetectChangesMut},
99
entity::Entity,
10-
hierarchy::{ChildOf, Children},
10+
hierarchy::Children,
1111
lifecycle::RemovedComponents,
12-
query::With,
12+
query::Added,
1313
system::{Query, ResMut},
1414
world::Ref,
1515
};
1616

1717
use bevy_math::{Affine2, Vec2};
1818
use bevy_sprite::BorderRect;
1919
use thiserror::Error;
20-
use tracing::warn;
2120
use ui_surface::UiSurface;
2221

2322
use bevy_text::ComputedTextBlock;
@@ -73,14 +72,14 @@ pub enum LayoutError {
7372
pub fn ui_layout_system(
7473
mut ui_surface: ResMut<UiSurface>,
7574
ui_root_node_query: UiRootNodes,
75+
ui_children: UiChildren,
7676
mut node_query: Query<(
7777
Entity,
7878
Ref<Node>,
7979
Option<&mut ContentSize>,
8080
Ref<ComputedNodeTarget>,
8181
)>,
82-
computed_node_query: Query<(Entity, Ref<Node>, Option<Ref<ChildOf>>), With<ComputedNode>>,
83-
ui_children: UiChildren,
82+
added_node_query: Query<(), Added<Node>>,
8483
mut node_update_query: Query<(
8584
&mut ComputedNode,
8685
&UiTransform,
@@ -126,41 +125,42 @@ pub fn ui_layout_system(
126125
ui_surface.try_remove_children(entity);
127126
}
128127

129-
computed_node_query
130-
.iter()
131-
.for_each(|(entity, node, maybe_child_of)| {
132-
if let Some(child_of) = maybe_child_of {
133-
// Note: This does not cover the case where a parent's Node component was removed.
134-
// Users are responsible for fixing hierarchies if they do that (it is not recommended).
135-
// Detecting it here would be a permanent perf burden on the hot path.
136-
if child_of.is_changed() && !ui_children.is_ui_node(child_of.parent()) {
137-
warn!(
138-
"Node ({entity}) is in a non-UI entity hierarchy. You are using an entity \
139-
with UI components as a child of an entity without UI components, your UI layout may be broken."
140-
);
141-
}
142-
}
143-
144-
if node.is_added() || ui_children.is_changed(entity) {
145-
ui_surface.update_children(entity, ui_children.iter_ui_children(entity));
146-
}
147-
});
148-
149128
// clean up removed nodes after syncing children to avoid potential panic (invalid SlotMap key used)
150129
ui_surface.remove_entities(
151130
removed_nodes
152131
.read()
153132
.filter(|entity| !node_query.contains(*entity)),
154133
);
155134

156-
// Re-sync changed children: avoid layout glitches caused by removed nodes that are still set as a child of another node
157-
computed_node_query.iter().for_each(|(entity, node, _)| {
158-
if node.is_added() || ui_children.is_changed(entity) {
159-
ui_surface.update_children(entity, ui_children.iter_ui_children(entity));
135+
for ui_root_entity in ui_root_node_query.iter() {
136+
fn update_children_recursively(
137+
ui_surface: &mut UiSurface,
138+
ui_children: &UiChildren,
139+
added_node_query: &Query<(), Added<Node>>,
140+
entity: Entity,
141+
) {
142+
if ui_surface.entity_to_taffy.contains_key(&entity)
143+
&& (added_node_query.contains(entity)
144+
|| ui_children.is_changed(entity)
145+
|| ui_children
146+
.iter_ui_children(entity)
147+
.any(|child| added_node_query.contains(child)))
148+
{
149+
ui_surface.update_children(entity, ui_children.iter_ui_children(entity));
150+
}
151+
152+
for child in ui_children.iter_ui_children(entity) {
153+
update_children_recursively(ui_surface, ui_children, added_node_query, child);
154+
}
160155
}
161-
});
162156

163-
for ui_root_entity in ui_root_node_query.iter() {
157+
update_children_recursively(
158+
&mut ui_surface,
159+
&ui_children,
160+
&added_node_query,
161+
ui_root_entity,
162+
);
163+
164164
let (_, _, _, computed_target) = node_query.get(ui_root_entity).unwrap();
165165

166166
ui_surface.compute_layout(
@@ -670,6 +670,29 @@ mod tests {
670670
assert_eq!(ui_surface.taffy.child_count(taffy_root.id), 1);
671671
}
672672

673+
#[test]
674+
fn node_addition_should_sync_parent_and_children() {
675+
let (mut world, mut ui_schedule) = setup_ui_test_world();
676+
677+
let d = world.spawn(Node::default()).id();
678+
let c = world.spawn(()).add_child(d).id();
679+
let b = world.spawn(Node::default()).id();
680+
let a = world.spawn(Node::default()).add_children(&[b, c]).id();
681+
682+
ui_schedule.run(&mut world);
683+
684+
// fix the invalid middle node by inserting a Node
685+
world.entity_mut(c).insert(Node::default());
686+
687+
ui_schedule.run(&mut world);
688+
689+
let ui_surface = world.resource::<UiSurface>();
690+
for (entity, n) in [(a, 2), (b, 0), (c, 1), (d, 0)] {
691+
let taffy_id = ui_surface.entity_to_taffy[&entity].id;
692+
assert_eq!(ui_surface.taffy.child_count(taffy_id), n);
693+
}
694+
}
695+
673696
/// regression test for >=0.13.1 root node layouts
674697
/// ensure root nodes act like they are absolutely positioned
675698
/// without explicitly declaring it.

0 commit comments

Comments
 (0)