//! Maps authoring [`EditorVisibility`] to runtime [`Visibility`]. use crate::{EditorVisibility, LevelObject}; use bevy::prelude::*; /// Establishes the runtime visibility hierarchy synchronously for all authored actors. /// /// Scene loading can occur between scheduled hydration sets, so callers that materialize a scene /// must run this before generated render children can be attached in the same frame. pub fn initialize_level_object_visibility_hierarchy(world: &mut World) { let targets: Vec<(Entity, EditorVisibility)> = world .query_filtered::<(Entity, Option<&EditorVisibility>), With>() .iter(world) .map(|(entity, visibility)| (entity, visibility.copied().unwrap_or_default())) .collect(); for (entity, editor_visibility) in targets { if let Ok(mut entity_mut) = world.get_entity_mut(entity) { entity_mut.insert((editor_visibility, visibility_from_editor(editor_visibility))); } } } #[expect( clippy::type_complexity, reason = "the query mirrors Bevy's three visibility hierarchy components" )] pub fn ensure_level_object_visibility_hierarchy( mut commands: Commands, query: Query< ( Entity, &EditorVisibility, Option<&Visibility>, Option<&InheritedVisibility>, Option<&ViewVisibility>, ), With, >, ) { for (entity, editor, visibility, inherited_visibility, view_visibility) in &query { let mut entity_commands = commands.entity(entity); if visibility.is_none() { entity_commands.insert(visibility_from_editor(*editor)); } if inherited_visibility.is_none() { entity_commands.insert(InheritedVisibility::default()); } if view_visibility.is_none() { entity_commands.insert(ViewVisibility::default()); } } } #[expect( clippy::type_complexity, reason = "the query expresses Bevy change detection at the system boundary" )] pub fn sync_editor_visibility( mut query: Query< (&EditorVisibility, &mut Visibility), ( With, Or<(Changed, Added)>, ), >, ) { for (editor, mut visibility) in query.iter_mut() { *visibility = visibility_from_editor(*editor); } } #[expect( clippy::type_complexity, reason = "the query expresses Bevy spawn detection at the system boundary" )] pub fn init_editor_visibility_on_spawn( mut query: Query< (&EditorVisibility, &mut Visibility), ( With, Added, With, ), >, ) { for (editor, mut visibility) in query.iter_mut() { *visibility = visibility_from_editor(*editor); } } pub fn visibility_from_editor(editor: EditorVisibility) -> Visibility { if editor.visible { Visibility::Visible } else { Visibility::Hidden } } #[cfg(test)] mod tests { use super::{ ensure_level_object_visibility_hierarchy, initialize_level_object_visibility_hierarchy, }; use crate::{EditorVisibility, LevelObject}; use bevy::prelude::*; #[test] fn level_object_visibility_hierarchy_is_inserted_for_generated_children() { let mut app = App::new(); app.add_systems(Update, ensure_level_object_visibility_hierarchy); let entity = app .world_mut() .spawn((LevelObject, EditorVisibility { visible: false })) .id(); app.update(); let world = app.world(); assert_eq!(world.get::(entity), Some(&Visibility::Hidden)); assert!(world.get::(entity).is_some()); assert!(world.get::(entity).is_some()); } #[test] fn immediate_initializer_establishes_hierarchy_before_scheduled_hydration() { let mut world = World::new(); let entity = world .spawn((LevelObject, EditorVisibility { visible: false })) .id(); initialize_level_object_visibility_hierarchy(&mut world); assert_eq!(world.get::(entity), Some(&Visibility::Hidden)); assert!(world.get::(entity).is_some()); assert!(world.get::(entity).is_some()); } #[test] fn immediate_initializer_migrates_missing_authored_visibility() { let mut world = World::new(); let entity = world.spawn(LevelObject).id(); initialize_level_object_visibility_hierarchy(&mut world); assert_eq!( world.get::(entity), Some(&EditorVisibility::default()) ); assert_eq!(world.get::(entity), Some(&Visibility::Visible)); assert!(world.get::(entity).is_some()); assert!(world.get::(entity).is_some()); } }