Skip to content

Conversation

codaishin
Copy link

@codaishin codaishin commented Sep 30, 2025

Objective

When iterating over descendant entities: It can be a bit cumbersome to ignore recursive RelationshipTargets, when some condition on their parent (which we already visited during the iteration) has invalidated them for a given purpose. See more in the Showcase below.

Solution

This PR adds 2 chain-able methods to entity descendant iteration (iter_descendants and iter_descendants_depth_first) as a way to deal with this:

  • filter_hierarchies
  • filter_map_hierarchies

Both allow skipping entire sub hierarchies based on a predicate/mapper applied their respective root entity. Breadth-first and depth-first traversal are abstracted behind a trait DescendantsIteration, so they could be nested in a FilterHierarchies and FilterMapHierarchies struct (in the spirit of Filter<Iter<...>>).

Testing

  • tests for the filter/mapper and for established behavior have been added
  • tests can be found in the local module: test_iter_descendants
  • tests can be run in isolation with cargo test -p bevy_ecs test_iter_descendants

Showcase

The following example shows a system working around a FontGroup component. It applies a text fonts on itself and all children as long as those children are not managed by another FontGroup component.

#[derive(Component)]
struct FontGroup {
	font: TextFont,
}

fn apply_font_groups(
    mut commands: Commands,
    children: Query<&Children>,
    font_groups: Query<(Entity, Ref<FontGroup>)>,
) {
    for (entity, font_group) in &font_groups {
        if !font_group.is_changed() {
            continue;
        }

        commands.entity(entity).insert(font_group.font.clone());

        let children = children
            .iter_descendants(entity)
            // ignore all sub hierarchies that have their own `FontGroup`
            .filter_hierarchies(|child| !font_groups.contains(*child));

        for child in children {
            commands.entity(child).insert(font_group.font.clone());
        }
    }
}

Copy link
Contributor

Welcome, new contributor!

Please make sure you've read our contributing guide and we look forward to reviewing your pull request shortly ✨

@codaishin codaishin force-pushed the filter-descendant-hiearchies branch from 479ad54 to f85ca74 Compare October 1, 2025 09:32
@alice-i-cecile alice-i-cecile added A-ECS Entities, components, systems, and events C-Usability A targeted quality-of-life change that makes Bevy easier to use S-Needs-Review Needs reviewer attention (from anyone!) to move forward D-Modest A "normal" level of difficulty; suitable for simple features or challenging fixes X-Uncontroversial This work is generally agreed upon labels Oct 1, 2025
refactor `DescendantIter` to be a wrapper around a generic traversal strategy. `filter_hierarchies` returns an iterator using that strategy.
@codaishin codaishin force-pushed the filter-descendant-hiearchies branch from f85ca74 to 630344f Compare October 6, 2025 08:19
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
A-ECS Entities, components, systems, and events C-Usability A targeted quality-of-life change that makes Bevy easier to use D-Modest A "normal" level of difficulty; suitable for simple features or challenging fixes S-Needs-Review Needs reviewer attention (from anyone!) to move forward X-Uncontroversial This work is generally agreed upon
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants