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

Generic hierarchy propagation #5507

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions crates/bevy_hierarchy/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,9 @@ pub use child_builder::*;
mod events;
pub use events::*;

mod propagation;
pub use propagation::*;

#[doc(hidden)]
pub mod prelude {
#[doc(hidden)]
Expand Down
109 changes: 109 additions & 0 deletions crates/bevy_hierarchy/src/propagation.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
use bevy_ecs::{
entity::Entity,
prelude::{Changed, Component, With, Without},
system::Query,
};

use crate::{Children, Parent};

/// Describe how the global state can be computed and updated from the local state
pub trait ComputedGlobal {
/// Local component associated to this global component
type Local: Component;

/// Subset of `Self` that is needed to propagate information
type ToPropagate;

/// How to get the global state from the local state
///
/// This is used on the root entity of the hierarchy, to initialize the global state from
/// the local state of the root entity
fn from_local(local: &Self::Local) -> Self;

/// How to combine the propagated value with the local state of an entity to get its global
/// state
fn combine_with_local(propagated: &Self::ToPropagate, local: &Self::Local) -> Self;

/// How to extract the value to propagate from a global state
fn value_to_propagate(&self) -> Self::ToPropagate;
}

/// Propagate a component through a hierarchy. This is done by having two components, a local describing the
/// state of a given entity, and a global describing its state combined with the one of its parent. As the
/// local state changes during execution, the global state reflect those changes and is updated automatically.
pub fn propagate_system<Global: Component + ComputedGlobal>(
mut root_query: Query<
(
Option<(&Children, Changed<Children>)>,
&Global::Local,
Changed<Global::Local>,
&mut Global,
Entity,
),
Without<Parent>,
>,
mut local_query: Query<(&Global::Local, Changed<Global::Local>, &mut Global, &Parent)>,
children_query: Query<(&Children, Changed<Children>), (With<Parent>, With<Global>)>,
) {
for (children, local, local_changed, mut global, entity) in &mut root_query {
let mut changed = local_changed;
if local_changed {
*global = Global::from_local(local);
}

if let Some((children, changed_children)) = children {
// If our `Children` has changed, we need to recalculate everything below us
changed |= changed_children;
for child in children {
let _ = propagate_recursive(
&global.value_to_propagate(),
&mut local_query,
&children_query,
*child,
entity,
changed,
);
}
}
}
}

fn propagate_recursive<Global: Component + ComputedGlobal>(
parent: &Global::ToPropagate,
local_query: &mut Query<(&Global::Local, Changed<Global::Local>, &mut Global, &Parent)>,
children_query: &Query<(&Children, Changed<Children>), (With<Parent>, With<Global>)>,
entity: Entity,
expected_parent: Entity,
mut changed: bool,
// BLOCKED: https://github.com/rust-lang/rust/issues/31436
// We use a result here to use the `?` operator. Ideally we'd use a try block instead
) -> Result<(), ()> {
let global_value = {
let (local, local_changed, mut global, child_parent) =
local_query.get_mut(entity).map_err(drop)?;
assert_eq!(
child_parent.get(), expected_parent,
"Malformed hierarchy. This probably means that your hierarchy has been improperly maintained, or contains a cycle"
);
changed |= local_changed;
if changed {
*global = ComputedGlobal::combine_with_local(parent, local);
}
global.value_to_propagate()
};

let (children, changed_children) = children_query.get(entity).map_err(drop)?;
// If our `Children` has changed, we need to recalculate everything below us
changed |= changed_children;
for child in children {
let _ = propagate_recursive(
&global_value,
local_query,
children_query,
*child,
entity,
changed,
);
}
Ok(())
}
92 changes: 31 additions & 61 deletions crates/bevy_render/src/view/visibility/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ pub use render_layers::*;
use bevy_app::{CoreStage, Plugin};
use bevy_asset::{Assets, Handle};
use bevy_ecs::prelude::*;
use bevy_hierarchy::{Children, Parent};
use bevy_hierarchy::{propagate_system, ComputedGlobal};
use bevy_reflect::std_traits::ReflectDefault;
use bevy_reflect::Reflect;
use bevy_transform::components::GlobalTransform;
Expand All @@ -21,14 +21,14 @@ use crate::{

/// User indication of whether an entity is visible. Propagates down the entity hierarchy.

/// If an entity is hidden in this way, all [`Children`] (and all of their children and so on) will also be hidden.
/// If an entity is hidden in this way, all [`Children`](bevy_hierarchy::Children) (and all of their children and so on) will also be hidden.
/// This is done by setting the values of their [`ComputedVisibility`] component.
#[derive(Component, Clone, Reflect, Debug)]
#[reflect(Component, Default)]
pub struct Visibility {
/// Indicates whether this entity is visible. Hidden values will propagate down the entity hierarchy.
/// If this entity is hidden, all of its descendants will be hidden as well. See [`Children`] and [`Parent`] for
/// hierarchy info.
/// If this entity is hidden, all of its descendants will be hidden as well. See [`Children`](bevy_hierarchy::Children)
/// and [`Parent`](bevy_hierarchy::Parent) for hierarchy info.
pub is_visible: bool,
}

Expand Down Expand Up @@ -78,8 +78,8 @@ impl ComputedVisibility {
}

/// Whether this entity is visible in the entity hierarchy, which is determined by the [`Visibility`] component.
/// This takes into account "visibility inheritance". If any of this entity's ancestors (see [`Parent`]) are hidden, this entity
/// will be hidden as well. This value is updated in the [`CoreStage::PostUpdate`] stage in the
/// This takes into account "visibility inheritance". If any of this entity's ancestors (see [`Parent`](bevy_hierarchy::Parent))
/// are hidden, this entity will be hidden as well. This value is updated in the [`CoreStage::PostUpdate`] stage in the
/// [`VisibilitySystems::VisibilityPropagate`] system label.
#[inline]
pub fn is_visible_in_hierarchy(&self) -> bool {
Expand Down Expand Up @@ -202,7 +202,7 @@ impl Plugin for VisibilityPlugin {
)
.add_system_to_stage(
CoreStage::PostUpdate,
visibility_propagate_system.label(VisibilityPropagate),
propagate_system::<ComputedVisibility>.label(VisibilityPropagate),
)
.add_system_to_stage(
CoreStage::PostUpdate,
Expand Down Expand Up @@ -247,63 +247,33 @@ pub fn update_frusta<T: Component + CameraProjection + Send + Sync + 'static>(
}
}

fn visibility_propagate_system(
mut root_query: Query<
(
Option<&Children>,
&Visibility,
&mut ComputedVisibility,
Entity,
),
Without<Parent>,
>,
mut visibility_query: Query<(&Visibility, &mut ComputedVisibility, &Parent)>,
children_query: Query<&Children, (With<Parent>, With<Visibility>, With<ComputedVisibility>)>,
) {
for (children, visibility, mut computed_visibility, entity) in root_query.iter_mut() {
computed_visibility.is_visible_in_hierarchy = visibility.is_visible;
// reset "view" visibility here ... if this entity should be drawn a future system should set this to true
computed_visibility.is_visible_in_view = false;
if let Some(children) = children {
for child in children.iter() {
let _ = propagate_recursive(
computed_visibility.is_visible_in_hierarchy,
&mut visibility_query,
&children_query,
*child,
entity,
);
}
impl ComputedGlobal for ComputedVisibility {
type Local = Visibility;

type ToPropagate = bool;

#[inline(always)]
fn from_local(local: &Self::Local) -> Self {
ComputedVisibility {
is_visible_in_hierarchy: local.is_visible,
// reset "view" visibility here ... if this entity should be drawn a future system should set this to true
is_visible_in_view: false,
}
}
}

fn propagate_recursive(
parent_visible: bool,
visibility_query: &mut Query<(&Visibility, &mut ComputedVisibility, &Parent)>,
children_query: &Query<&Children, (With<Parent>, With<Visibility>, With<ComputedVisibility>)>,
entity: Entity,
expected_parent: Entity,
// BLOCKED: https://github.com/rust-lang/rust/issues/31436
// We use a result here to use the `?` operator. Ideally we'd use a try block instead
) -> Result<(), ()> {
let is_visible = {
let (visibility, mut computed_visibility, child_parent) =
visibility_query.get_mut(entity).map_err(drop)?;
assert_eq!(
child_parent.get(), expected_parent,
"Malformed hierarchy. This probably means that your hierarchy has been improperly maintained, or contains a cycle"
);
computed_visibility.is_visible_in_hierarchy = visibility.is_visible && parent_visible;
// reset "view" visibility here ... if this entity should be drawn a future system should set this to true
computed_visibility.is_visible_in_view = false;
computed_visibility.is_visible_in_hierarchy
};

for child in children_query.get(entity).map_err(drop)?.iter() {
let _ = propagate_recursive(is_visible, visibility_query, children_query, *child, entity);
#[inline(always)]
fn combine_with_local(propagated: &Self::ToPropagate, local: &Self::Local) -> Self {
ComputedVisibility {
is_visible_in_hierarchy: local.is_visible && *propagated,
// reset "view" visibility here ... if this entity should be drawn a future system should set this to true
is_visible_in_view: false,
}
}

#[inline(always)]
fn value_to_propagate(&self) -> Self::ToPropagate {
self.is_visible_in_hierarchy
}
Ok(())
}

// the batch size used for check_visibility, chosen because this number tends to perform well
Expand Down Expand Up @@ -419,7 +389,7 @@ mod test {
#[test]
fn visibility_propagation() {
let mut app = App::new();
app.add_system(visibility_propagate_system);
app.add_system(propagate_system::<ComputedVisibility>);

let root1 = app
.world
Expand Down
2 changes: 1 addition & 1 deletion crates/bevy_transform/src/components/global_transform.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ use bevy_reflect::Reflect;
/// [`GlobalTransform`] is the position of an entity relative to the reference frame.
///
/// [`GlobalTransform`] is updated from [`Transform`] in the system
/// [`transform_propagate_system`](crate::transform_propagate_system).
/// [`propagate_system`](bevy_hierarchy::propagate_system).
///
/// This system runs in stage [`CoreStage::PostUpdate`](crate::CoreStage::PostUpdate). If you
/// update the [`Transform`] of an entity in this stage or after, you will notice a 1 frame lag
Expand Down
2 changes: 1 addition & 1 deletion crates/bevy_transform/src/components/transform.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ use std::ops::Mul;
/// [`GlobalTransform`] is the position of an entity relative to the reference frame.
///
/// [`GlobalTransform`] is updated from [`Transform`] in the system
/// [`transform_propagate_system`](crate::transform_propagate_system).
/// [`propagate_system`](bevy_hierarchy::propagate_system).
///
/// This system runs in stage [`CoreStage::PostUpdate`](crate::CoreStage::PostUpdate). If you
/// update the [`Transform`] of an entity in this stage or after, you will notice a 1 frame lag
Expand Down
10 changes: 5 additions & 5 deletions crates/bevy_transform/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,7 @@

/// The basic components of the transform crate
pub mod components;
mod systems;
pub use crate::systems::transform_propagate_system;
mod propagation;

#[doc(hidden)]
pub mod prelude {
Expand All @@ -14,6 +13,7 @@ pub mod prelude {

use bevy_app::prelude::*;
use bevy_ecs::prelude::*;
use bevy_hierarchy::propagate_system;
use prelude::{GlobalTransform, Transform};

/// A [`Bundle`] of the [`Transform`] and [`GlobalTransform`]
Expand All @@ -32,7 +32,7 @@ use prelude::{GlobalTransform, Transform};
/// [`GlobalTransform`] is the position of an entity relative to the reference frame.
///
/// [`GlobalTransform`] is updated from [`Transform`] in the system
/// [`transform_propagate_system`].
/// [`propagate_system`](bevy_hierarchy::propagate_system).
///
/// This system runs in stage [`CoreStage::PostUpdate`](crate::CoreStage::PostUpdate). If you
/// update the [`Transform`] of an entity in this stage or after, you will notice a 1 frame lag
Expand Down Expand Up @@ -94,11 +94,11 @@ impl Plugin for TransformPlugin {
// add transform systems to startup so the first update is "correct"
.add_startup_system_to_stage(
StartupStage::PostStartup,
systems::transform_propagate_system.label(TransformSystem::TransformPropagate),
propagate_system::<GlobalTransform>.label(TransformSystem::TransformPropagate),
)
.add_system_to_stage(
CoreStage::PostUpdate,
systems::transform_propagate_system.label(TransformSystem::TransformPropagate),
propagate_system::<GlobalTransform>.label(TransformSystem::TransformPropagate),
);
}
}
Loading