Blacksite/crates/shared/src/hydration/visibility.rs

156 lines
4.9 KiB
Rust

//! 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<LevelObject>>()
.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<LevelObject>,
>,
) {
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<LevelObject>,
Or<(Changed<EditorVisibility>, Added<EditorVisibility>)>,
),
>,
) {
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<LevelObject>,
Added<LevelObject>,
With<EditorVisibility>,
),
>,
) {
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::<Visibility>(entity), Some(&Visibility::Hidden));
assert!(world.get::<InheritedVisibility>(entity).is_some());
assert!(world.get::<ViewVisibility>(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::<Visibility>(entity), Some(&Visibility::Hidden));
assert!(world.get::<InheritedVisibility>(entity).is_some());
assert!(world.get::<ViewVisibility>(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::<EditorVisibility>(entity),
Some(&EditorVisibility::default())
);
assert_eq!(world.get::<Visibility>(entity), Some(&Visibility::Visible));
assert!(world.get::<InheritedVisibility>(entity).is_some());
assert!(world.get::<ViewVisibility>(entity).is_some());
}
}