Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Filter UI traversal to only Node and GhostNode #15746

Merged
merged 6 commits into from
Oct 13, 2024

Conversation

villor
Copy link
Contributor

@villor villor commented Oct 8, 2024

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.

@alice-i-cecile alice-i-cecile requested a review from viridia October 8, 2024 21:04
@alice-i-cecile alice-i-cecile added this to the 0.15 milestone Oct 8, 2024
@alice-i-cecile alice-i-cecile added C-Bug An unexpected or incorrect behavior A-UI Graphical user interfaces, styles, layouts, and widgets S-Needs-Review Needs reviewer attention (from anyone!) to move forward labels Oct 8, 2024
@UkoeHB
Copy link
Contributor

UkoeHB commented Oct 8, 2024

This means the warning in UiSurface::get_layout won't fire for nodes with non-node parents (see #15736). I support this PR but we need a way to detect and warn for nodes with non-node parents (preferably one that isn't incidental like the current reliance on get_layout).

@UkoeHB UkoeHB added the S-Waiting-on-Author The author needs to make changes or address concerns before this can be merged label Oct 8, 2024
@villor
Copy link
Contributor Author

villor commented Oct 8, 2024

This means the warning in UiSurface::get_layout won't fire for nodes with non-node parents (see #15736). I support this PR but we need a way to detect and warn for nodes with non-node parents (preferably one that isn't incidental like the current reliance on get_layout).

Right! Any ideas on how we could do that? A system just for the warning? 🤔

@cart
Copy link
Member

cart commented Oct 8, 2024

This means the warning in UiSurface::get_layout won't fire for nodes with non-node parents

Ah yeah we definitely want to retain that warning. The wins here aren't worth dropping that.

@UkoeHB
Copy link
Contributor

UkoeHB commented Oct 8, 2024

Looks like the warning actually never prints, since update_uinode_geometry_recursive early-outs if it hits an entity without Node.

One solution would be for the UiChildren iterator to return Has<Node> || Has<GhostNode> then in the for child_uinode in ui_children.iter_ui_children(entity) block in update_uinode_geometry_recursive, inspect any non-node entities to see if they have any descendants with Node or GhostNode and issue a warning. The warning in UiChildren::get_layout should be relocated since that function already returns an error.

@alice-i-cecile alice-i-cecile removed the S-Needs-Review Needs reviewer attention (from anyone!) to move forward label Oct 8, 2024
@villor
Copy link
Contributor Author

villor commented Oct 8, 2024

Looks like the warning actually never prints, since update_uinode_geometry_recursive early-outs if it hits an entity without Node.

One solution would be for the UiChildren iterator to return Has<Node> || Has<GhostNode> then in the for child_uinode in ui_children.iter_ui_children(entity) block in update_uinode_geometry_recursive, inspect any non-node entities to see if they have any descendants with Node or GhostNode and issue a warning. The warning in UiChildren::get_layout should be relocated since that function already returns an error.

Wouldn't that basically mean we still traverse all non-UI entities? To check the descendants.

Also the warning would still only show up for Nodes under non-UI entities somewhere below a valid UI tree. We would not catch Nodes added somewhere else, right?

I think for this warning to really work anywhere, it needs to be decoupled from the layout system completely. How about a system like this?

fn warn_ui_entity_in_non_ui_hierarchy(
    nodes_with_changed_parent: Query<(Entity, &Parent), (With<Node>, Changed<Parent>)>,
    ui_entities: Query<Entity, Or<(With<Node>, With<GhostNode>)>>,
) {
    for (entity, parent) in nodes_with_changed_parent.iter() {
        if !ui_entities.contains(parent.get()) {
            warn!(
                "Styled child ({entity}) in a non-UI entity hierarchy. You are using an entity \
with UI components as a child of an entity without UI components, your UI layout may be broken."
            );
        }
    }
}

@UkoeHB
Copy link
Contributor

UkoeHB commented Oct 8, 2024

I think for this warning to really work anywhere, it needs to be decoupled from the layout system completely. How about a system like this?

Yeah this would catch orphaned sub-trees (non-UI -> UI without any preceding UI). Main downside is Changed<Parent> means we iterate all nodes every tick. It also doesn't catch the edge case of Node being removed from a parent.

@villor
Copy link
Contributor Author

villor commented Oct 8, 2024

I think for this warning to really work anywhere, it needs to be decoupled from the layout system completely. How about a system like this?

Yeah this would catch orphaned sub-trees (non-UI -> UI without any preceding UI). Main downside is Changed<Parent> means we iterate all nodes every tick. It also doesn't catch the edge case of Node being removed from a parent.

Huh, I had no idea that’s how Changed worked. You learn something new every day 😅 could we use HierarchyEvent::ChildAdded/ChildMoved to avoid that?

Since a Node without parent is a valid root node, we shouldn’t really need to care about removals for this warning system.

@UkoeHB
Copy link
Contributor

UkoeHB commented Oct 8, 2024

could we use HierarchyEvent::ChildAdded/ChildMoved to avoid that?

This seems viable, although now the perf issue is frames where you spawn a big bunch of entities in a hierarchy (like a scene, or a bunch of UI) will become even more expensive.

@BenjaminBrienen BenjaminBrienen added X-Contentious There are nontrivial implications that should be thought through D-Straightforward Simple bug fixes and API improvements, docs, test and examples labels Oct 10, 2024
@villor
Copy link
Contributor Author

villor commented Oct 11, 2024

Maybe we can include the warning system conditionally using #[cfg(debug_assertions)]. Then at least it won’t affect production perf.

Just have to decide between Changed or HierarchyEvent, or something else…

@alice-i-cecile @cart do you have any preference here?

@cart
Copy link
Member

cart commented Oct 11, 2024

Maybe we can include the warning system conditionally using #[cfg(debug_assertions)]. Then at least it won’t affect production perf.

I like this idea.

Just have to decide between Changed or HierarchyEvent, or something else…

HierarchyEvent is a blunt-force tool, given that it is unfiltered. I personally think we should reconsider its existence entirely. Consuming that event is committing to iterating every hierarchy change across the entire world (and then checking if each change matches your criteria). Pretty much nobody should want to that. Producing those events feels likes a fixed cost that we shouldn't need to pay for most things.

Given that we're already iterating every UI node entity here, it feels like we might as well just insert our logic there (check if parent has changed, and if so, check if the parent has Node or GhostNode)

@villor
Copy link
Contributor Author

villor commented Oct 11, 2024

Given that we're already iterating every UI node entity here, it feels like we might as well just insert our logic there (check if parent has changed, and if so, check if the parent has Node or GhostNode)

Done!

Tested with the following:

commands.spawn(()).with_children(|parent| {
    parent.spawn(NodeBundle::default());
});

Triggered just fine 👍

Decided not to do any #[cfg(debug_assertions)]-check since I figured there might be cases in the future where UI:s are created by someone running a release-mode binary (like an editor), and the warning might be helpful to them. We are iterating all the nodes either way so why not 🤷‍♂️

I think this is ready for reviews now.

Copy link
Contributor

@UkoeHB UkoeHB left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just a new comment

crates/bevy_ui/src/layout/mod.rs Outdated Show resolved Hide resolved
Co-authored-by: UkoeHB <37489173+UkoeHB@users.noreply.github.com>
@alice-i-cecile alice-i-cecile added S-Ready-For-Final-Review This PR has been approved by the community. It's ready for a maintainer to consider merging it and removed S-Waiting-on-Author The author needs to make changes or address concerns before this can be merged labels Oct 13, 2024
@alice-i-cecile alice-i-cecile added this pull request to the merge queue Oct 13, 2024
Merged via the queue into bevyengine:main with commit 5989a84 Oct 13, 2024
28 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
A-UI Graphical user interfaces, styles, layouts, and widgets C-Bug An unexpected or incorrect behavior D-Straightforward Simple bug fixes and API improvements, docs, test and examples S-Ready-For-Final-Review This PR has been approved by the community. It's ready for a maintainer to consider merging it X-Contentious There are nontrivial implications that should be thought through
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants