Skip to content

Commit a0448ec

Browse files
ickkcart
andcommitted
enum Visibility component (#6320)
Consolidation of all the feedback about #6271 as well as the addition of an "unconditionally visible" mode. # Objective The current implementation of the `Visibility` struct simply wraps a boolean.. which seems like an odd pattern when rust has such nice enums that allow for more expression using pattern-matching. Additionally as it stands Bevy only has two settings for visibility of an entity: - "unconditionally hidden" `Visibility { is_visible: false }`, - "inherit visibility from parent" `Visibility { is_visible: true }` where a root level entity set to "inherit" is visible. Note that given the behaviour, the current naming of the inner field is a little deceptive or unclear. Using an enum for `Visibility` opens the door for adding an extra behaviour mode. This PR adds a new "unconditionally visible" mode, which causes an entity to be visible even if its Parent entity is hidden. There should not really be any performance cost to the addition of this new mode. -- The recently added `toggle` method is removed in this PR, as its semantics could be confusing with 3 variants. ## Solution Change the Visibility component into ```rust enum Visibility { Hidden, // unconditionally hidden Visible, // unconditionally visible Inherited, // inherit visibility from parent } ``` --- ## Changelog ### Changed `Visibility` is now an enum ## Migration Guide - evaluation of the `visibility.is_visible` field should now check for `visibility == Visibility::Inherited`. - setting the `visibility.is_visible` field should now directly set the value: `*visibility = Visibility::Inherited`. - usage of `Visibility::VISIBLE` or `Visibility::INVISIBLE` should now use `Visibility::Inherited` or `Visibility::Hidden` respectively. - `ComputedVisibility::INVISIBLE` and `SpatialBundle::VISIBLE_IDENTITY` have been renamed to `ComputedVisibility::HIDDEN` and `SpatialBundle::INHERITED_IDENTITY` respectively. Co-authored-by: Carter Anderson <mcanders1@gmail.com>
1 parent 9717204 commit a0448ec

File tree

7 files changed

+136
-50
lines changed

7 files changed

+136
-50
lines changed

crates/bevy_gltf/src/loader.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -465,7 +465,7 @@ async fn load_gltf<'a, 'b>(
465465
let mut entity_to_skin_index_map = HashMap::new();
466466

467467
world
468-
.spawn(SpatialBundle::VISIBLE_IDENTITY)
468+
.spawn(SpatialBundle::INHERITED_IDENTITY)
469469
.with_children(|parent| {
470470
for node in scene.nodes() {
471471
let result = load_node(

crates/bevy_render/src/spatial_bundle.rs

+7-7
Original file line numberDiff line numberDiff line change
@@ -33,22 +33,22 @@ impl SpatialBundle {
3333
pub const fn from_transform(transform: Transform) -> Self {
3434
SpatialBundle {
3535
transform,
36-
..Self::VISIBLE_IDENTITY
36+
..Self::INHERITED_IDENTITY
3737
}
3838
}
3939

4040
/// A visible [`SpatialBundle`], with no translation, rotation, and a scale of 1 on all axes.
41-
pub const VISIBLE_IDENTITY: Self = SpatialBundle {
42-
visibility: Visibility::VISIBLE,
43-
computed: ComputedVisibility::INVISIBLE,
41+
pub const INHERITED_IDENTITY: Self = SpatialBundle {
42+
visibility: Visibility::Inherited,
43+
computed: ComputedVisibility::HIDDEN,
4444
transform: Transform::IDENTITY,
4545
global_transform: GlobalTransform::IDENTITY,
4646
};
4747

4848
/// An invisible [`SpatialBundle`], with no translation, rotation, and a scale of 1 on all axes.
49-
pub const INVISIBLE_IDENTITY: Self = SpatialBundle {
50-
visibility: Visibility::INVISIBLE,
51-
..Self::VISIBLE_IDENTITY
49+
pub const HIDDEN_IDENTITY: Self = SpatialBundle {
50+
visibility: Visibility::Hidden,
51+
..Self::INHERITED_IDENTITY
5252
};
5353
}
5454

crates/bevy_render/src/view/visibility/mod.rs

+124-38
Original file line numberDiff line numberDiff line change
@@ -23,34 +23,42 @@ use crate::{
2323
};
2424

2525
/// User indication of whether an entity is visible. Propagates down the entity hierarchy.
26-
27-
/// If an entity is hidden in this way, all [`Children`] (and all of their children and so on) will also be hidden.
28-
/// This is done by setting the values of their [`ComputedVisibility`] component.
29-
#[derive(Component, Clone, Reflect, FromReflect, Debug)]
26+
///
27+
/// If an entity is hidden in this way, all [`Children`] (and all of their children and so on) who
28+
/// are set to `Inherited` will also be hidden.
29+
///
30+
/// This is done by the `visibility_propagate_system` which uses the entity hierarchy and
31+
/// `Visibility` to set the values of each entity's [`ComputedVisibility`] component.
32+
#[derive(Component, Clone, Copy, Reflect, FromReflect, Debug, PartialEq, Eq, Default)]
3033
#[reflect(Component, Default)]
31-
pub struct Visibility {
32-
/// Indicates whether this entity is visible. Hidden values will propagate down the entity hierarchy.
33-
/// If this entity is hidden, all of its descendants will be hidden as well. See [`Children`] and [`Parent`] for
34-
/// hierarchy info.
35-
pub is_visible: bool,
34+
pub enum Visibility {
35+
/// An entity with `Visibility::Inherited` will inherit the Visibility of its [`Parent`].
36+
///
37+
/// A root-level entity that is set to `Inherited` will be visible.
38+
#[default]
39+
Inherited,
40+
/// An entity with `Visibility::Hidden` will be unconditionally hidden.
41+
Hidden,
42+
/// An entity with `Visibility::Visible` will be unconditionally visible.
43+
///
44+
/// Note that an entity with `Visibility::Visible` will be visible regardless of whether the
45+
/// [`Parent`] entity is hidden.
46+
Visible,
3647
}
3748

38-
impl Default for Visibility {
39-
fn default() -> Self {
40-
Visibility::VISIBLE
49+
// Allows `&Visibility == Visibility`
50+
impl std::cmp::PartialEq<Visibility> for &Visibility {
51+
#[inline]
52+
fn eq(&self, other: &Visibility) -> bool {
53+
**self == *other
4154
}
4255
}
4356

44-
impl Visibility {
45-
/// A [`Visibility`], set as visible.
46-
pub const VISIBLE: Self = Visibility { is_visible: true };
47-
48-
/// A [`Visibility`], set as invisible.
49-
pub const INVISIBLE: Self = Visibility { is_visible: false };
50-
51-
/// Toggle the visibility.
52-
pub fn toggle(&mut self) {
53-
self.is_visible = !self.is_visible;
57+
// Allows `Visibility == &Visibility`
58+
impl std::cmp::PartialEq<&Visibility> for Visibility {
59+
#[inline]
60+
fn eq(&self, other: &&Visibility) -> bool {
61+
*self == **other
5462
}
5563
}
5664

@@ -71,13 +79,13 @@ pub struct ComputedVisibility {
7179

7280
impl Default for ComputedVisibility {
7381
fn default() -> Self {
74-
Self::INVISIBLE
82+
Self::HIDDEN
7583
}
7684
}
7785

7886
impl ComputedVisibility {
7987
/// A [`ComputedVisibility`], set as invisible.
80-
pub const INVISIBLE: Self = ComputedVisibility {
88+
pub const HIDDEN: Self = ComputedVisibility {
8189
flags: ComputedVisibilityFlags::empty(),
8290
};
8391

@@ -298,7 +306,8 @@ fn visibility_propagate_system(
298306
) {
299307
for (children, visibility, mut computed_visibility, entity) in root_query.iter_mut() {
300308
// reset "view" visibility here ... if this entity should be drawn a future system should set this to true
301-
computed_visibility.reset(visibility.is_visible);
309+
computed_visibility
310+
.reset(visibility == Visibility::Inherited || visibility == Visibility::Visible);
302311
if let Some(children) = children {
303312
for child in children.iter() {
304313
let _ = propagate_recursive(
@@ -329,7 +338,8 @@ fn propagate_recursive(
329338
child_parent.get(), expected_parent,
330339
"Malformed hierarchy. This probably means that your hierarchy has been improperly maintained, or contains a cycle"
331340
);
332-
let visible_in_hierarchy = visibility.is_visible && parent_visible;
341+
let visible_in_hierarchy = (parent_visible && visibility == Visibility::Inherited)
342+
|| visibility == Visibility::Visible;
333343
// reset "view" visibility here ... if this entity should be drawn a future system should set this to true
334344
computed_visibility.reset(visible_in_hierarchy);
335345
visible_in_hierarchy
@@ -458,21 +468,15 @@ mod test {
458468

459469
let root1 = app
460470
.world
461-
.spawn((
462-
Visibility { is_visible: false },
463-
ComputedVisibility::default(),
464-
))
471+
.spawn((Visibility::Hidden, ComputedVisibility::default()))
465472
.id();
466473
let root1_child1 = app
467474
.world
468475
.spawn((Visibility::default(), ComputedVisibility::default()))
469476
.id();
470477
let root1_child2 = app
471478
.world
472-
.spawn((
473-
Visibility { is_visible: false },
474-
ComputedVisibility::default(),
475-
))
479+
.spawn((Visibility::Hidden, ComputedVisibility::default()))
476480
.id();
477481
let root1_child1_grandchild1 = app
478482
.world
@@ -503,10 +507,7 @@ mod test {
503507
.id();
504508
let root2_child2 = app
505509
.world
506-
.spawn((
507-
Visibility { is_visible: false },
508-
ComputedVisibility::default(),
509-
))
510+
.spawn((Visibility::Hidden, ComputedVisibility::default()))
510511
.id();
511512
let root2_child1_grandchild1 = app
512513
.world
@@ -578,4 +579,89 @@ mod test {
578579
"child's invisibility propagates down to grandchild"
579580
);
580581
}
582+
583+
#[test]
584+
fn visibility_propagation_unconditional_visible() {
585+
let mut app = App::new();
586+
app.add_system(visibility_propagate_system);
587+
588+
let root1 = app
589+
.world
590+
.spawn((Visibility::Visible, ComputedVisibility::default()))
591+
.id();
592+
let root1_child1 = app
593+
.world
594+
.spawn((Visibility::Inherited, ComputedVisibility::default()))
595+
.id();
596+
let root1_child2 = app
597+
.world
598+
.spawn((Visibility::Hidden, ComputedVisibility::default()))
599+
.id();
600+
let root1_child1_grandchild1 = app
601+
.world
602+
.spawn((Visibility::Visible, ComputedVisibility::default()))
603+
.id();
604+
let root1_child2_grandchild1 = app
605+
.world
606+
.spawn((Visibility::Visible, ComputedVisibility::default()))
607+
.id();
608+
609+
let root2 = app
610+
.world
611+
.spawn((Visibility::Inherited, ComputedVisibility::default()))
612+
.id();
613+
let root3 = app
614+
.world
615+
.spawn((Visibility::Hidden, ComputedVisibility::default()))
616+
.id();
617+
618+
app.world
619+
.entity_mut(root1)
620+
.push_children(&[root1_child1, root1_child2]);
621+
app.world
622+
.entity_mut(root1_child1)
623+
.push_children(&[root1_child1_grandchild1]);
624+
app.world
625+
.entity_mut(root1_child2)
626+
.push_children(&[root1_child2_grandchild1]);
627+
628+
app.update();
629+
630+
let is_visible = |e: Entity| {
631+
app.world
632+
.entity(e)
633+
.get::<ComputedVisibility>()
634+
.unwrap()
635+
.is_visible_in_hierarchy()
636+
};
637+
assert!(
638+
is_visible(root1),
639+
"an unconditionally visible root is visible"
640+
);
641+
assert!(
642+
is_visible(root1_child1),
643+
"an inheriting child of an unconditionally visible parent is visible"
644+
);
645+
assert!(
646+
!is_visible(root1_child2),
647+
"a hidden child on an unconditionally visible parent is hidden"
648+
);
649+
assert!(
650+
is_visible(root1_child1_grandchild1),
651+
"an unconditionally visible child of an inheriting parent is visible"
652+
);
653+
assert!(
654+
is_visible(root1_child2_grandchild1),
655+
"an unconditionally visible child of a hidden parent is visible"
656+
);
657+
assert!(is_visible(root2), "an inheriting root is visible");
658+
assert!(!is_visible(root3), "a hidden root is hidden");
659+
}
660+
661+
#[test]
662+
fn ensure_visibility_enum_size() {
663+
use std::mem;
664+
assert_eq!(1, mem::size_of::<Visibility>());
665+
assert_eq!(1, mem::size_of::<Option<Visibility>>());
666+
}
581667
}

examples/2d/mesh2d_manual.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -103,7 +103,7 @@ fn star(
103103
// The `Handle<Mesh>` needs to be wrapped in a `Mesh2dHandle` to use 2d rendering instead of 3d
104104
Mesh2dHandle(meshes.add(star)),
105105
// This bundle's components are needed for something to be rendered
106-
SpatialBundle::VISIBLE_IDENTITY,
106+
SpatialBundle::INHERITED_IDENTITY,
107107
));
108108

109109
// Spawn the camera

examples/animation/animated_transform.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -129,7 +129,7 @@ fn setup(
129129
.with_children(|p| {
130130
// This entity is just used for animation, but doesn't display anything
131131
p.spawn((
132-
SpatialBundle::VISIBLE_IDENTITY,
132+
SpatialBundle::INHERITED_IDENTITY,
133133
// Add the Name component
134134
orbit_controller,
135135
))

examples/shader/shader_instancing.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ fn main() {
3535
fn setup(mut commands: Commands, mut meshes: ResMut<Assets<Mesh>>) {
3636
commands.spawn((
3737
meshes.add(Mesh::from(shape::Cube { size: 0.5 })),
38-
SpatialBundle::VISIBLE_IDENTITY,
38+
SpatialBundle::INHERITED_IDENTITY,
3939
InstanceMaterialData(
4040
(1..=10)
4141
.flat_map(|x| (1..=10).map(move |y| (x as f32 / 10.0, y as f32 / 10.0)))

examples/stress_tests/many_foxes.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -113,7 +113,7 @@ fn setup(
113113
let (base_rotation, ring_direction) = ring_directions[ring_index % 2];
114114
let ring_parent = commands
115115
.spawn((
116-
SpatialBundle::VISIBLE_IDENTITY,
116+
SpatialBundle::INHERITED_IDENTITY,
117117
ring_direction,
118118
Ring { radius },
119119
))

0 commit comments

Comments
 (0)