bevy icon indicating copy to clipboard operation
bevy copied to clipboard

Add more tools to conveniently work with hierarchies, especially in the presence of ghost nodes

Open alice-i-cecile opened this issue 1 year ago • 3 comments

What problem does this solve or what need does it fill?

Following the introduction of https://github.com/bevyengine/bevy/pull/15341, some users have expressed concerns around difficulties migrating to account for ghost nodes in their hierarchies.

This is a great opportunity to add more tools for traversing hierarchies in the spirit of iter_descendants.

What solution would you like?

There are two stages to this:

  1. Add more methods to HierarchyQueryExt to capture other common tasks.
  2. Add a ghost-node aware version of this trait, which lives in bevy_ui and takes a Has<GhostNode> argument as well. These methods should be named iter_node_descendants and so on.

In theory these can be done in either order, or in a single PR. Your choice!

In terms of additional tasks to cover:

  1. root_parent: the entity that is highest up the hierarchy tree, if any.
  2. leaf_children: an iterator over the children that have no children of their own.
  3. siblings: other entities that are in direct children of the same parent.
  4. parent: a convenience method that looks for the parent (much more useful when working with ghost nodes).
  5. children: as above, but for children.

What alternative(s) have you considered?

One day (🥺), this will get rolled into #3742. It's nice to ship helpful APIs to users sooner though, and these methods will be easier to migrate since they have semantic meaning.

alice-i-cecile avatar Oct 03 '24 01:10 alice-i-cecile

The UiChildren SystemParam tackles a good chunk of task 2. I'm going to start on task 1, get that merged, and then beef up that API to match it.

alice-i-cecile avatar Oct 03 '24 16:10 alice-i-cecile

With the first half of this issue is merged (#15627), we can focus on the second half: UI tree.

The second half

For the second half of this issue, we would want to make UIChildren have feature parity with HierarchyQueryExt.

I propose we also change name of UIChildren to UiTree to better convey what it is. This could also serve as a good place to document the rules of the tree (e.g. has to be all Node/GhostNode up to the root, non-UI entities beneath the tree are allowed/ignored)

I looked over the APIs and summarized the current state along with proposed UiTree APIs:

HierarchyQueryExt (current) UiChildren (current) UiTree (proposal) Notes
parent get_parent parent
children iter_ui_children children Alternative name iter_children to indicate it is an iterator rather than a slice.
root_ancestor missing root_ancestor
iter_leaves missing iter_leaves
iter_siblings missing iter_siblings
iter_descendants missing iter_descendants
iter_descendants_depth_first missing iter_descendants_depth_first
iter_ancestors missing iter_ancestors
missing is_changed children_is_changed Could be added to hierarchy with #15670
missing missing parent_is_changed Could be added to hierarchy with #15670
irrelevant iter_ghost_nodes iter_ghost_nodes

Bonus utilities

In addition to the above, we could also add more utilities that are useful in UI, here is a list inspired by CSS psuedo-classes:

is_root No parent or only ghost node ancestors
is_empty No children
child_index The index of a child among its siblings
is_nth_child child_index(entity) == n
is_nth_last_child child_index(entity) == children().len() - n
is_first_child child_index(entity) == 0
is_last_child child_index(entity) == children().len() - 1
is_odd_child (child_index(entity) + 1) % 2 == 1
is_even_child (child_index(entity) + 1) % 2 == 0

Maybe something for a follow-up though, and I'm not sure how much bloat we want in the API.

villor avatar Oct 12 '24 13:10 villor

Excellent investigation. Of the bonus APIs, I like is_root, is_empty (renamed to is_leaf) and child_index. The rest don't feel useful to me.

alice-i-cecile avatar Oct 12 '24 16:10 alice-i-cecile

I believe that @ickshonpe has previously expressed opposition to this approach. If I remember correctly, they believe that the proposed wrapper abstractions don't actually help all that much, and have noticeable costs. A straightforward recursive traversal of the hierarchy is faster than a fancy iterator, and may be easier to maintain in the long run.

viridia avatar Nov 01 '25 16:11 viridia

Some more detail on this:

As the person who (at this time) is the primary user of ghost nodes, I want to share my impressions. None of my uses of ghost nodes involves any kind of traversal. I never use the UiChildren iterator. The reason is because, as a client, the way I used ghost nodes is purely local and context-free: I insert ghost nodes wherever I need dynamic control flow, but I don't need to know anything about the parent or children in order to do this. I just assume they work.

The places where traversal needs to know about ghost nodes are all inside Bevy itself: computing visibility, transforms, layout, picking, and all the other things that the framework does. As a user, I don't invoke any of this code directly and I don't care if the APIs are convenient or not.

(This is not to say that I never do traversal as a user. But the places where I do traversal don't care about ghosts.)

So really, the issue is not "how do we make hierarchy traversal better for users", because I don't think we need to do this. The real issue is how we get the engine code to do traversal consistently with little or no performance impact.

viridia avatar Nov 01 '25 21:11 viridia

@viridia

I think this issue was mainly motivated by third party crate author concerns with ghost nodes and having to take them into account for their UI traversal. I would guess this is more apparent in non-reactive UI utilities that need to look up and down the hierarchy, and may not even be relevant in the future.

However, the initial motivation behind UiChildren / UiRootNodes was not primarily to make things easier for end users. It was about ensuring correctness within bevy_ui itself. There are (or at least were, maybe this has changed) a lot of places that query for UI roots and traverse the tree. Adding special logic for ghost nodes in each of these places seemed bug-prone and verbose.

If they have serious impacts on performance that can't be fixed I agree to remove them though. There are optimizations that can be made within those iterators though, which I did not do in the initial ghost node PR (for example iter_ui_children allocates on every level with > 8 children).

villor avatar Nov 01 '25 23:11 villor