diff --git a/benches/Cargo.toml b/benches/Cargo.toml index 1e55f712a7a79..ce2467b0228b0 100644 --- a/benches/Cargo.toml +++ b/benches/Cargo.toml @@ -16,7 +16,6 @@ criterion = { version = "0.5.1", features = ["html_reports"] } # Bevy crates bevy_app = { path = "../crates/bevy_app" } bevy_ecs = { path = "../crates/bevy_ecs", features = ["multi_threaded"] } -bevy_hierarchy = { path = "../crates/bevy_hierarchy" } bevy_math = { path = "../crates/bevy_math" } bevy_picking = { path = "../crates/bevy_picking", features = [ "bevy_mesh_picking_backend", diff --git a/benches/benches/bevy_ecs/entity_cloning.rs b/benches/benches/bevy_ecs/entity_cloning.rs index 80577b9a9d0b5..5558b600698cd 100644 --- a/benches/benches/bevy_ecs/entity_cloning.rs +++ b/benches/benches/bevy_ecs/entity_cloning.rs @@ -3,9 +3,9 @@ use core::hint::black_box; use benches::bench; use bevy_ecs::bundle::Bundle; use bevy_ecs::component::ComponentCloneHandler; +use bevy_ecs::hierarchy::Parent; use bevy_ecs::reflect::AppTypeRegistry; use bevy_ecs::{component::Component, world::World}; -use bevy_hierarchy::{BuildChildren, CloneEntityHierarchyExt}; use bevy_math::Mat4; use bevy_reflect::{GetTypeRegistration, Reflect}; use criterion::{criterion_group, Bencher, Criterion, Throughput}; @@ -142,8 +142,7 @@ fn bench_clone_hierarchy( for parent_id in current_hierarchy_level { for _ in 0..children { - let child_id = world.spawn(B::default()).set_parent(parent_id).id(); - + let child_id = world.spawn((B::default(), Parent(parent_id))).id(); hierarchy_level.push(child_id); } } diff --git a/benches/benches/bevy_ecs/observers/propagation.rs b/benches/benches/bevy_ecs/observers/propagation.rs index 1989c05fc62f7..b4560895c073e 100644 --- a/benches/benches/bevy_ecs/observers/propagation.rs +++ b/benches/benches/bevy_ecs/observers/propagation.rs @@ -1,10 +1,6 @@ use core::hint::black_box; -use bevy_ecs::{ - component::Component, entity::Entity, event::Event, observer::Trigger, world::World, -}; -use bevy_hierarchy::{BuildChildren, Parent}; - +use bevy_ecs::prelude::*; use criterion::Criterion; use rand::SeedableRng; use rand::{seq::IteratorRandom, Rng}; diff --git a/benches/benches/bevy_ecs/world/despawn_recursive.rs b/benches/benches/bevy_ecs/world/despawn_recursive.rs index 482086ab17444..dd1ca4325ba22 100644 --- a/benches/benches/bevy_ecs/world/despawn_recursive.rs +++ b/benches/benches/bevy_ecs/world/despawn_recursive.rs @@ -1,7 +1,4 @@ use bevy_ecs::prelude::*; -use bevy_hierarchy::despawn_with_children_recursive; -use bevy_hierarchy::BuildChildren; -use bevy_hierarchy::ChildBuild; use criterion::Criterion; use glam::*; @@ -29,7 +26,7 @@ pub fn world_despawn_recursive(criterion: &mut Criterion) { group.bench_function(format!("{}_entities", entity_count), |bencher| { bencher.iter(|| { ents.iter().for_each(|e| { - despawn_with_children_recursive(&mut world, *e, true); + world.entity_mut(*e).despawn(); }); }); }); diff --git a/crates/bevy_animation/Cargo.toml b/crates/bevy_animation/Cargo.toml index 8dfa704b8d0f5..36091c8db3922 100644 --- a/crates/bevy_animation/Cargo.toml +++ b/crates/bevy_animation/Cargo.toml @@ -25,7 +25,6 @@ bevy_time = { path = "../bevy_time", version = "0.16.0-dev" } bevy_utils = { path = "../bevy_utils", version = "0.16.0-dev" } bevy_ecs = { path = "../bevy_ecs", version = "0.16.0-dev" } bevy_transform = { path = "../bevy_transform", version = "0.16.0-dev" } -bevy_hierarchy = { path = "../bevy_hierarchy", version = "0.16.0-dev" } # other petgraph = { version = "0.6", features = ["serde-1"] } diff --git a/crates/bevy_app/src/app.rs b/crates/bevy_app/src/app.rs index 6ff5b9993dab1..e9889670bc64c 100644 --- a/crates/bevy_app/src/app.rs +++ b/crates/bevy_app/src/app.rs @@ -104,6 +104,8 @@ impl Default for App { { app.init_resource::(); app.register_type::(); + app.register_type::(); + app.register_type::(); } #[cfg(feature = "reflect_functions")] diff --git a/crates/bevy_audio/Cargo.toml b/crates/bevy_audio/Cargo.toml index 71c7efe65ba08..4d7967977e78f 100644 --- a/crates/bevy_audio/Cargo.toml +++ b/crates/bevy_audio/Cargo.toml @@ -13,7 +13,6 @@ keywords = ["bevy"] bevy_app = { path = "../bevy_app", version = "0.16.0-dev" } bevy_asset = { path = "../bevy_asset", version = "0.16.0-dev" } bevy_ecs = { path = "../bevy_ecs", version = "0.16.0-dev" } -bevy_hierarchy = { path = "../bevy_hierarchy", version = "0.16.0-dev" } bevy_math = { path = "../bevy_math", version = "0.16.0-dev" } bevy_reflect = { path = "../bevy_reflect", version = "0.16.0-dev", features = [ "bevy", diff --git a/crates/bevy_audio/src/audio_output.rs b/crates/bevy_audio/src/audio_output.rs index 90bdc38499974..c098ac8382b4d 100644 --- a/crates/bevy_audio/src/audio_output.rs +++ b/crates/bevy_audio/src/audio_output.rs @@ -4,7 +4,6 @@ use crate::{ }; use bevy_asset::{Asset, Assets}; use bevy_ecs::{prelude::*, system::SystemParam}; -use bevy_hierarchy::DespawnRecursiveExt; use bevy_math::Vec3; use bevy_transform::prelude::GlobalTransform; use rodio::{OutputStream, OutputStreamHandle, Sink, Source, SpatialSink}; @@ -253,12 +252,12 @@ pub(crate) fn cleanup_finished_audio( ) { for (entity, sink) in &query_nonspatial_despawn { if sink.sink.empty() { - commands.entity(entity).despawn_recursive(); + commands.entity(entity).despawn(); } } for (entity, sink) in &query_spatial_despawn { if sink.sink.empty() { - commands.entity(entity).despawn_recursive(); + commands.entity(entity).despawn(); } } for (entity, sink) in &query_nonspatial_remove { diff --git a/crates/bevy_dev_tools/Cargo.toml b/crates/bevy_dev_tools/Cargo.toml index 223474a0f0493..0b9618d20d9eb 100644 --- a/crates/bevy_dev_tools/Cargo.toml +++ b/crates/bevy_dev_tools/Cargo.toml @@ -18,7 +18,6 @@ bevy_asset = { path = "../bevy_asset", version = "0.16.0-dev" } bevy_color = { path = "../bevy_color", version = "0.16.0-dev" } bevy_diagnostic = { path = "../bevy_diagnostic", version = "0.16.0-dev" } bevy_ecs = { path = "../bevy_ecs", version = "0.16.0-dev" } -bevy_hierarchy = { path = "../bevy_hierarchy", version = "0.16.0-dev" } bevy_input = { path = "../bevy_input", version = "0.16.0-dev" } bevy_picking = { path = "../bevy_picking", version = "0.16.0-dev" } bevy_render = { path = "../bevy_render", version = "0.16.0-dev" } diff --git a/crates/bevy_dev_tools/src/fps_overlay.rs b/crates/bevy_dev_tools/src/fps_overlay.rs index a6368cf92d41b..a68324e235137 100644 --- a/crates/bevy_dev_tools/src/fps_overlay.rs +++ b/crates/bevy_dev_tools/src/fps_overlay.rs @@ -12,7 +12,6 @@ use bevy_ecs::{ schedule::{common_conditions::resource_changed, IntoSystemConfigs}, system::{Commands, Query, Res, Resource}, }; -use bevy_hierarchy::{BuildChildren, ChildBuild}; use bevy_render::view::Visibility; use bevy_text::{Font, TextColor, TextFont, TextSpan}; use bevy_ui::{ diff --git a/crates/bevy_ecs/macros/src/component.rs b/crates/bevy_ecs/macros/src/component.rs index e89436e42622a..44c25171c250a 100644 --- a/crates/bevy_ecs/macros/src/component.rs +++ b/crates/bevy_ecs/macros/src/component.rs @@ -9,7 +9,8 @@ use syn::{ punctuated::Punctuated, spanned::Spanned, token::{Comma, Paren}, - DeriveInput, ExprClosure, ExprPath, Ident, LitStr, Path, Result, + Data, DataStruct, DeriveInput, ExprClosure, ExprPath, Fields, Ident, LitStr, Path, Result, + Token, Visibility, }; pub fn derive_event(input: TokenStream) -> TokenStream { @@ -59,12 +60,81 @@ pub fn derive_component(input: TokenStream) -> TokenStream { Err(e) => return e.into_compile_error().into(), }; + let relationship = match derive_relationship(&ast, &attrs, &bevy_ecs_path) { + Ok(value) => value, + Err(err) => err.into_compile_error().into(), + }; + let relationship_target = match derive_relationship_target(&ast, &attrs, &bevy_ecs_path) { + Ok(value) => value, + Err(err) => err.into_compile_error().into(), + }; + let storage = storage_path(&bevy_ecs_path, attrs.storage); let on_add = hook_register_function_call(quote! {on_add}, attrs.on_add); - let on_insert = hook_register_function_call(quote! {on_insert}, attrs.on_insert); - let on_replace = hook_register_function_call(quote! {on_replace}, attrs.on_replace); - let on_remove = hook_register_function_call(quote! {on_remove}, attrs.on_remove); + let mut on_insert = hook_register_function_call(quote! {on_insert}, attrs.on_insert); + let mut on_replace = hook_register_function_call(quote! {on_replace}, attrs.on_replace); + let on_remove: Option = + hook_register_function_call(quote! {on_remove}, attrs.on_remove); + let mut on_despawn = hook_register_function_call(quote! {on_despawn}, attrs.on_despawn); + + if relationship.is_some() { + if on_insert.is_some() { + return syn::Error::new( + ast.span(), + "Custom on_insert hooks are not supported as relationships already define an on_insert hook", + ) + .into_compile_error() + .into(); + } + + on_insert = Some( + quote!(hooks.on_insert(::on_insert);), + ); + + if on_replace.is_some() { + return syn::Error::new( + ast.span(), + "Custom on_replace hooks are not supported as Relationships already define an on_replace hook", + ) + .into_compile_error() + .into(); + } + + on_replace = Some( + quote!(hooks.on_replace(::on_replace);), + ); + } + + if let Some(relationship_target) = &attrs.relationship_target { + if on_replace.is_some() { + return syn::Error::new( + ast.span(), + "Custom on_replace hooks are not supported as RelationshipTarget already defines an on_replace hook", + ) + .into_compile_error() + .into(); + } + + on_replace = Some( + quote!(hooks.on_replace(::on_replace);), + ); + + if relationship_target.despawn_descendants { + if on_despawn.is_some() { + return syn::Error::new( + ast.span(), + "Custom on_despawn hooks are not supported as this RelationshipTarget already defines an on_despawn hook, via the despawn_descendants attribute", + ) + .into_compile_error() + .into(); + } + + on_despawn = Some( + quote!(hooks.on_despawn(::on_despawn);), + ); + } + } ast.generics .make_where_clause() @@ -127,11 +197,19 @@ pub fn derive_component(input: TokenStream) -> TokenStream { let struct_name = &ast.ident; let (impl_generics, type_generics, where_clause) = &ast.generics.split_for_impl(); - let mutable_type = attrs - .immutable + let mutable_type = (attrs.immutable || relationship.is_some()) .then_some(quote! { #bevy_ecs_path::component::Immutable }) .unwrap_or(quote! { #bevy_ecs_path::component::Mutable }); + let clone_handler = if relationship_target.is_some() { + quote!(#bevy_ecs_path::component::ComponentCloneHandler::ignore()) + } else { + quote!( + use #bevy_ecs_path::component::{ComponentCloneViaClone, ComponentCloneBase}; + (&&&#bevy_ecs_path::component::ComponentCloneSpecializationWrapper::::default()).get_component_clone_handler() + ) + }; + // This puts `register_required` before `register_recursive_requires` to ensure that the constructors of _all_ top // level components are initialized first, giving them precedence over recursively defined constructors for the same component type TokenStream::from(quote! { @@ -160,14 +238,17 @@ pub fn derive_component(input: TokenStream) -> TokenStream { #on_insert #on_replace #on_remove + #on_despawn } fn get_component_clone_handler() -> #bevy_ecs_path::component::ComponentCloneHandler { - use #bevy_ecs_path::component::{ComponentCloneViaClone, ComponentCloneBase}; - (&&&#bevy_ecs_path::component::ComponentCloneSpecializationWrapper::::default()) - .get_component_clone_handler() + #clone_handler } } + + #relationship + + #relationship_target }) } @@ -202,11 +283,14 @@ pub fn document_required_components(attr: TokenStream, item: TokenStream) -> Tok pub const COMPONENT: &str = "component"; pub const STORAGE: &str = "storage"; pub const REQUIRE: &str = "require"; +pub const RELATIONSHIP: &str = "relationship"; +pub const RELATIONSHIP_TARGET: &str = "relationship_target"; pub const ON_ADD: &str = "on_add"; pub const ON_INSERT: &str = "on_insert"; pub const ON_REPLACE: &str = "on_replace"; pub const ON_REMOVE: &str = "on_remove"; +pub const ON_DESPAWN: &str = "on_despawn"; pub const IMMUTABLE: &str = "immutable"; @@ -217,6 +301,9 @@ struct Attrs { on_insert: Option, on_replace: Option, on_remove: Option, + on_despawn: Option, + relationship: Option, + relationship_target: Option, immutable: bool, } @@ -236,6 +323,15 @@ enum RequireFunc { Closure(ExprClosure), } +struct Relationship { + relationship_target: Ident, +} + +struct RelationshipTarget { + relationship: Ident, + despawn_descendants: bool, +} + // values for `storage` attribute const TABLE: &str = "Table"; const SPARSE_SET: &str = "SparseSet"; @@ -247,7 +343,10 @@ fn parse_component_attr(ast: &DeriveInput) -> Result { on_insert: None, on_replace: None, on_remove: None, + on_despawn: None, requires: None, + relationship: None, + relationship_target: None, immutable: false, }; @@ -278,6 +377,9 @@ fn parse_component_attr(ast: &DeriveInput) -> Result { } else if nested.path.is_ident(ON_REMOVE) { attrs.on_remove = Some(nested.value()?.parse::()?); Ok(()) + } else if nested.path.is_ident(ON_DESPAWN) { + attrs.on_despawn = Some(nested.value()?.parse::()?); + Ok(()) } else if nested.path.is_ident(IMMUTABLE) { attrs.immutable = true; Ok(()) @@ -301,6 +403,12 @@ fn parse_component_attr(ast: &DeriveInput) -> Result { } else { attrs.requires = Some(punctuated); } + } else if attr.path().is_ident(RELATIONSHIP) { + let relationship = attr.parse_args::()?; + attrs.relationship = Some(relationship); + } else if attr.path().is_ident(RELATIONSHIP_TARGET) { + let relationship_target = attr.parse_args::()?; + attrs.relationship_target = Some(relationship_target); } } @@ -341,3 +449,158 @@ fn hook_register_function_call( ) -> Option { function.map(|meta| quote! { hooks. #hook (#meta); }) } + +impl Parse for Relationship { + fn parse(input: syn::parse::ParseStream) -> Result { + syn::custom_keyword!(relationship_target); + input.parse::()?; + input.parse::()?; + Ok(Relationship { + relationship_target: input.parse::()?, + }) + } +} + +impl Parse for RelationshipTarget { + fn parse(input: syn::parse::ParseStream) -> Result { + let mut relationship_ident = None; + let mut despawn_descendants_exists = false; + syn::custom_keyword!(relationship); + syn::custom_keyword!(despawn_descendants); + let mut done = false; + loop { + if input.peek(relationship) { + input.parse::()?; + input.parse::()?; + relationship_ident = Some(input.parse::()?); + } else if input.peek(despawn_descendants) { + input.parse::()?; + despawn_descendants_exists = true; + } else { + done = true; + } + if input.peek(Token![,]) { + input.parse::()?; + } + if done { + break; + } + } + + let relationship = relationship_ident.ok_or_else(|| syn::Error::new(input.span(), "RelationshipTarget derive must specify a relationship via #[relationship_target(relationship = X)"))?; + Ok(RelationshipTarget { + relationship, + despawn_descendants: despawn_descendants_exists, + }) + } +} + +fn derive_relationship( + ast: &DeriveInput, + attrs: &Attrs, + bevy_ecs_path: &Path, +) -> Result> { + let Some(relationship) = &attrs.relationship else { + return Ok(None); + }; + const RELATIONSHIP_FORMAT_MESSAGE: &str = "Relationship derives must be a tuple struct with the only element being an EntityTargets type (ex: ChildOf(Entity))"; + if let Data::Struct(DataStruct { + fields: Fields::Unnamed(unnamed_fields), + struct_token, + .. + }) = &ast.data + { + if unnamed_fields.unnamed.len() != 1 { + return Err(syn::Error::new(ast.span(), RELATIONSHIP_FORMAT_MESSAGE)); + } + if unnamed_fields.unnamed.first().is_none() { + return Err(syn::Error::new( + struct_token.span(), + RELATIONSHIP_FORMAT_MESSAGE, + )); + } + } else { + return Err(syn::Error::new(ast.span(), RELATIONSHIP_FORMAT_MESSAGE)); + }; + + let struct_name = &ast.ident; + let (impl_generics, type_generics, where_clause) = &ast.generics.split_for_impl(); + + let relationship_target = &relationship.relationship_target; + + Ok(Some(quote! { + impl #impl_generics #bevy_ecs_path::relationship::Relationship for #struct_name #type_generics #where_clause { + type RelationshipTarget = #relationship_target; + + #[inline(always)] + fn get(&self) -> #bevy_ecs_path::entity::Entity { + self.0 + } + + #[inline] + fn from(entity: #bevy_ecs_path::entity::Entity) -> Self { + Self(entity) + } + } + })) +} + +fn derive_relationship_target( + ast: &DeriveInput, + attrs: &Attrs, + bevy_ecs_path: &Path, +) -> Result> { + let Some(relationship_target) = &attrs.relationship_target else { + return Ok(None); + }; + + const RELATIONSHIP_TARGET_FORMAT_MESSAGE: &str = "RelationshipTarget derives must be a tuple struct with the first element being a private RelationshipSourceCollection (ex: Children(Vec))"; + let collection = if let Data::Struct(DataStruct { + fields: Fields::Unnamed(unnamed_fields), + struct_token, + .. + }) = &ast.data + { + if let Some(first) = unnamed_fields.unnamed.first() { + if first.vis != Visibility::Inherited { + return Err(syn::Error::new(first.span(), "The collection in RelationshipTarget must be private to prevent users from directly mutating it, which could invalidate the correctness of relationships.")); + } + first.ty.clone() + } else { + return Err(syn::Error::new( + struct_token.span(), + RELATIONSHIP_TARGET_FORMAT_MESSAGE, + )); + } + } else { + return Err(syn::Error::new( + ast.span(), + RELATIONSHIP_TARGET_FORMAT_MESSAGE, + )); + }; + + let relationship = &relationship_target.relationship; + let struct_name = &ast.ident; + let (impl_generics, type_generics, where_clause) = &ast.generics.split_for_impl(); + Ok(Some(quote! { + impl #impl_generics #bevy_ecs_path::relationship::RelationshipTarget for #struct_name #type_generics #where_clause { + type Relationship = #relationship; + type Collection = #collection; + + #[inline] + fn collection(&self) -> &Self::Collection { + &self.0 + } + + #[inline] + fn collection_mut_risky(&mut self) -> &mut Self::Collection { + &mut self.0 + } + + #[inline] + fn from_collection_risky(collection: Self::Collection) -> Self { + Self(collection) + } + } + })) +} diff --git a/crates/bevy_ecs/macros/src/lib.rs b/crates/bevy_ecs/macros/src/lib.rs index 0893e721aef8e..8d8f19ca9a61e 100644 --- a/crates/bevy_ecs/macros/src/lib.rs +++ b/crates/bevy_ecs/macros/src/lib.rs @@ -589,7 +589,7 @@ pub fn derive_resource(input: TokenStream) -> TokenStream { component::derive_resource(input) } -#[proc_macro_derive(Component, attributes(component))] +#[proc_macro_derive(Component, attributes(component, relationship, relationship_target))] pub fn derive_component(input: TokenStream) -> TokenStream { component::derive_component(input) } diff --git a/crates/bevy_ecs/src/archetype.rs b/crates/bevy_ecs/src/archetype.rs index e3b8b8dac545d..cb72db3067650 100644 --- a/crates/bevy_ecs/src/archetype.rs +++ b/crates/bevy_ecs/src/archetype.rs @@ -354,10 +354,12 @@ bitflags::bitflags! { const ON_INSERT_HOOK = (1 << 1); const ON_REPLACE_HOOK = (1 << 2); const ON_REMOVE_HOOK = (1 << 3); - const ON_ADD_OBSERVER = (1 << 4); - const ON_INSERT_OBSERVER = (1 << 5); - const ON_REPLACE_OBSERVER = (1 << 6); - const ON_REMOVE_OBSERVER = (1 << 7); + const ON_DESPAWN_HOOK = (1 << 4); + const ON_ADD_OBSERVER = (1 << 5); + const ON_INSERT_OBSERVER = (1 << 6); + const ON_REPLACE_OBSERVER = (1 << 7); + const ON_REMOVE_OBSERVER = (1 << 8); + const ON_DESPAWN_OBSERVER = (1 << 9); } } @@ -672,6 +674,12 @@ impl Archetype { self.flags().contains(ArchetypeFlags::ON_REMOVE_HOOK) } + /// Returns true if any of the components in this archetype have `on_despawn` hooks + #[inline] + pub fn has_despawn_hook(&self) -> bool { + self.flags().contains(ArchetypeFlags::ON_DESPAWN_HOOK) + } + /// Returns true if any of the components in this archetype have at least one [`OnAdd`] observer /// /// [`OnAdd`]: crate::world::OnAdd @@ -703,6 +711,14 @@ impl Archetype { pub fn has_remove_observer(&self) -> bool { self.flags().contains(ArchetypeFlags::ON_REMOVE_OBSERVER) } + + /// Returns true if any of the components in this archetype have at least one [`OnDespawn`] observer + /// + /// [`OnDespawn`]: crate::world::OnDespawn + #[inline] + pub fn has_despawn_observer(&self) -> bool { + self.flags().contains(ArchetypeFlags::ON_DESPAWN_OBSERVER) + } } /// The next [`ArchetypeId`] in an [`Archetypes`] collection. diff --git a/crates/bevy_ecs/src/bundle.rs b/crates/bevy_ecs/src/bundle.rs index 6b4d9da2811bb..d18d6bdd2a68a 100644 --- a/crates/bevy_ecs/src/bundle.rs +++ b/crates/bevy_ecs/src/bundle.rs @@ -1044,7 +1044,6 @@ impl<'w> BundleInserter<'w> { ) -> EntityLocation { let bundle_info = self.bundle_info.as_ref(); let archetype_after_insert = self.archetype_after_insert.as_ref(); - let table = self.table.as_mut(); let archetype = self.archetype.as_ref(); // SAFETY: All components in the bundle are guaranteed to exist in the World @@ -1069,6 +1068,8 @@ impl<'w> BundleInserter<'w> { } } + let table = self.table.as_mut(); + // SAFETY: Archetype gets borrowed when running the on_replace observers above, // so this reference can only be promoted from shared to &mut down here, after they have been ran let archetype = self.archetype.as_mut(); diff --git a/crates/bevy_ecs/src/component.rs b/crates/bevy_ecs/src/component.rs index 66d5db98f8fcb..6cab46f8e25a4 100644 --- a/crates/bevy_ecs/src/component.rs +++ b/crates/bevy_ecs/src/component.rs @@ -563,6 +563,7 @@ pub struct ComponentHooks { pub(crate) on_insert: Option, pub(crate) on_replace: Option, pub(crate) on_remove: Option, + pub(crate) on_despawn: Option, } impl ComponentHooks { @@ -629,6 +630,16 @@ impl ComponentHooks { .expect("Component already has an on_remove hook") } + /// Register a [`ComponentHook`] that will be run for each component on an entity when it is despawned. + /// + /// # Panics + /// + /// Will panic if the component already has an `on_despawn` hook + pub fn on_despawn(&mut self, hook: ComponentHook) -> &mut Self { + self.try_on_despawn(hook) + .expect("Component already has an on_despawn hook") + } + /// Attempt to register a [`ComponentHook`] that will be run when this component is added to an entity. /// /// This is a fallible version of [`Self::on_add`]. @@ -680,6 +691,19 @@ impl ComponentHooks { self.on_remove = Some(hook); Some(self) } + + /// Attempt to register a [`ComponentHook`] that will be run for each component on an entity when it is despawned. + /// + /// This is a fallible version of [`Self::on_despawn`]. + /// + /// Returns `None` if the component already has an `on_despawn` hook. + pub fn try_on_despawn(&mut self, hook: ComponentHook) -> Option<&mut Self> { + if self.on_despawn.is_some() { + return None; + } + self.on_despawn = Some(hook); + Some(self) + } } /// Stores metadata for a type of component or resource stored in a specific [`World`]. @@ -775,6 +799,9 @@ impl ComponentInfo { if self.hooks().on_remove.is_some() { flags.insert(ArchetypeFlags::ON_REMOVE_HOOK); } + if self.hooks().on_despawn.is_some() { + flags.insert(ArchetypeFlags::ON_DESPAWN_HOOK); + } } /// Provides a reference to the collection of hooks associated with this [`Component`] diff --git a/crates/bevy_ecs/src/entity/clone_entities.rs b/crates/bevy_ecs/src/entity/clone_entities.rs index 2b51c1f937b34..e209df4ac3649 100644 --- a/crates/bevy_ecs/src/entity/clone_entities.rs +++ b/crates/bevy_ecs/src/entity/clone_entities.rs @@ -18,8 +18,9 @@ use crate::{ bundle::Bundle, component::{Component, ComponentCloneHandler, ComponentId, ComponentInfo, Components}, entity::Entity, + hierarchy::{Children, Parent}, query::DebugCheckedUnwrap, - world::World, + world::{DeferredWorld, World}, }; /// Context for component clone handlers. @@ -621,6 +622,29 @@ impl<'w> EntityCloneBuilder<'w> { self } + /// Sets the option to recursively clone entities. + /// When set to true all children will be cloned with the same options as the parent. + pub fn recursive(&mut self, recursive: bool) -> &mut Self { + if recursive { + self.override_component_clone_handler::( + ComponentCloneHandler::custom_handler(component_clone_children), + ) + } else { + self.remove_component_clone_handler_override::() + } + } + + /// Sets the option to add cloned entity as a child to the parent entity. + pub fn as_child(&mut self, as_child: bool) -> &mut Self { + if as_child { + self.override_component_clone_handler::(ComponentCloneHandler::custom_handler( + component_clone_parent, + )) + } else { + self.remove_component_clone_handler_override::() + } + } + /// Helper function that allows a component through the filter. fn filter_allow(&mut self, id: ComponentId) { if self.filter_allows_components { @@ -662,6 +686,34 @@ impl<'w> EntityCloneBuilder<'w> { } } +/// Clone handler for the [`Children`] component. Allows to clone the entity recursively. +fn component_clone_children(world: &mut DeferredWorld, ctx: &mut ComponentCloneCtx) { + let children = ctx + .read_source_component::() + .expect("Source entity must have Children component") + .iter(); + let parent = ctx.target(); + for child in children { + let child_clone = world.commands().spawn_empty().id(); + let mut clone_entity = ctx + .entity_cloner() + .with_source_and_target(*child, child_clone); + world.commands().queue(move |world: &mut World| { + clone_entity.clone_entity(world); + world.entity_mut(child_clone).insert(Parent(parent)); + }); + } +} + +/// Clone handler for the [`Parent`] component. Allows to add clone as a child to the parent entity. +fn component_clone_parent(world: &mut DeferredWorld, ctx: &mut ComponentCloneCtx) { + let parent = ctx + .read_source_component::() + .map(|p| p.0) + .expect("Source entity must have Parent component"); + world.commands().entity(ctx.target()).insert(Parent(parent)); +} + #[cfg(test)] mod tests { use super::ComponentCloneCtx; diff --git a/crates/bevy_ecs/src/hierarchy.rs b/crates/bevy_ecs/src/hierarchy.rs new file mode 100644 index 0000000000000..6e495a116b7f9 --- /dev/null +++ b/crates/bevy_ecs/src/hierarchy.rs @@ -0,0 +1,454 @@ +//! The canonical "parent-child" [`Relationship`] for entities, driven by +//! the [`Parent`] [`Relationship`] and the [`Children`] [`RelationshipTarget`]. +//! +//! See [`Parent`] for a full description of the relationship and how to use it. +//! +//! [`Relationship`]: crate::relationship::Relationship +//! [`RelationshipTarget`]: crate::relationship::RelationshipTarget + +#[cfg(feature = "bevy_reflect")] +use crate::reflect::{ + ReflectComponent, ReflectFromWorld, ReflectMapEntities, ReflectVisitEntities, + ReflectVisitEntitiesMut, +}; +use crate::{ + self as bevy_ecs, + bundle::Bundle, + component::{Component, ComponentId}, + entity::{Entity, VisitEntities}, + relationship::{RelatedSpawner, RelatedSpawnerCommands}, + system::EntityCommands, + world::{DeferredWorld, EntityWorldMut, FromWorld, World}, +}; +use alloc::{format, string::String, vec::Vec}; +use bevy_ecs_macros::VisitEntitiesMut; +use core::ops::Deref; +use core::slice; +use disqualified::ShortName; +use log::warn; + +/// A [`Relationship`](crate::relationship::Relationship) component that creates the canonical +/// "parent / child" hierarchy. This is the "source of truth" component, and it pairs with +/// the [`Children`] [`RelationshipTarget`](crate::relationship::RelationshipTarget). +/// +/// This relationship should be used for things like: +/// +/// 1. Organizing entities in a scene +/// 2. Propagating configuration or data inherited from a parent, such as "visibility" or "world-space global transforms". +/// 3. Ensuring a hierarchy is despawned when an entity is despawned. +/// 4. +/// +/// [`Parent`] contains a single "target" [`Entity`]. When [`Parent`] is inserted on a "source" entity, +/// the "target" entity will automatically (and immediately, via a component hook) have a [`Children`] +/// component inserted, and the "source" entity will be added to that [`Children`] instance. +/// +/// If the [`Parent`] component is replaced with a different "target" entity, the old target's [`Children`] +/// will be automatically (and immediately, via a component hook) be updated to reflect that change. +/// +/// Likewise, when the [`Parent`] component is removed, the "source" entity will be removed from the old +/// target's [`Children`]. If this results in [`Children`] being empty, [`Children`] will be automatically removed. +/// +/// When a parent is despawned, all children (and their descendants) will _also_ be despawned. +/// +/// You can create parent-child relationships in a variety of ways. The most direct way is to insert a [`Parent`] component: +/// +/// ``` +/// # use bevy_ecs::prelude::*; +/// # let mut world = World::new(); +/// let root = world.spawn_empty().id(); +/// let child1 = world.spawn(Parent(root)).id(); +/// let child2 = world.spawn(Parent(root)).id(); +/// let grandchild = world.spawn(Parent(child1)).id(); +/// +/// assert_eq!(&**world.entity(root).get::().unwrap(), &[child1, child2]); +/// assert_eq!(&**world.entity(child1).get::().unwrap(), &[grandchild]); +/// +/// world.entity_mut(child2).remove::(); +/// assert_eq!(&**world.entity(root).get::().unwrap(), &[child1]); +/// +/// world.entity_mut(root).despawn(); +/// assert!(world.get_entity(root).is_err()); +/// assert!(world.get_entity(child1).is_err()); +/// assert!(world.get_entity(grandchild).is_err()); +/// ``` +/// +/// However if you are spawning many children, you might want to use the [`EntityWorldMut::with_children`] helper instead: +/// +/// ``` +/// # use bevy_ecs::prelude::*; +/// # let mut world = World::new(); +/// let mut child1 = Entity::PLACEHOLDER; +/// let mut child2 = Entity::PLACEHOLDER; +/// let mut grandchild = Entity::PLACEHOLDER; +/// let root = world.spawn_empty().with_children(|p| { +/// child1 = p.spawn_empty().with_children(|p| { +/// grandchild = p.spawn_empty().id(); +/// }).id(); +/// child2 = p.spawn_empty().id(); +/// }).id(); +/// +/// assert_eq!(&**world.entity(root).get::().unwrap(), &[child1, child2]); +/// assert_eq!(&**world.entity(child1).get::().unwrap(), &[grandchild]); +/// ``` +#[derive(Component, Clone, VisitEntities, VisitEntitiesMut, PartialEq, Eq, Debug)] +#[cfg_attr(feature = "bevy_reflect", derive(bevy_reflect::Reflect))] +#[cfg_attr( + feature = "bevy_reflect", + reflect( + Component, + MapEntities, + VisitEntities, + VisitEntitiesMut, + PartialEq, + Debug, + FromWorld + ) +)] +#[relationship(relationship_target = Children)] +pub struct Parent(pub Entity); + +impl Parent { + /// Returns the "target" entity. + pub fn get(&self) -> Entity { + self.0 + } +} + +impl Deref for Parent { + type Target = Entity; + + #[inline] + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +// TODO: We need to impl either FromWorld or Default so Parent can be registered as Reflect. +// This is because Reflect deserialize by creating an instance and apply a patch on top. +// However Parent should only ever be set with a real user-defined entity. Its worth looking into +// better ways to handle cases like this. +impl FromWorld for Parent { + #[inline(always)] + fn from_world(_world: &mut World) -> Self { + Parent(Entity::PLACEHOLDER) + } +} + +/// A [`RelationshipTarget`](crate::relationship::RelationshipTarget) collection component that is populated +/// with entities that "target" this entity with the [`Parent`] [`Relationship`](crate::relationship::Relationship) component. +/// +/// Together, these components form the "canonical parent-child hierarchy". See the [`Parent`] component for all full +/// description of this relationship and instructions on how to use it. +#[derive(Component, Default, VisitEntitiesMut, Debug, PartialEq, Eq)] +#[relationship_target(relationship = Parent, despawn_descendants)] +#[cfg_attr(feature = "bevy_reflect", derive(bevy_reflect::Reflect))] +#[cfg_attr( + feature = "bevy_reflect", + reflect(Component, MapEntities, VisitEntities, VisitEntitiesMut, FromWorld) +)] +pub struct Children(Vec); + +impl<'a> IntoIterator for &'a Children { + type Item = ::Item; + + type IntoIter = slice::Iter<'a, Entity>; + + #[inline(always)] + fn into_iter(self) -> Self::IntoIter { + self.0.iter() + } +} + +impl Deref for Children { + type Target = [Entity]; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +/// A type alias over [`RelatedSpawner`] used to spawn child entities containing a [`Parent`] relationship. +pub type ChildSpawner<'w> = RelatedSpawner<'w, Parent>; + +/// A type alias over [`RelatedSpawnerCommands`] used to spawn child entities containing a [`Parent`] relationship. +pub type ChildSpawnerCommands<'w> = RelatedSpawnerCommands<'w, Parent>; + +impl<'w> EntityWorldMut<'w> { + /// Spawns children of this entity (with a [`Parent`] relationship) by taking a function that operates on a [`ChildSpawner`]. + pub fn with_children(&mut self, func: impl FnOnce(&mut ChildSpawner)) -> &mut Self { + self.with_related(func); + self + } + + /// Adds the given children to this entity + pub fn add_children(&mut self, children: &[Entity]) -> &mut Self { + self.add_related::(children) + } + + /// Adds the given child to this entity + pub fn add_child(&mut self, child: Entity) -> &mut Self { + self.add_related::(&[child]) + } + + /// Spawns the passed bundle and adds it to this entity as a child. + /// + /// For efficient spawning of multiple children, use [`with_children`]. + /// + /// [`with_children`]: EntityWorldMut::with_children + pub fn with_child(&mut self, bundle: impl Bundle) -> &mut Self { + let id = self.id(); + self.world_scope(|world| { + world.spawn((bundle, Parent(id))); + }); + self + } + + /// Removes the [`Parent`] component, if it exists. + #[deprecated(since = "0.16.0", note = "Use entity_mut.remove::()")] + pub fn remove_parent(&mut self) -> &mut Self { + self.remove::(); + self + } + + /// Inserts the [`Parent`] component with the given `parent` entity, if it exists. + #[deprecated(since = "0.16.0", note = "Use entity_mut.insert(Parent(entity))")] + pub fn set_parent(&mut self, parent: Entity) -> &mut Self { + self.insert(Parent(parent)); + self + } +} + +impl<'a> EntityCommands<'a> { + /// Spawns children of this entity (with a [`Parent`] relationship) by taking a function that operates on a [`ChildSpawner`]. + pub fn with_children( + &mut self, + func: impl FnOnce(&mut RelatedSpawnerCommands), + ) -> &mut Self { + self.with_related(func); + self + } + + /// Adds the given children to this entity + pub fn add_children(&mut self, children: &[Entity]) -> &mut Self { + self.add_related::(children) + } + + /// Adds the given child to this entity + pub fn add_child(&mut self, child: Entity) -> &mut Self { + self.add_related::(&[child]) + } + + /// Spawns the passed bundle and adds it to this entity as a child. + /// + /// For efficient spawning of multiple children, use [`with_children`]. + /// + /// [`with_children`]: EntityCommands::with_children + pub fn with_child(&mut self, bundle: impl Bundle) -> &mut Self { + let id = self.id(); + self.commands.spawn((bundle, Parent(id))); + self + } + + /// Removes the [`Parent`] component, if it exists. + #[deprecated(since = "0.16.0", note = "Use entity_commands.remove::()")] + pub fn remove_parent(&mut self) -> &mut Self { + self.remove::(); + self + } + + /// Inserts the [`Parent`] component with the given `parent` entity, if it exists. + #[deprecated(since = "0.16.0", note = "Use entity_commands.insert(Parent(entity))")] + pub fn set_parent(&mut self, parent: Entity) -> &mut Self { + self.insert(Parent(parent)); + self + } +} + +/// An `on_insert` component hook that when run, will validate that the parent of a given entity +/// contains component `C`. This will print a warning if the parent does not contain `C`. +pub fn validate_parent_has_component( + world: DeferredWorld, + entity: Entity, + _: ComponentId, +) { + let entity_ref = world.entity(entity); + let Some(child_of) = entity_ref.get::() else { + return; + }; + if !world + .get_entity(child_of.get()) + .is_ok_and(|e| e.contains::()) + { + // TODO: print name here once Name lives in bevy_ecs + let name: Option = None; + warn!( + "warning[B0004]: {name} with the {ty_name} component has a parent without {ty_name}.\n\ + This will cause inconsistent behaviors! See: https://bevyengine.org/learn/errors/b0004", + ty_name = ShortName::of::(), + name = name.map_or_else( + || format!("Entity {}", entity), + |s| format!("The {s} entity") + ), + ); + } +} + +#[cfg(test)] +mod tests { + use crate::{ + entity::Entity, + hierarchy::{Children, Parent}, + relationship::RelationshipTarget, + world::World, + }; + use alloc::{vec, vec::Vec}; + + #[derive(PartialEq, Eq, Debug)] + struct Node { + entity: Entity, + children: Vec, + } + + impl Node { + fn new(entity: Entity) -> Self { + Self { + entity, + children: Vec::new(), + } + } + + fn new_with(entity: Entity, children: Vec) -> Self { + Self { entity, children } + } + } + + fn get_hierarchy(world: &World, entity: Entity) -> Node { + Node { + entity, + children: world + .entity(entity) + .get::() + .map_or_else(Default::default, |c| { + c.iter().map(|e| get_hierarchy(world, e)).collect() + }), + } + } + + #[test] + fn hierarchy() { + let mut world = World::new(); + let root = world.spawn_empty().id(); + let child1 = world.spawn(Parent(root)).id(); + let grandchild = world.spawn(Parent(child1)).id(); + let child2 = world.spawn(Parent(root)).id(); + + // Spawn + let hierarchy = get_hierarchy(&world, root); + assert_eq!( + hierarchy, + Node::new_with( + root, + vec![ + Node::new_with(child1, vec![Node::new(grandchild)]), + Node::new(child2) + ] + ) + ); + + // Removal + world.entity_mut(child1).remove::(); + let hierarchy = get_hierarchy(&world, root); + assert_eq!(hierarchy, Node::new_with(root, vec![Node::new(child2)])); + + // Insert + world.entity_mut(child1).insert(Parent(root)); + let hierarchy = get_hierarchy(&world, root); + assert_eq!( + hierarchy, + Node::new_with( + root, + vec![ + Node::new(child2), + Node::new_with(child1, vec![Node::new(grandchild)]) + ] + ) + ); + + // Recursive Despawn + world.entity_mut(root).despawn(); + assert!(world.get_entity(root).is_err()); + assert!(world.get_entity(child1).is_err()); + assert!(world.get_entity(child2).is_err()); + assert!(world.get_entity(grandchild).is_err()); + } + + #[test] + fn with_children() { + let mut world = World::new(); + let mut child1 = Entity::PLACEHOLDER; + let mut child2 = Entity::PLACEHOLDER; + let root = world + .spawn_empty() + .with_children(|p| { + child1 = p.spawn_empty().id(); + child2 = p.spawn_empty().id(); + }) + .id(); + + let hierarchy = get_hierarchy(&world, root); + assert_eq!( + hierarchy, + Node::new_with(root, vec![Node::new(child1), Node::new(child2)]) + ); + } + + #[test] + fn add_children() { + let mut world = World::new(); + let child1 = world.spawn_empty().id(); + let child2 = world.spawn_empty().id(); + let root = world.spawn_empty().add_children(&[child1, child2]).id(); + + let hierarchy = get_hierarchy(&world, root); + assert_eq!( + hierarchy, + Node::new_with(root, vec![Node::new(child1), Node::new(child2)]) + ); + } + + #[test] + fn self_parenting_invalid() { + let mut world = World::new(); + let id = world.spawn_empty().id(); + world.entity_mut(id).insert(Parent(id)); + assert!( + world.entity(id).get::().is_none(), + "invalid Parent relationships should self-remove" + ); + } + + #[test] + fn missing_parent_invalid() { + let mut world = World::new(); + let parent = world.spawn_empty().id(); + world.entity_mut(parent).despawn(); + let id = world.spawn(Parent(parent)).id(); + assert!( + world.entity(id).get::().is_none(), + "invalid Parent relationships should self-remove" + ); + } + + #[test] + fn reinsert_same_parent() { + let mut world = World::new(); + let parent = world.spawn_empty().id(); + let id = world.spawn(Parent(parent)).id(); + world.entity_mut(id).insert(Parent(parent)); + assert_eq!( + Some(&Parent(parent)), + world.entity(id).get::(), + "Parent should still be there" + ); + } +} diff --git a/crates/bevy_ecs/src/lib.rs b/crates/bevy_ecs/src/lib.rs index 606d1b9e9113f..f6b9bba576871 100644 --- a/crates/bevy_ecs/src/lib.rs +++ b/crates/bevy_ecs/src/lib.rs @@ -40,6 +40,7 @@ pub mod change_detection; pub mod component; pub mod entity; pub mod event; +pub mod hierarchy; pub mod identifier; pub mod intern; pub mod label; @@ -48,6 +49,7 @@ pub mod observer; pub mod query; #[cfg(feature = "bevy_reflect")] pub mod reflect; +pub mod relationship; pub mod removal_detection; pub mod result; pub mod schedule; @@ -73,6 +75,7 @@ pub mod prelude { component::{require, Component}, entity::{Entity, EntityBorrow, EntityMapper}, event::{Event, EventMutator, EventReader, EventWriter, Events}, + hierarchy::{ChildSpawner, ChildSpawnerCommands, Children, Parent}, name::{Name, NameOrEntity}, observer::{CloneEntityWithObserversExt, Observer, Trigger}, query::{Added, AnyOf, Changed, Has, Or, QueryBuilder, QueryState, With, Without}, diff --git a/crates/bevy_ecs/src/observer/mod.rs b/crates/bevy_ecs/src/observer/mod.rs index 36c74a3b9825b..9f147dd91b829 100644 --- a/crates/bevy_ecs/src/observer/mod.rs +++ b/crates/bevy_ecs/src/observer/mod.rs @@ -351,6 +351,7 @@ pub struct Observers { on_insert: CachedObservers, on_replace: CachedObservers, on_remove: CachedObservers, + on_despawn: CachedObservers, // Map from trigger type to set of observers cache: HashMap, } @@ -362,6 +363,7 @@ impl Observers { ON_INSERT => &mut self.on_insert, ON_REPLACE => &mut self.on_replace, ON_REMOVE => &mut self.on_remove, + ON_DESPAWN => &mut self.on_despawn, _ => self.cache.entry(event_type).or_default(), } } @@ -372,6 +374,7 @@ impl Observers { ON_INSERT => Some(&self.on_insert), ON_REPLACE => Some(&self.on_replace), ON_REMOVE => Some(&self.on_remove), + ON_DESPAWN => Some(&self.on_despawn), _ => self.cache.get(&event_type), } } @@ -446,6 +449,7 @@ impl Observers { ON_INSERT => Some(ArchetypeFlags::ON_INSERT_OBSERVER), ON_REPLACE => Some(ArchetypeFlags::ON_REPLACE_OBSERVER), ON_REMOVE => Some(ArchetypeFlags::ON_REMOVE_OBSERVER), + ON_DESPAWN => Some(ArchetypeFlags::ON_DESPAWN_OBSERVER), _ => None, } } @@ -482,6 +486,14 @@ impl Observers { { flags.insert(ArchetypeFlags::ON_REMOVE_OBSERVER); } + + if self + .on_despawn + .component_observers + .contains_key(&component_id) + { + flags.insert(ArchetypeFlags::ON_DESPAWN_OBSERVER); + } } } @@ -1463,6 +1475,7 @@ mod tests { } #[test] + #[should_panic] fn observer_invalid_params() { #[derive(Resource)] struct ResA; @@ -1476,8 +1489,6 @@ mod tests { commands.insert_resource(ResB); }); world.trigger(EventA); - - assert!(world.get_resource::().is_none()); } #[test] diff --git a/crates/bevy_ecs/src/reflect/component.rs b/crates/bevy_ecs/src/reflect/component.rs index 7778f16451a4a..6af8e34a31304 100644 --- a/crates/bevy_ecs/src/reflect/component.rs +++ b/crates/bevy_ecs/src/reflect/component.rs @@ -301,14 +301,16 @@ impl FromType for ReflectComponent { component.apply(reflected_component); }, apply_or_insert: |entity, reflected_component, registry| { - if !C::Mutability::MUTABLE { - let name = ShortName::of::(); - panic!("Cannot call `ReflectComponent::apply_or_insert` on component {name}. It is immutable, and cannot modified through reflection"); - } - - // SAFETY: guard ensures `C` is a mutable component - if let Some(mut component) = unsafe { entity.get_mut_assume_mutable::() } { - component.apply(reflected_component.as_partial_reflect()); + if C::Mutability::MUTABLE { + // SAFETY: guard ensures `C` is a mutable component + if let Some(mut component) = unsafe { entity.get_mut_assume_mutable::() } { + component.apply(reflected_component.as_partial_reflect()); + } else { + let component = entity.world_scope(|world| { + from_reflect_with_fallback::(reflected_component, world, registry) + }); + entity.insert(component); + } } else { let component = entity.world_scope(|world| { from_reflect_with_fallback::(reflected_component, world, registry) diff --git a/crates/bevy_ecs/src/relationship/mod.rs b/crates/bevy_ecs/src/relationship/mod.rs new file mode 100644 index 0000000000000..54105c0d7391d --- /dev/null +++ b/crates/bevy_ecs/src/relationship/mod.rs @@ -0,0 +1,253 @@ +//! This module provides functionality to link entities to each other using specialized components called "relationships". See the [`Relationship`] trait for more info. + +mod related_methods; +mod relationship_query; +mod relationship_source_collection; + +pub use related_methods::*; +pub use relationship_query::*; +pub use relationship_source_collection::*; + +use crate::{ + component::{Component, ComponentId, Mutable}, + entity::Entity, + system::{ + command::HandleError, + entity_command::{self, CommandWithEntity}, + error_handler, + }, + world::{DeferredWorld, EntityWorldMut}, +}; +use log::warn; + +/// A [`Component`] on a "source" [`Entity`] that references another target [`Entity`], creating a "relationship" between them. Every [`Relationship`] +/// has a corresponding [`RelationshipTarget`] type (and vice-versa), which exists on the "target" entity of a relationship and contains the list of all +/// "source" entities that relate to the given "target" +/// +/// The [`Relationship`] component is the "source of truth" and the [`RelationshipTarget`] component reflects that source of truth. When a [`Relationship`] +/// component is inserted on an [`Entity`], the corresponding [`RelationshipTarget`] component is immediately inserted on the target component if it does +/// not already exist, and the "source" entity is automatically added to the [`RelationshipTarget`] collection (this is done via "component hooks"). +/// +/// A common example of a [`Relationship`] is the parent / child relationship. Bevy ECS includes a canonical form of this via the [`Parent`](crate::hierarchy::Parent) +/// [`Relationship`] and the [`Children`](crate::hierarchy::Children) [`RelationshipTarget`]. +/// +/// [`Relationship`] and [`RelationshipTarget`] should always be derived via the [`Component`] trait to ensure the hooks are set up properly. +/// +/// ``` +/// # use bevy_ecs::component::Component; +/// # use bevy_ecs::entity::Entity; +/// #[derive(Component)] +/// #[relationship(relationship_target = Children)] +/// pub struct Parent(pub Entity); +/// +/// #[derive(Component)] +/// #[relationship_target(relationship = Parent)] +/// pub struct Children(Vec); +/// ``` +/// +/// When deriving [`RelationshipTarget`] you can specify the `#[relationship_target(despawn_descendants)]` attribute to +/// automatically despawn entities stored in an entity's [`RelationshipTarget`] when that entity is despawned: +/// +/// ``` +/// # use bevy_ecs::component::Component; +/// # use bevy_ecs::entity::Entity; +/// #[derive(Component)] +/// #[relationship(relationship_target = Children)] +/// pub struct Parent(pub Entity); +/// +/// #[derive(Component)] +/// #[relationship_target(relationship = Parent, despawn_descendants)] +/// pub struct Children(Vec); +/// ``` +pub trait Relationship: Component + Sized { + /// The [`Component`] added to the "target" entities of this [`Relationship`], which contains the list of all "source" + /// entities that relate to the "target". + type RelationshipTarget: RelationshipTarget; + + /// Gets the [`Entity`] ID of the related entity. + fn get(&self) -> Entity; + + /// Creates this [`Relationship`] from the given `entity`. + fn from(entity: Entity) -> Self; + + /// The `on_insert` component hook that maintains the [`Relationship`] / [`RelationshipTarget`] connection. + fn on_insert(mut world: DeferredWorld, entity: Entity, _: ComponentId) { + let target_entity = world.entity(entity).get::().unwrap().get(); + if target_entity == entity { + warn!( + "The {}({target_entity:?}) relationship on entity {entity:?} points to itself. The invalid {} relationship has been removed.", + core::any::type_name::(), + core::any::type_name::() + ); + world.commands().entity(entity).remove::(); + } + if let Ok(mut target_entity_mut) = world.get_entity_mut(target_entity) { + if let Some(mut relationship_target) = + target_entity_mut.get_mut::() + { + relationship_target.collection_mut_risky().add(entity); + } else { + let mut target = ::with_capacity(1); + target.collection_mut_risky().add(entity); + world.commands().entity(target_entity).insert(target); + } + } else { + warn!( + "The {}({target_entity:?}) relationship on entity {entity:?} relates to an entity that does not exist. The invalid {} relationship has been removed.", + core::any::type_name::(), + core::any::type_name::() + ); + world.commands().entity(entity).remove::(); + } + } + + /// The `on_replace` component hook that maintains the [`Relationship`] / [`RelationshipTarget`] connection. + // note: think of this as "on_drop" + fn on_replace(mut world: DeferredWorld, entity: Entity, _: ComponentId) { + let target_entity = world.entity(entity).get::().unwrap().get(); + if let Ok(mut target_entity_mut) = world.get_entity_mut(target_entity) { + if let Some(mut relationship_target) = + target_entity_mut.get_mut::() + { + relationship_target.collection_mut_risky().remove(entity); + if relationship_target.len() == 0 { + if let Some(mut entity) = world.commands().get_entity(target_entity) { + // this "remove" operation must check emptiness because in the event that an identical + // relationship is inserted on top, this despawn would result in the removal of that identical + // relationship ... not what we want! + entity.queue(|mut entity: EntityWorldMut| { + if entity + .get::() + .is_some_and(RelationshipTarget::is_empty) + { + entity.remove::(); + } + }); + } + } + } + } + } +} + +/// A [`Component`] containing the collection of entities that relate to this [`Entity`] via the associated `Relationship` type. +/// See the [`Relationship`] documentation for more information. +pub trait RelationshipTarget: Component + Sized { + /// The [`Relationship`] that populates this [`RelationshipTarget`] collection. + type Relationship: Relationship; + /// The collection type that stores the "source" entities for this [`RelationshipTarget`] component. + type Collection: RelationshipSourceCollection; + + /// Returns a reference to the stored [`RelationshipTarget::Collection`]. + fn collection(&self) -> &Self::Collection; + /// Returns a mutable reference to the stored [`RelationshipTarget::Collection`]. + /// + /// # Warning + /// This should generally not be called by user code, as modifying the internal collection could invalidate the relationship. + fn collection_mut_risky(&mut self) -> &mut Self::Collection; + + /// Creates a new [`RelationshipTarget`] from the given [`RelationshipTarget::Collection`]. + /// + /// # Warning + /// This should generally not be called by user code, as constructing the internal collection could invalidate the relationship. + fn from_collection_risky(collection: Self::Collection) -> Self; + + /// The `on_replace` component hook that maintains the [`Relationship`] / [`RelationshipTarget`] connection. + // note: think of this as "on_drop" + fn on_replace(mut world: DeferredWorld, entity: Entity, _: ComponentId) { + // NOTE: this unsafe code is an optimization. We could make this safe, but it would require + // copying the RelationshipTarget collection + // SAFETY: This only reads the Self component and queues Remove commands + unsafe { + let world = world.as_unsafe_world_cell(); + let relationship_target = world.get_entity(entity).unwrap().get::().unwrap(); + let mut commands = world.get_raw_command_queue(); + for source_entity in relationship_target.iter() { + if world.get_entity(source_entity).is_some() { + commands.push( + entity_command::remove::() + .with_entity(source_entity) + .handle_error_with(error_handler::silent()), + ); + } else { + warn!("Tried to despawn non-existent entity {}", source_entity); + } + } + } + } + + /// The `on_despawn` component hook that despawns entities stored in an entity's [`RelationshipTarget`] when + /// that entity is despawned. + // note: think of this as "on_drop" + fn on_despawn(mut world: DeferredWorld, entity: Entity, _: ComponentId) { + // NOTE: this unsafe code is an optimization. We could make this safe, but it would require + // copying the RelationshipTarget collection + // SAFETY: This only reads the Self component and queues despawn commands + unsafe { + let world = world.as_unsafe_world_cell(); + let relationship_target = world.get_entity(entity).unwrap().get::().unwrap(); + let mut commands = world.get_raw_command_queue(); + for source_entity in relationship_target.iter() { + if world.get_entity(source_entity).is_some() { + commands.push( + entity_command::despawn() + .with_entity(source_entity) + .handle_error_with(error_handler::silent()), + ); + } else { + warn!("Tried to despawn non-existent entity {}", source_entity); + } + } + } + } + + /// Creates this [`RelationshipTarget`] with the given pre-allocated entity capacity. + fn with_capacity(capacity: usize) -> Self { + let collection = + ::with_capacity(capacity); + Self::from_collection_risky(collection) + } + + /// Iterates the entities stored in this collection. + #[inline] + fn iter(&self) -> impl DoubleEndedIterator { + self.collection().iter() + } + + /// Returns the number of entities in this collection. + #[inline] + fn len(&self) -> usize { + self.collection().len() + } + + /// Returns true if this entity collection is empty. + #[inline] + fn is_empty(&self) -> bool { + self.collection().is_empty() + } +} + +#[cfg(test)] +mod tests { + use crate as bevy_ecs; + use crate::world::World; + use crate::{component::Component, entity::Entity}; + use alloc::vec::Vec; + + #[test] + fn custom_relationship() { + #[derive(Component)] + #[relationship(relationship_target = LikedBy)] + struct Likes(pub Entity); + + #[derive(Component)] + #[relationship_target(relationship = Likes)] + struct LikedBy(Vec); + + let mut world = World::new(); + let a = world.spawn_empty().id(); + let b = world.spawn(Likes(a)).id(); + let c = world.spawn(Likes(a)).id(); + assert_eq!(world.entity(a).get::().unwrap().0, &[b, c]); + } +} diff --git a/crates/bevy_ecs/src/relationship/related_methods.rs b/crates/bevy_ecs/src/relationship/related_methods.rs new file mode 100644 index 0000000000000..4b42709384d1d --- /dev/null +++ b/crates/bevy_ecs/src/relationship/related_methods.rs @@ -0,0 +1,164 @@ +use crate::{ + bundle::Bundle, + entity::Entity, + relationship::{Relationship, RelationshipTarget}, + system::{Commands, EntityCommands}, + world::{EntityWorldMut, World}, +}; +use core::marker::PhantomData; + +impl<'w> EntityWorldMut<'w> { + /// Spawns entities related to this entity (with the `R` relationship) by taking a function that operates on a [`RelatedSpawner`]. + pub fn with_related( + &mut self, + func: impl FnOnce(&mut RelatedSpawner), + ) -> &mut Self { + let parent = self.id(); + self.world_scope(|world| { + func(&mut RelatedSpawner::new(world, parent)); + }); + self + } + + /// Relates the given entities to this entity with the relation `R` + pub fn add_related(&mut self, related: &[Entity]) -> &mut Self { + let id = self.id(); + self.world_scope(|world| { + for related in related { + world.entity_mut(*related).insert(R::from(id)); + } + }); + self + } + + /// Despawns entities that relate to this one via the given [`RelationshipTarget`]. + /// This entity will not be despawned. + pub fn despawn_related(&mut self) -> &mut Self { + if let Some(sources) = self.take::() { + self.world_scope(|world| { + for entity in sources.iter() { + if let Ok(entity_mut) = world.get_entity_mut(entity) { + entity_mut.despawn(); + } + } + }); + } + self + } +} + +impl<'a> EntityCommands<'a> { + /// Spawns entities related to this entity (with the `R` relationship) by taking a function that operates on a [`RelatedSpawner`]. + pub fn with_related( + &mut self, + func: impl FnOnce(&mut RelatedSpawnerCommands), + ) -> &mut Self { + let id = self.id(); + func(&mut RelatedSpawnerCommands::new(self.commands(), id)); + self + } + + /// Relates the given entities to this entity with the relation `R` + pub fn add_related(&mut self, related: &[Entity]) -> &mut Self { + let id = self.id(); + let related = related.to_vec(); + self.commands().queue(move |world: &mut World| { + for related in related { + world.entity_mut(related).insert(R::from(id)); + } + }); + self + } + + /// Despawns entities that relate to this one via the given [`RelationshipTarget`]. + /// This entity will not be despawned. + pub fn despawn_related(&mut self) -> &mut Self { + let id = self.id(); + self.commands.queue(move |world: &mut World| { + world.entity_mut(id).despawn_related::(); + }); + self + } +} + +/// Directly spawns related "source" entities with the given [`Relationship`], targeting +/// a specific entity. +pub struct RelatedSpawner<'w, R: Relationship> { + target: Entity, + world: &'w mut World, + _marker: PhantomData, +} + +impl<'w, R: Relationship> RelatedSpawner<'w, R> { + /// Creates a new instance that will spawn entities targeting the `target` entity. + pub fn new(world: &'w mut World, target: Entity) -> Self { + Self { + world, + target, + _marker: PhantomData, + } + } + + /// Spawns an entity with the given `bundle` and an `R` relationship targeting the `target` + /// entity this spawner was initialized with. + pub fn spawn(&mut self, bundle: impl Bundle) -> EntityWorldMut<'_> { + self.world.spawn((R::from(self.target), bundle)) + } + + /// Spawns an entity with an `R` relationship targeting the `target` + /// entity this spawner was initialized with. + pub fn spawn_empty(&mut self) -> EntityWorldMut<'_> { + self.world.spawn(R::from(self.target)) + } + + /// Returns the "target entity" used when spawning entities with an `R` [`Relationship`]. + pub fn target_entity(&self) -> Entity { + self.target + } +} + +/// Uses commands to spawn related "source" entities with the given [`Relationship`], targeting +/// a specific entity. +pub struct RelatedSpawnerCommands<'w, R: Relationship> { + target: Entity, + commands: Commands<'w, 'w>, + _marker: PhantomData, +} + +impl<'w, R: Relationship> RelatedSpawnerCommands<'w, R> { + /// Creates a new instance that will spawn entities targeting the `target` entity. + pub fn new(commands: Commands<'w, 'w>, target: Entity) -> Self { + Self { + commands, + target, + _marker: PhantomData, + } + } + + /// Spawns an entity with the given `bundle` and an `R` relationship targeting the `target` + /// entity this spawner was initialized with. + pub fn spawn(&mut self, bundle: impl Bundle) -> EntityCommands<'_> { + self.commands.spawn((R::from(self.target), bundle)) + } + + /// Spawns an entity with an `R` relationship targeting the `target` + /// entity this spawner was initialized with. + pub fn spawn_empty(&mut self) -> EntityCommands<'_> { + self.commands.spawn(R::from(self.target)) + } + + /// Returns the "target entity" used when spawning entities with an `R` [`Relationship`]. + pub fn target_entity(&self) -> Entity { + self.target + } + + /// Returns the underlying [`Commands`]. + pub fn commands(&mut self) -> Commands { + self.commands.reborrow() + } + + /// Returns a mutable reference to the underlying [`Commands`]. + pub fn commands_mut(&mut self) -> &mut Commands<'w, 'w> { + &mut self.commands + } +} diff --git a/crates/bevy_ecs/src/relationship/relationship_query.rs b/crates/bevy_ecs/src/relationship/relationship_query.rs new file mode 100644 index 0000000000000..f47b6c14caa14 --- /dev/null +++ b/crates/bevy_ecs/src/relationship/relationship_query.rs @@ -0,0 +1,261 @@ +use crate::{ + entity::Entity, + query::{QueryData, QueryFilter, WorldQuery}, + relationship::{Relationship, RelationshipTarget}, + system::Query, +}; +use alloc::collections::VecDeque; +use smallvec::SmallVec; + +impl<'w, 's, D: QueryData, F: QueryFilter> Query<'w, 's, D, F> { + /// If the given `entity` contains the `R` [`Relationship`] component, returns the + /// target entity of that relationship. + pub fn related(&'w self, entity: Entity) -> Option + where + ::ReadOnly: WorldQuery = &'w R>, + { + self.get(entity).map(R::get).ok() + } + + /// If the given `entity` contains the `S` [`RelationshipTarget`] component, returns the + /// source entities stored on that component. + pub fn relationship_sources( + &'w self, + entity: Entity, + ) -> impl Iterator + 'w + where + ::ReadOnly: WorldQuery = &'w S>, + { + self.get(entity) + .into_iter() + .flat_map(RelationshipTarget::iter) + } + + /// Recursively walks up the tree defined by the given `R` [`Relationship`] until + /// there are no more related entities, returning the "root entity" of the relationship hierarchy. + /// + /// # Warning + /// For relationship graphs that contain loops, this could loop infinitely. Only call this for "hierarchy-style" + /// relationships. + pub fn root_ancestor(&'w self, entity: Entity) -> Entity + where + ::ReadOnly: WorldQuery = &'w R>, + { + // Recursively search up the tree until we're out of parents + match self.get(entity) { + Ok(parent) => self.root_ancestor(parent.get()), + Err(_) => entity, + } + } + + /// Iterates all "leaf entities" as defined by the [`RelationshipTarget`] hierarchy. + /// + /// # Warning + /// For relationship graphs that contain loops, this could loop infinitely. Only call this for "hierarchy-style" + /// relationships. + pub fn iter_leaves( + &'w self, + entity: Entity, + ) -> impl Iterator + 'w + where + ::ReadOnly: WorldQuery = &'w S>, + { + self.iter_descendants_depth_first(entity).filter(|entity| { + self.get(*entity) + // These are leaf nodes if they have the `Children` component but it's empty + .map(|children| children.len() == 0) + // Or if they don't have the `Children` component at all + .unwrap_or(true) + }) + } + + /// Iterates all sibling entities that also have the `R` [`Relationship`] with the same target entity. + pub fn iter_siblings( + &'w self, + entity: Entity, + ) -> impl Iterator + 'w + where + D::ReadOnly: WorldQuery = (Option<&'w R>, Option<&'w R::RelationshipTarget>)>, + { + self.get(entity) + .ok() + .and_then(|(maybe_parent, _)| maybe_parent.map(R::get)) + .and_then(|parent| self.get(parent).ok()) + .and_then(|(_, maybe_children)| maybe_children) + .into_iter() + .flat_map(move |children| children.iter().filter(move |child| *child != entity)) + } + + /// Iterates all descendant entities as defined by the given `entity`'s [`RelationshipTarget`] and their recursive + /// [`RelationshipTarget`]. + /// + /// # Warning + /// For relationship graphs that contain loops, this could loop infinitely. Only call this for "hierarchy-style" + /// relationships. + pub fn iter_descendants( + &'w self, + entity: Entity, + ) -> DescendantIter<'w, 's, D, F, S> + where + D::ReadOnly: WorldQuery = &'w S>, + { + DescendantIter::new(self, entity) + } + + /// Iterates all descendant entities as defined by the given `entity`'s [`RelationshipTarget`] and their recursive + /// [`RelationshipTarget`] in depth-first order. + /// + /// # Warning + /// For relationship graphs that contain loops, this could loop infinitely. Only call this for "hierarchy-style" + /// relationships. + pub fn iter_descendants_depth_first( + &'w self, + entity: Entity, + ) -> DescendantDepthFirstIter<'w, 's, D, F, S> + where + D::ReadOnly: WorldQuery = &'w S>, + { + DescendantDepthFirstIter::new(self, entity) + } + + /// Iterates all ancestors of the given `entity` as defined by the `R` [`Relationship`]. + /// + /// # Warning + /// For relationship graphs that contain loops, this could loop infinitely. Only call this for "hierarchy-style" + /// relationships. + pub fn iter_ancestors( + &'w self, + entity: Entity, + ) -> AncestorIter<'w, 's, D, F, R> + where + D::ReadOnly: WorldQuery = &'w R>, + { + AncestorIter::new(self, entity) + } +} + +/// An [`Iterator`] of [`Entity`]s over the descendants of an [`Entity`]. +/// +/// Traverses the hierarchy breadth-first. +pub struct DescendantIter<'w, 's, D: QueryData, F: QueryFilter, S: RelationshipTarget> +where + D::ReadOnly: WorldQuery = &'w S>, +{ + children_query: &'w Query<'w, 's, D, F>, + vecdeque: VecDeque, +} + +impl<'w, 's, D: QueryData, F: QueryFilter, S: RelationshipTarget> DescendantIter<'w, 's, D, F, S> +where + D::ReadOnly: WorldQuery = &'w S>, +{ + /// Returns a new [`DescendantIter`]. + pub fn new(children_query: &'w Query<'w, 's, D, F>, entity: Entity) -> Self { + DescendantIter { + children_query, + vecdeque: children_query + .get(entity) + .into_iter() + .flat_map(RelationshipTarget::iter) + .collect(), + } + } +} + +impl<'w, 's, D: QueryData, F: QueryFilter, S: RelationshipTarget> Iterator + for DescendantIter<'w, 's, D, F, S> +where + D::ReadOnly: WorldQuery = &'w S>, +{ + type Item = Entity; + + fn next(&mut self) -> Option { + let entity = self.vecdeque.pop_front()?; + + if let Ok(children) = self.children_query.get(entity) { + self.vecdeque.extend(children.iter()); + } + + Some(entity) + } +} + +/// An [`Iterator`] of [`Entity`]s over the descendants of an [`Entity`]. +/// +/// Traverses the hierarchy depth-first. +pub struct DescendantDepthFirstIter<'w, 's, D: QueryData, F: QueryFilter, S: RelationshipTarget> +where + D::ReadOnly: WorldQuery = &'w S>, +{ + children_query: &'w Query<'w, 's, D, F>, + stack: SmallVec<[Entity; 8]>, +} + +impl<'w, 's, D: QueryData, F: QueryFilter, S: RelationshipTarget> + DescendantDepthFirstIter<'w, 's, D, F, S> +where + D::ReadOnly: WorldQuery = &'w S>, +{ + /// Returns a new [`DescendantDepthFirstIter`]. + pub fn new(children_query: &'w Query<'w, 's, D, F>, entity: Entity) -> Self { + DescendantDepthFirstIter { + children_query, + stack: children_query + .get(entity) + .map_or(SmallVec::new(), |children| children.iter().rev().collect()), + } + } +} + +impl<'w, 's, D: QueryData, F: QueryFilter, S: RelationshipTarget> Iterator + for DescendantDepthFirstIter<'w, 's, D, F, S> +where + D::ReadOnly: WorldQuery = &'w S>, +{ + type Item = Entity; + + fn next(&mut self) -> Option { + let entity = self.stack.pop()?; + + if let Ok(children) = self.children_query.get(entity) { + self.stack.extend(children.iter().rev()); + } + + Some(entity) + } +} + +/// An [`Iterator`] of [`Entity`]s over the ancestors of an [`Entity`]. +pub struct AncestorIter<'w, 's, D: QueryData, F: QueryFilter, R: Relationship> +where + D::ReadOnly: WorldQuery = &'w R>, +{ + parent_query: &'w Query<'w, 's, D, F>, + next: Option, +} + +impl<'w, 's, D: QueryData, F: QueryFilter, R: Relationship> AncestorIter<'w, 's, D, F, R> +where + D::ReadOnly: WorldQuery = &'w R>, +{ + /// Returns a new [`AncestorIter`]. + pub fn new(parent_query: &'w Query<'w, 's, D, F>, entity: Entity) -> Self { + AncestorIter { + parent_query, + next: Some(entity), + } + } +} + +impl<'w, 's, D: QueryData, F: QueryFilter, R: Relationship> Iterator + for AncestorIter<'w, 's, D, F, R> +where + D::ReadOnly: WorldQuery = &'w R>, +{ + type Item = Entity; + + fn next(&mut self) -> Option { + self.next = self.parent_query.get(self.next?).ok().map(R::get); + self.next + } +} diff --git a/crates/bevy_ecs/src/relationship/relationship_source_collection.rs b/crates/bevy_ecs/src/relationship/relationship_source_collection.rs new file mode 100644 index 0000000000000..0158f05f4b457 --- /dev/null +++ b/crates/bevy_ecs/src/relationship/relationship_source_collection.rs @@ -0,0 +1,51 @@ +use crate::entity::Entity; +use alloc::vec::Vec; + +/// The internal [`Entity`] collection used by a [`RelationshipTarget`](crate::relationship::RelationshipTarget) component. +/// This is not intended to be modified directly by users, as it could invalidate the correctness of relationships. +pub trait RelationshipSourceCollection { + /// Returns an instance with the given pre-allocated entity `capacity`. + fn with_capacity(capacity: usize) -> Self; + + /// Adds the given `entity` to the collection. + fn add(&mut self, entity: Entity); + + /// Removes the given `entity` from the collection. + fn remove(&mut self, entity: Entity); + + /// Iterates all entities in the collection. + fn iter(&self) -> impl DoubleEndedIterator; + + /// Returns the current length of the collection. + fn len(&self) -> usize; + + /// Returns true if the collection contains no entities. + #[inline] + fn is_empty(&self) -> bool { + self.len() == 0 + } +} + +impl RelationshipSourceCollection for Vec { + fn with_capacity(capacity: usize) -> Self { + Vec::with_capacity(capacity) + } + + fn add(&mut self, entity: Entity) { + Vec::push(self, entity); + } + + fn remove(&mut self, entity: Entity) { + if let Some(index) = <[Entity]>::iter(self).position(|e| *e == entity) { + Vec::remove(self, index); + } + } + + fn iter(&self) -> impl DoubleEndedIterator { + <[Entity]>::iter(self).copied() + } + + fn len(&self) -> usize { + Vec::len(self) + } +} diff --git a/crates/bevy_ecs/src/system/commands/entity_command.rs b/crates/bevy_ecs/src/system/commands/entity_command.rs index c81f657084ec0..eea85ba4e48c5 100644 --- a/crates/bevy_ecs/src/system/commands/entity_command.rs +++ b/crates/bevy_ecs/src/system/commands/entity_command.rs @@ -254,8 +254,8 @@ pub fn retain() -> impl EntityCommand { /// /// # Note /// -/// This won't clean up external references to the entity (such as parent-child relationships -/// if you're using `bevy_hierarchy`), which may leave the world in an invalid state. +/// This will also despawn any [`Children`](crate::hierarchy::Children) entities, and any other [`RelationshipTarget`](crate::relationship::RelationshipTarget) that is configured +/// to despawn descendants. This results in "recursive despawn" behavior. pub fn despawn() -> impl EntityCommand { #[cfg(feature = "track_location")] let caller = Location::caller(); diff --git a/crates/bevy_ecs/src/system/commands/mod.rs b/crates/bevy_ecs/src/system/commands/mod.rs index 52f0417ee2ec1..d68c1f5a6769c 100644 --- a/crates/bevy_ecs/src/system/commands/mod.rs +++ b/crates/bevy_ecs/src/system/commands/mod.rs @@ -1699,8 +1699,8 @@ impl<'a> EntityCommands<'a> { /// /// # Note /// - /// This won't clean up external references to the entity (such as parent-child relationships - /// if you're using `bevy_hierarchy`), which may leave the world in an invalid state. + /// This will also despawn the entities in any [`RelationshipTarget`](crate::relationship::RelationshipTarget) that is configured + /// to despawn descendants. For example, this will recursively despawn [`Children`](crate::hierarchy::Children). /// /// # Example /// @@ -1723,11 +1723,24 @@ impl<'a> EntityCommands<'a> { pub fn despawn(&mut self) { self.queue_handled(entity_command::despawn(), error_handler::warn()); } + /// Despawns the provided entity and its descendants. + #[deprecated( + since = "0.16.0", + note = "Use entity.despawn(), which now automatically despawns recursively." + )] + pub fn despawn_recursive(&mut self) { + self.despawn(); + } /// Despawns the entity. /// /// This will not emit a warning if the entity does not exist, essentially performing /// the same function as [`Self::despawn`] without emitting warnings. + /// + /// # Note + /// + /// This will also despawn the entities in any [`RelationshipTarget`](crate::relationship::RelationshipTarget) that are configured + /// to despawn descendants. For example, this will recursively despawn [`Children`](crate::hierarchy::Children). pub fn try_despawn(&mut self) { self.queue_handled(entity_command::despawn(), error_handler::silent()); } diff --git a/crates/bevy_ecs/src/traversal.rs b/crates/bevy_ecs/src/traversal.rs index a8605e94ec8d0..342ad47849e06 100644 --- a/crates/bevy_ecs/src/traversal.rs +++ b/crates/bevy_ecs/src/traversal.rs @@ -1,6 +1,6 @@ //! A trait for components that let you traverse the ECS. -use crate::{entity::Entity, query::ReadOnlyQueryData}; +use crate::{entity::Entity, query::ReadOnlyQueryData, relationship::Relationship}; /// A component that can point to another entity, and which can be used to define a path through the ECS. /// @@ -30,3 +30,16 @@ impl Traversal for () { None } } + +/// This provides generalized hierarchy traversal for use in [event propagation]. +/// +/// # Warning +/// +/// Traversing in a loop could result in infinite loops for relationship graphs with loops. +/// +/// [event propagation]: crate::observer::Trigger::propagate +impl Traversal for &R { + fn traverse(item: Self::Item<'_>, _data: &D) -> Option { + Some(item.get()) + } +} diff --git a/crates/bevy_ecs/src/world/component_constants.rs b/crates/bevy_ecs/src/world/component_constants.rs index 5eea8dc6229ef..fa941edb4cef1 100644 --- a/crates/bevy_ecs/src/world/component_constants.rs +++ b/crates/bevy_ecs/src/world/component_constants.rs @@ -13,6 +13,8 @@ pub const ON_INSERT: ComponentId = ComponentId::new(1); pub const ON_REPLACE: ComponentId = ComponentId::new(2); /// [`ComponentId`] for [`OnRemove`] pub const ON_REMOVE: ComponentId = ComponentId::new(3); +/// [`ComponentId`] for [`OnDespawn`] +pub const ON_DESPAWN: ComponentId = ComponentId::new(4); /// Trigger emitted when a component is added to an entity. See [`crate::component::ComponentHooks::on_add`] /// for more information. @@ -41,3 +43,10 @@ pub struct OnReplace; #[cfg_attr(feature = "bevy_reflect", derive(Reflect))] #[cfg_attr(feature = "bevy_reflect", reflect(Debug))] pub struct OnRemove; + +/// Trigger emitted for each component on an entity when it is despawned. See [`crate::component::ComponentHooks::on_despawn`] +/// for more information. +#[derive(Event, Debug)] +#[cfg_attr(feature = "bevy_reflect", derive(Reflect))] +#[cfg_attr(feature = "bevy_reflect", reflect(Debug))] +pub struct OnDespawn; diff --git a/crates/bevy_ecs/src/world/deferred_world.rs b/crates/bevy_ecs/src/world/deferred_world.rs index 1d28051fc516e..3564b8eae1002 100644 --- a/crates/bevy_ecs/src/world/deferred_world.rs +++ b/crates/bevy_ecs/src/world/deferred_world.rs @@ -580,6 +580,28 @@ impl<'w> DeferredWorld<'w> { } } + /// Triggers all `on_despawn` hooks for [`ComponentId`] in target. + /// + /// # Safety + /// Caller must ensure [`ComponentId`] in target exist in self. + #[inline] + pub(crate) unsafe fn trigger_on_despawn( + &mut self, + archetype: &Archetype, + entity: Entity, + targets: impl Iterator, + ) { + if archetype.has_despawn_hook() { + for component_id in targets { + // SAFETY: Caller ensures that these components exist + let hooks = unsafe { self.components().get_info_unchecked(component_id) }.hooks(); + if let Some(hook) = hooks.on_despawn { + hook(DeferredWorld { world: self.world }, entity, component_id); + } + } + } + } + /// Triggers all event observers for [`ComponentId`] in target. /// /// # Safety diff --git a/crates/bevy_ecs/src/world/entity_ref.rs b/crates/bevy_ecs/src/world/entity_ref.rs index ddbfa81e627a6..66018f7a4e476 100644 --- a/crates/bevy_ecs/src/world/entity_ref.rs +++ b/crates/bevy_ecs/src/world/entity_ref.rs @@ -12,7 +12,10 @@ use crate::{ removal_detection::RemovedComponentEvents, storage::Storages, system::{IntoObserverSystem, Resource}, - world::{error::EntityComponentError, DeferredWorld, Mut, World}, + world::{ + error::EntityComponentError, unsafe_world_cell::UnsafeEntityCell, DeferredWorld, Mut, Ref, + World, ON_DESPAWN, ON_REMOVE, ON_REPLACE, + }, }; use alloc::vec::Vec; use bevy_ptr::{OwningPtr, Ptr}; @@ -28,8 +31,6 @@ use core::{ }; use thiserror::Error; -use super::{unsafe_world_cell::UnsafeEntityCell, Ref, ON_REMOVE, ON_REPLACE}; - /// A read-only reference to a particular [`Entity`] and all of its components. /// /// # Examples @@ -2095,6 +2096,11 @@ impl<'w> EntityWorldMut<'w> { /// /// See [`World::despawn`] for more details. /// + /// # Note + /// + /// This will also despawn any [`Children`](crate::hierarchy::Children) entities, and any other [`RelationshipTarget`](crate::relationship::RelationshipTarget) that is configured + /// to despawn descendants. This results in "recursive despawn" behavior. + /// /// # Panics /// /// If the entity has been despawned while this `EntityWorldMut` is still alive. @@ -2106,6 +2112,15 @@ impl<'w> EntityWorldMut<'w> { ); } + /// Despawns the provided entity and its descendants. + #[deprecated( + since = "0.16.0", + note = "Use entity.despawn(), which now automatically despawns recursively." + )] + pub fn despawn_recursive(self) { + self.despawn(); + } + pub(crate) fn despawn_with_caller( self, #[cfg(feature = "track_location")] caller: &'static Location, @@ -2123,6 +2138,10 @@ impl<'w> EntityWorldMut<'w> { // SAFETY: All components in the archetype exist in world unsafe { + if archetype.has_despawn_observer() { + deferred_world.trigger_observers(ON_DESPAWN, self.entity, archetype.components()); + } + deferred_world.trigger_on_despawn(archetype, self.entity, archetype.components()); if archetype.has_replace_observer() { deferred_world.trigger_observers(ON_REPLACE, self.entity, archetype.components()); } @@ -5272,7 +5291,8 @@ mod tests { .resource_mut::() .0 .push("OrdA hook on_insert"); - world.commands().entity(entity).despawn(); + world.commands().entity(entity).remove::(); + world.commands().entity(entity).remove::(); } fn ord_a_hook_on_replace(mut world: DeferredWorld, _entity: Entity, _id: ComponentId) { @@ -5380,12 +5400,12 @@ mod tests { "OrdB observer on_insert", "OrdB command on_add", // command added by OrdB hook on_add, needs to run before despawn command "OrdA observer on_replace", // start of despawn - "OrdB observer on_replace", "OrdA hook on_replace", - "OrdB hook on_replace", "OrdA observer on_remove", - "OrdB observer on_remove", "OrdA hook on_remove", + "OrdB observer on_replace", + "OrdB hook on_replace", + "OrdB observer on_remove", "OrdB hook on_remove", ]; world.flush(); diff --git a/crates/bevy_ecs/src/world/mod.rs b/crates/bevy_ecs/src/world/mod.rs index 4b8be6cc1f3e1..1bac1df44dcd7 100644 --- a/crates/bevy_ecs/src/world/mod.rs +++ b/crates/bevy_ecs/src/world/mod.rs @@ -159,6 +159,9 @@ impl World { let on_remove = OnRemove::register_component_id(self); assert_eq!(ON_REMOVE, on_remove); + + let on_despawn = OnDespawn::register_component_id(self); + assert_eq!(ON_DESPAWN, on_despawn); } /// Creates a new empty [`World`]. /// @@ -1080,19 +1083,26 @@ impl World { self.flush(); let change_tick = self.change_tick(); let entity = self.entities.alloc(); - let entity_location = { - let mut bundle_spawner = BundleSpawner::new::(self, change_tick); - // SAFETY: bundle's type matches `bundle_info`, entity is allocated but non-existent - unsafe { - bundle_spawner.spawn_non_existent( - entity, - bundle, - #[cfg(feature = "track_location")] - Location::caller(), - ) - } + let mut bundle_spawner = BundleSpawner::new::(self, change_tick); + // SAFETY: bundle's type matches `bundle_info`, entity is allocated but non-existent + let mut entity_location = unsafe { + bundle_spawner.spawn_non_existent( + entity, + bundle, + #[cfg(feature = "track_location")] + Location::caller(), + ) }; + // SAFETY: command_queue is not referenced anywhere else + if !unsafe { self.command_queue.is_empty() } { + self.flush_commands(); + entity_location = self + .entities() + .get(entity) + .unwrap_or(EntityLocation::INVALID); + } + #[cfg(feature = "track_location")] self.entities .set_spawned_or_despawned_by(entity.index(), Location::caller()); @@ -1266,8 +1276,8 @@ impl World { /// /// # Note /// - /// This won't clean up external references to the entity (such as parent-child relationships - /// if you're using `bevy_hierarchy`), which may leave the world in an invalid state. + /// This will also despawn the entities in any [`RelationshipTarget`](crate::relationship::RelationshipTarget) that is configured + /// to despawn descendants. For example, this will recursively despawn [`Children`](crate::hierarchy::Children). /// /// ``` /// use bevy_ecs::{component::Component, world::World}; diff --git a/crates/bevy_gltf/Cargo.toml b/crates/bevy_gltf/Cargo.toml index 167ed3c19d4d0..023194f8a849a 100644 --- a/crates/bevy_gltf/Cargo.toml +++ b/crates/bevy_gltf/Cargo.toml @@ -24,7 +24,6 @@ bevy_asset = { path = "../bevy_asset", version = "0.16.0-dev" } bevy_color = { path = "../bevy_color", version = "0.16.0-dev" } bevy_core_pipeline = { path = "../bevy_core_pipeline", version = "0.16.0-dev" } bevy_ecs = { path = "../bevy_ecs", version = "0.16.0-dev" } -bevy_hierarchy = { path = "../bevy_hierarchy", version = "0.16.0-dev" } bevy_image = { path = "../bevy_image", version = "0.16.0-dev" } bevy_math = { path = "../bevy_math", version = "0.16.0-dev" } bevy_pbr = { path = "../bevy_pbr", version = "0.16.0-dev" } diff --git a/crates/bevy_gltf/src/loader.rs b/crates/bevy_gltf/src/loader.rs index e83ed9351f09c..5b9810d184c84 100644 --- a/crates/bevy_gltf/src/loader.rs +++ b/crates/bevy_gltf/src/loader.rs @@ -11,10 +11,10 @@ use bevy_color::{Color, LinearRgba}; use bevy_core_pipeline::prelude::Camera3d; use bevy_ecs::{ entity::{Entity, EntityHashMap}, + hierarchy::ChildSpawner, name::Name, world::World, }; -use bevy_hierarchy::{BuildChildren, ChildBuild, WorldChildBuilder}; use bevy_image::{ CompressedImageFormats, Image, ImageAddressMode, ImageFilterMode, ImageLoaderSettings, ImageSampler, ImageSamplerDescriptor, ImageType, TextureError, @@ -1377,7 +1377,7 @@ fn warn_on_differing_texture_transforms( )] fn load_node( gltf_node: &Node, - world_builder: &mut WorldChildBuilder, + child_spawner: &mut ChildSpawner, root_load_context: &LoadContext, load_context: &mut LoadContext, settings: &GltfLoaderSettings, @@ -1399,7 +1399,7 @@ fn load_node( // of negative scale factors is odd. if so we will assign a copy of the material with face // culling inverted, rather than modifying the mesh data directly. let is_scale_inverted = world_transform.scale.is_negative_bitmask().count_ones() & 1 == 1; - let mut node = world_builder.spawn((transform, Visibility::default())); + let mut node = child_spawner.spawn((transform, Visibility::default())); let name = node_name(gltf_node); node.insert(name.clone()); diff --git a/crates/bevy_hierarchy/Cargo.toml b/crates/bevy_hierarchy/Cargo.toml deleted file mode 100644 index 076428a613f41..0000000000000 --- a/crates/bevy_hierarchy/Cargo.toml +++ /dev/null @@ -1,66 +0,0 @@ -[package] -name = "bevy_hierarchy" -version = "0.16.0-dev" -edition = "2021" -description = "Provides hierarchy functionality for Bevy Engine" -homepage = "https://bevyengine.org" -repository = "https://github.com/bevyengine/bevy" -license = "MIT OR Apache-2.0" -keywords = ["bevy"] - -[features] -default = ["std", "bevy_app", "reflect"] - -# Functionality - -## Adds integration with the `bevy_app` plugin API. -bevy_app = ["dep:bevy_app"] - -## Adds runtime reflection support using `bevy_reflect`. -reflect = ["bevy_ecs/bevy_reflect", "bevy_reflect", "bevy_app?/bevy_reflect"] - -# Debugging Features - -## Enables `tracing` integration, allowing spans and other metrics to be reported -## through that framework. -trace = ["dep:tracing"] - -# Platform Compatibility - -## Allows access to the `std` crate. Enabling this feature will prevent compilation -## on `no_std` targets, but provides access to certain additional features on -## supported platforms. -std = [ - "bevy_app?/std", - "bevy_ecs/std", - "bevy_reflect/std", - "bevy_utils/std", - "disqualified/alloc", -] - -[dependencies] -# bevy -bevy_app = { path = "../bevy_app", version = "0.16.0-dev", default-features = false, optional = true } -bevy_ecs = { path = "../bevy_ecs", version = "0.16.0-dev", default-features = false } -bevy_reflect = { path = "../bevy_reflect", version = "0.16.0-dev", features = [ - "smallvec", -], default-features = false, optional = true } -bevy_utils = { path = "../bevy_utils", version = "0.16.0-dev", default-features = false, features = [ - "alloc", -] } -disqualified = { version = "1.0", default-features = false } - -# other -smallvec = { version = "1.11", default-features = false, features = [ - "union", - "const_generics", -] } -tracing = { version = "0.1", default-features = false, optional = true } -log = { version = "0.4", default-features = false } - -[lints] -workspace = true - -[package.metadata.docs.rs] -rustdoc-args = ["-Zunstable-options", "--generate-link-to-definition"] -all-features = true diff --git a/crates/bevy_hierarchy/README.md b/crates/bevy_hierarchy/README.md deleted file mode 100644 index 22d7802e9a957..0000000000000 --- a/crates/bevy_hierarchy/README.md +++ /dev/null @@ -1,7 +0,0 @@ -# Bevy Hierarchy - -[![License](https://img.shields.io/badge/license-MIT%2FApache-blue.svg)](https://github.com/bevyengine/bevy#license) -[![Crates.io](https://img.shields.io/crates/v/bevy_hierarchy.svg)](https://crates.io/crates/bevy_hierarchy) -[![Downloads](https://img.shields.io/crates/d/bevy_hierarchy.svg)](https://crates.io/crates/bevy_hierarchy) -[![Docs](https://docs.rs/bevy_hierarchy/badge.svg)](https://docs.rs/bevy_hierarchy/latest/bevy_hierarchy/) -[![Discord](https://img.shields.io/discord/691052431525675048.svg?label=&logo=discord&logoColor=ffffff&color=7389D8&labelColor=6A7EC2)](https://discord.gg/bevy) diff --git a/crates/bevy_hierarchy/src/child_builder.rs b/crates/bevy_hierarchy/src/child_builder.rs deleted file mode 100644 index 811c4f4eaa8e3..0000000000000 --- a/crates/bevy_hierarchy/src/child_builder.rs +++ /dev/null @@ -1,1222 +0,0 @@ -use crate::{Children, HierarchyEvent, Parent}; -use bevy_ecs::{ - bundle::Bundle, - entity::Entity, - event::Events, - system::{Command, Commands, EntityCommands}, - world::{EntityWorldMut, World}, -}; -use smallvec::{smallvec, SmallVec}; - -// Do not use `world.send_event_batch` as it prints error message when the Events are not available in the world, -// even though it's a valid use case to execute commands on a world without events. Loading a GLTF file for example -fn push_events(world: &mut World, events: impl IntoIterator) { - if let Some(mut moved) = world.get_resource_mut::>() { - moved.extend(events); - } -} - -/// Adds `child` to `parent`'s [`Children`], without checking if it is already present there. -/// -/// This might cause unexpected results when removing duplicate children. -fn add_child_unchecked(world: &mut World, parent: Entity, child: Entity) { - let mut parent = world.entity_mut(parent); - if let Some(mut children) = parent.get_mut::() { - children.0.push(child); - } else { - parent.insert(Children(smallvec![child])); - } -} - -/// Sets [`Parent`] of the `child` to `new_parent`. Inserts [`Parent`] if `child` doesn't have one. -fn update_parent(world: &mut World, child: Entity, new_parent: Entity) -> Option { - let mut child = world.entity_mut(child); - if let Some(mut parent) = child.get_mut::() { - let previous = parent.0; - *parent = Parent(new_parent); - Some(previous) - } else { - child.insert(Parent(new_parent)); - None - } -} - -/// Remove child from the parent's [`Children`] component. -/// -/// Removes the [`Children`] component from the parent if it's empty. -fn remove_from_children(world: &mut World, parent: Entity, child: Entity) { - let Ok(mut parent) = world.get_entity_mut(parent) else { - return; - }; - let Some(mut children) = parent.get_mut::() else { - return; - }; - children.0.retain(|x| *x != child); - if children.is_empty() { - parent.remove::(); - } -} - -/// Update the [`Parent`] component of the `child`. -/// Removes the `child` from the previous parent's [`Children`]. -/// -/// Does not update the new parents [`Children`] component. -/// -/// Does nothing if `child` was already a child of `parent`. -/// -/// Sends [`HierarchyEvent`]'s. -fn update_old_parent(world: &mut World, child: Entity, parent: Entity) { - let previous = update_parent(world, child, parent); - if let Some(previous_parent) = previous { - // Do nothing if the child was already parented to this entity. - if previous_parent == parent { - return; - } - remove_from_children(world, previous_parent, child); - - push_events( - world, - [HierarchyEvent::ChildMoved { - child, - previous_parent, - new_parent: parent, - }], - ); - } else { - push_events(world, [HierarchyEvent::ChildAdded { child, parent }]); - } -} - -/// Update the [`Parent`] components of the `children`. -/// Removes the `children` from their previous parent's [`Children`]. -/// -/// Does not update the new parents [`Children`] component. -/// -/// Does nothing for a child if it was already a child of `parent`. -/// -/// Sends [`HierarchyEvent`]'s. -fn update_old_parents(world: &mut World, parent: Entity, children: &[Entity]) { - let mut events: SmallVec<[HierarchyEvent; 8]> = SmallVec::with_capacity(children.len()); - for &child in children { - if let Some(previous) = update_parent(world, child, parent) { - // Do nothing if the entity already has the correct parent. - if parent == previous { - continue; - } - - remove_from_children(world, previous, child); - events.push(HierarchyEvent::ChildMoved { - child, - previous_parent: previous, - new_parent: parent, - }); - } else { - events.push(HierarchyEvent::ChildAdded { child, parent }); - } - } - push_events(world, events); -} - -/// Removes entities in `children` from `parent`'s [`Children`], removing the component if it ends up empty. -/// Also removes [`Parent`] component from `children`. -fn remove_children(parent: Entity, children: &[Entity], world: &mut World) { - let mut events: SmallVec<[HierarchyEvent; 8]> = SmallVec::new(); - if let Some(parent_children) = world.get::(parent) { - for &child in children { - if parent_children.contains(&child) { - events.push(HierarchyEvent::ChildRemoved { child, parent }); - } - } - } else { - return; - } - for event in &events { - if let &HierarchyEvent::ChildRemoved { child, .. } = event { - world.entity_mut(child).remove::(); - } - } - push_events(world, events); - - let mut parent = world.entity_mut(parent); - if let Some(mut parent_children) = parent.get_mut::() { - parent_children - .0 - .retain(|parent_child| !children.contains(parent_child)); - - if parent_children.is_empty() { - parent.remove::(); - } - } -} - -/// Struct for building children entities and adding them to a parent entity. -/// -/// # Example -/// -/// This example creates three entities, a parent and two children. -/// -/// ``` -/// # use bevy_ecs::bundle::Bundle; -/// # use bevy_ecs::system::Commands; -/// # use bevy_hierarchy::{ChildBuild, BuildChildren}; -/// # #[derive(Bundle)] -/// # struct MyBundle {} -/// # #[derive(Bundle)] -/// # struct MyChildBundle {} -/// # -/// # fn test(mut commands: Commands) { -/// commands.spawn(MyBundle {}).with_children(|child_builder| { -/// child_builder.spawn(MyChildBundle {}); -/// child_builder.spawn(MyChildBundle {}); -/// }); -/// # } -/// ``` -pub struct ChildBuilder<'a> { - commands: Commands<'a, 'a>, - children: SmallVec<[Entity; 8]>, - parent: Entity, -} - -/// Trait for building children entities and adding them to a parent entity. This is used in -/// implementations of [`BuildChildren`] as a bound on the [`Builder`](BuildChildren::Builder) -/// associated type. The closure passed to [`BuildChildren::with_children`] accepts an -/// implementation of `ChildBuild` so that children can be spawned via [`ChildBuild::spawn`]. -pub trait ChildBuild { - /// Spawn output type. Both [`spawn`](Self::spawn) and [`spawn_empty`](Self::spawn_empty) return - /// an implementation of this type so that children can be operated on via method-chaining. - /// Implementations of `ChildBuild` reborrow `self` when spawning entities (see - /// [`Commands::spawn_empty`] and [`World::get_entity_mut`]). Lifetime `'a` corresponds to this - /// reborrowed self, and `Self` outlives it. - type SpawnOutput<'a>: BuildChildren - where - Self: 'a; - - /// Spawns an entity with the given bundle and inserts it into the parent entity's [`Children`]. - /// Also adds [`Parent`] component to the created entity. - fn spawn(&mut self, bundle: impl Bundle) -> Self::SpawnOutput<'_>; - - /// Spawns an [`Entity`] with no components and inserts it into the parent entity's [`Children`]. - /// Also adds [`Parent`] component to the created entity. - fn spawn_empty(&mut self) -> Self::SpawnOutput<'_>; - - /// Returns the parent entity. - fn parent_entity(&self) -> Entity; - - /// Adds a command to be executed, like [`Commands::queue`]. - fn queue_command(&mut self, command: C) -> &mut Self; -} - -impl ChildBuild for ChildBuilder<'_> { - type SpawnOutput<'a> - = EntityCommands<'a> - where - Self: 'a; - - fn spawn(&mut self, bundle: impl Bundle) -> EntityCommands { - let e = self.commands.spawn(bundle); - self.children.push(e.id()); - e - } - - fn spawn_empty(&mut self) -> EntityCommands { - let e = self.commands.spawn_empty(); - self.children.push(e.id()); - e - } - - fn parent_entity(&self) -> Entity { - self.parent - } - - fn queue_command(&mut self, command: C) -> &mut Self { - self.commands.queue(command); - self - } -} - -/// Trait for removing, adding and replacing children and parents of an entity. -pub trait BuildChildren { - /// Child builder type. - type Builder<'a>: ChildBuild; - - /// Takes a closure which builds children for this entity using [`ChildBuild`]. - /// - /// For convenient spawning of a single child, you can use [`with_child`]. - /// - /// [`with_child`]: BuildChildren::with_child - fn with_children(&mut self, f: impl FnOnce(&mut Self::Builder<'_>)) -> &mut Self; - - /// Spawns the passed bundle and adds it to this entity as a child. - /// - /// The bundle's [`Parent`] component will be updated to the new parent. - /// - /// For efficient spawning of multiple children, use [`with_children`]. - /// - /// [`with_children`]: BuildChildren::with_children - fn with_child(&mut self, bundle: B) -> &mut Self; - - /// Pushes children to the back of the builder's children. For any entities that are - /// already a child of this one, this method does nothing. - /// - /// The children's [`Parent`] component will be updated to the new parent. - /// - /// If the children were previously children of another parent, that parent's [`Children`] component - /// will have those children removed from its list. Removing all children from a parent causes its - /// [`Children`] component to be removed from the entity. - /// - /// # Panics - /// - /// Panics if any of the children are the same as the parent. - fn add_children(&mut self, children: &[Entity]) -> &mut Self; - - /// Inserts children at the given index. - /// - /// The children's [`Parent`] component will be updated to the new parent. - /// - /// If the children were previously children of another parent, that parent's [`Children`] component - /// will have those children removed from its list. Removing all children from a parent causes its - /// [`Children`] component to be removed from the entity. - /// - /// # Panics - /// - /// Panics if any of the children are the same as the parent. - fn insert_children(&mut self, index: usize, children: &[Entity]) -> &mut Self; - - /// Removes the given children. - /// - /// The removed children will have their [`Parent`] component removed. - /// - /// Removing all children from a parent causes its [`Children`] component to be removed from the entity. - fn remove_children(&mut self, children: &[Entity]) -> &mut Self; - - /// Adds a single child. - /// - /// The child's [`Parent`] component will be updated to the new parent. - /// - /// If the child was previously the child of another parent, that parent's [`Children`] component - /// will have the child removed from its list. Removing all children from a parent causes its - /// [`Children`] component to be removed from the entity. - /// - /// # Panics - /// - /// Panics if the child is the same as the parent. - fn add_child(&mut self, child: Entity) -> &mut Self; - - /// Removes all children from this entity. The [`Children`] component and the children's [`Parent`] component will be removed. - /// If the [`Children`] component is not present, this has no effect. - fn clear_children(&mut self) -> &mut Self; - - /// Removes all current children from this entity, replacing them with the specified list of entities. - /// - /// The added children's [`Parent`] component will be updated to the new parent. - /// The removed children will have their [`Parent`] component removed. - /// - /// # Panics - /// - /// Panics if any of the children are the same as the parent. - fn replace_children(&mut self, children: &[Entity]) -> &mut Self; - - /// Sets the parent of this entity. - /// - /// If this entity already had a parent, the parent's [`Children`] component will have this - /// child removed from its list. Removing all children from a parent causes its [`Children`] - /// component to be removed from the entity. - /// - /// # Panics - /// - /// Panics if the parent is the same as the child. - fn set_parent(&mut self, parent: Entity) -> &mut Self; - - /// Removes the [`Parent`] of this entity. - /// - /// Also removes this entity from its parent's [`Children`] component. Removing all children from a parent causes - /// its [`Children`] component to be removed from the entity. - fn remove_parent(&mut self) -> &mut Self; -} - -impl BuildChildren for EntityCommands<'_> { - type Builder<'a> = ChildBuilder<'a>; - - fn with_children(&mut self, spawn_children: impl FnOnce(&mut Self::Builder<'_>)) -> &mut Self { - let parent = self.id(); - let mut builder = ChildBuilder { - commands: self.commands(), - children: SmallVec::default(), - parent, - }; - - spawn_children(&mut builder); - - let children = builder.children; - if children.contains(&parent) { - panic!("Entity cannot be a child of itself."); - } - self.queue(move |mut entity: EntityWorldMut| { - entity.add_children(&children); - }) - } - - fn with_child(&mut self, bundle: B) -> &mut Self { - let child = self.commands().spawn(bundle).id(); - self.queue(move |mut entity: EntityWorldMut| { - entity.add_child(child); - }) - } - - fn add_children(&mut self, children: &[Entity]) -> &mut Self { - let parent = self.id(); - if children.contains(&parent) { - panic!("Cannot add entity as a child of itself."); - } - let children = SmallVec::<[Entity; 8]>::from_slice(children); - self.queue(move |mut entity: EntityWorldMut| { - entity.add_children(&children); - }) - } - - fn insert_children(&mut self, index: usize, children: &[Entity]) -> &mut Self { - let parent = self.id(); - if children.contains(&parent) { - panic!("Cannot insert entity as a child of itself."); - } - let children = SmallVec::<[Entity; 8]>::from_slice(children); - self.queue(move |mut entity: EntityWorldMut| { - entity.insert_children(index, &children); - }) - } - - fn remove_children(&mut self, children: &[Entity]) -> &mut Self { - let children = SmallVec::<[Entity; 8]>::from_slice(children); - self.queue(move |mut entity: EntityWorldMut| { - entity.remove_children(&children); - }) - } - - fn add_child(&mut self, child: Entity) -> &mut Self { - let parent = self.id(); - if child == parent { - panic!("Cannot add entity as a child of itself."); - } - self.queue(move |mut entity: EntityWorldMut| { - entity.add_child(child); - }) - } - - fn clear_children(&mut self) -> &mut Self { - self.queue(move |mut entity: EntityWorldMut| { - entity.clear_children(); - }) - } - - fn replace_children(&mut self, children: &[Entity]) -> &mut Self { - let parent = self.id(); - if children.contains(&parent) { - panic!("Cannot replace entity as a child of itself."); - } - let children = SmallVec::<[Entity; 8]>::from_slice(children); - self.queue(move |mut entity: EntityWorldMut| { - entity.replace_children(&children); - }) - } - - fn set_parent(&mut self, parent: Entity) -> &mut Self { - let child = self.id(); - if child == parent { - panic!("Cannot set parent to itself"); - } - self.queue(move |mut entity: EntityWorldMut| { - entity.world_scope(|world| { - world.entity_mut(parent).add_child(child); - }); - }) - } - - fn remove_parent(&mut self) -> &mut Self { - self.queue(move |mut entity: EntityWorldMut| { - entity.remove_parent(); - }) - } -} - -/// Struct for adding children to an entity directly through the [`World`] for use in exclusive systems. -#[derive(Debug)] -pub struct WorldChildBuilder<'w> { - world: &'w mut World, - parent: Entity, -} - -impl ChildBuild for WorldChildBuilder<'_> { - type SpawnOutput<'a> - = EntityWorldMut<'a> - where - Self: 'a; - - fn spawn(&mut self, bundle: impl Bundle) -> EntityWorldMut { - let entity = self.world.spawn((bundle, Parent(self.parent))).id(); - add_child_unchecked(self.world, self.parent, entity); - push_events( - self.world, - [HierarchyEvent::ChildAdded { - child: entity, - parent: self.parent, - }], - ); - self.world.entity_mut(entity) - } - - fn spawn_empty(&mut self) -> EntityWorldMut { - self.spawn(()) - } - - fn parent_entity(&self) -> Entity { - self.parent - } - - fn queue_command(&mut self, command: C) -> &mut Self { - self.world.commands().queue(command); - self - } -} - -impl WorldChildBuilder<'_> { - /// Calls the world's [`World::flush`] to apply any commands - /// queued by [`Self::queue_command`]. - pub fn flush_world(&mut self) { - self.world.flush(); - } -} - -impl BuildChildren for EntityWorldMut<'_> { - type Builder<'a> = WorldChildBuilder<'a>; - - fn with_children(&mut self, spawn_children: impl FnOnce(&mut WorldChildBuilder)) -> &mut Self { - let parent = self.id(); - self.world_scope(|world| { - spawn_children(&mut WorldChildBuilder { world, parent }); - }); - self - } - - fn with_child(&mut self, bundle: B) -> &mut Self { - let parent = self.id(); - let child = self.world_scope(|world| world.spawn((bundle, Parent(parent))).id()); - if let Some(mut children_component) = self.get_mut::() { - children_component.0.retain(|value| child != *value); - children_component.0.push(child); - } else { - self.insert(Children::from_entities(&[child])); - } - self - } - - fn add_child(&mut self, child: Entity) -> &mut Self { - let parent = self.id(); - if child == parent { - panic!("Cannot add entity as a child of itself."); - } - self.world_scope(|world| { - update_old_parent(world, child, parent); - }); - if let Some(mut children_component) = self.get_mut::() { - children_component.0.retain(|value| child != *value); - children_component.0.push(child); - } else { - self.insert(Children::from_entities(&[child])); - } - self - } - - fn add_children(&mut self, children: &[Entity]) -> &mut Self { - if children.is_empty() { - return self; - } - - let parent = self.id(); - if children.contains(&parent) { - panic!("Cannot push entity as a child of itself."); - } - self.world_scope(|world| { - update_old_parents(world, parent, children); - }); - if let Some(mut children_component) = self.get_mut::() { - children_component - .0 - .retain(|value| !children.contains(value)); - children_component.0.extend(children.iter().cloned()); - } else { - self.insert(Children::from_entities(children)); - } - self - } - - fn insert_children(&mut self, index: usize, children: &[Entity]) -> &mut Self { - let parent = self.id(); - if children.contains(&parent) { - panic!("Cannot insert entity as a child of itself."); - } - self.world_scope(|world| { - update_old_parents(world, parent, children); - }); - if let Some(mut children_component) = self.get_mut::() { - children_component - .0 - .retain(|value| !children.contains(value)); - children_component.0.insert_from_slice(index, children); - } else { - self.insert(Children::from_entities(children)); - } - self - } - - fn remove_children(&mut self, children: &[Entity]) -> &mut Self { - let parent = self.id(); - self.world_scope(|world| { - remove_children(parent, children, world); - }); - self - } - - fn set_parent(&mut self, parent: Entity) -> &mut Self { - let child = self.id(); - self.world_scope(|world| { - world.entity_mut(parent).add_child(child); - }); - self - } - - fn remove_parent(&mut self) -> &mut Self { - let child = self.id(); - if let Some(parent) = self.take::().map(|p| p.get()) { - self.world_scope(|world| { - remove_from_children(world, parent, child); - push_events(world, [HierarchyEvent::ChildRemoved { child, parent }]); - }); - } - self - } - - fn clear_children(&mut self) -> &mut Self { - let parent = self.id(); - self.world_scope(|world| { - if let Some(children) = world.entity_mut(parent).take::() { - for &child in &children.0 { - world.entity_mut(child).remove::(); - } - } - }); - self - } - - fn replace_children(&mut self, children: &[Entity]) -> &mut Self { - self.clear_children().add_children(children) - } -} - -#[cfg(test)] -mod tests { - use super::{BuildChildren, ChildBuild}; - use crate::{ - components::{Children, Parent}, - HierarchyEvent::{self, ChildAdded, ChildMoved, ChildRemoved}, - }; - use alloc::{vec, vec::Vec}; - use smallvec::{smallvec, SmallVec}; - - use bevy_ecs::{ - component::Component, - entity::Entity, - event::Events, - system::Commands, - world::{CommandQueue, World}, - }; - - /// Assert the (non)existence and state of the child's [`Parent`] component. - fn assert_parent(world: &World, child: Entity, parent: Option) { - assert_eq!(world.get::(child).map(Parent::get), parent); - } - - /// Assert the (non)existence and state of the parent's [`Children`] component. - fn assert_children(world: &World, parent: Entity, children: Option<&[Entity]>) { - assert_eq!(world.get::(parent).map(|c| &**c), children); - } - - /// Assert the number of children in the parent's [`Children`] component if it exists. - fn assert_num_children(world: &World, parent: Entity, num_children: usize) { - assert_eq!( - world.get::(parent).map(|c| c.len()).unwrap_or(0), - num_children - ); - } - - /// Used to omit a number of events that are not relevant to a particular test. - fn omit_events(world: &mut World, number: usize) { - let mut events_resource = world.resource_mut::>(); - let mut events: Vec<_> = events_resource.drain().collect(); - events_resource.extend(events.drain(number..)); - } - - fn assert_events(world: &mut World, expected_events: &[HierarchyEvent]) { - let events: Vec<_> = world - .resource_mut::>() - .drain() - .collect(); - assert_eq!(events, expected_events); - } - - #[test] - fn add_child() { - let world = &mut World::new(); - world.insert_resource(Events::::default()); - - let [a, b, c, d] = core::array::from_fn(|_| world.spawn_empty().id()); - - world.entity_mut(a).add_child(b); - - assert_parent(world, b, Some(a)); - assert_children(world, a, Some(&[b])); - assert_events( - world, - &[ChildAdded { - child: b, - parent: a, - }], - ); - - world.entity_mut(a).add_child(c); - - assert_children(world, a, Some(&[b, c])); - assert_parent(world, c, Some(a)); - assert_events( - world, - &[ChildAdded { - child: c, - parent: a, - }], - ); - // Children component should be removed when it's empty. - world.entity_mut(d).add_child(b).add_child(c); - assert_children(world, a, None); - } - - #[test] - fn set_parent() { - let world = &mut World::new(); - world.insert_resource(Events::::default()); - - let [a, b, c] = core::array::from_fn(|_| world.spawn_empty().id()); - - world.entity_mut(a).set_parent(b); - - assert_parent(world, a, Some(b)); - assert_children(world, b, Some(&[a])); - assert_events( - world, - &[ChildAdded { - child: a, - parent: b, - }], - ); - - world.entity_mut(a).set_parent(c); - - assert_parent(world, a, Some(c)); - assert_children(world, b, None); - assert_children(world, c, Some(&[a])); - assert_events( - world, - &[ChildMoved { - child: a, - previous_parent: b, - new_parent: c, - }], - ); - } - - // regression test for https://github.com/bevyengine/bevy/pull/8346 - #[test] - fn set_parent_of_orphan() { - let world = &mut World::new(); - - let [a, b, c] = core::array::from_fn(|_| world.spawn_empty().id()); - world.entity_mut(a).set_parent(b); - assert_parent(world, a, Some(b)); - assert_children(world, b, Some(&[a])); - - world.entity_mut(b).despawn(); - world.entity_mut(a).set_parent(c); - - assert_parent(world, a, Some(c)); - assert_children(world, c, Some(&[a])); - } - - #[test] - fn remove_parent() { - let world = &mut World::new(); - world.insert_resource(Events::::default()); - - let [a, b, c] = core::array::from_fn(|_| world.spawn_empty().id()); - - world.entity_mut(a).add_children(&[b, c]); - world.entity_mut(b).remove_parent(); - - assert_parent(world, b, None); - assert_parent(world, c, Some(a)); - assert_children(world, a, Some(&[c])); - omit_events(world, 2); // Omit ChildAdded events. - assert_events( - world, - &[ChildRemoved { - child: b, - parent: a, - }], - ); - - world.entity_mut(c).remove_parent(); - assert_parent(world, c, None); - assert_children(world, a, None); - assert_events( - world, - &[ChildRemoved { - child: c, - parent: a, - }], - ); - } - - #[expect( - dead_code, - reason = "The inner field is used to differentiate the different instances of this struct." - )] - #[derive(Component)] - struct C(u32); - - #[test] - fn build_children() { - let mut world = World::default(); - let mut queue = CommandQueue::default(); - let mut commands = Commands::new(&mut queue, &world); - - let parent = commands.spawn(C(1)).id(); - let mut children = Vec::new(); - commands.entity(parent).with_children(|parent| { - children.extend([ - parent.spawn(C(2)).id(), - parent.spawn(C(3)).id(), - parent.spawn(C(4)).id(), - ]); - }); - - queue.apply(&mut world); - assert_eq!( - world.get::(parent).unwrap().0.as_slice(), - children.as_slice(), - ); - assert_eq!(*world.get::(children[0]).unwrap(), Parent(parent)); - assert_eq!(*world.get::(children[1]).unwrap(), Parent(parent)); - - assert_eq!(*world.get::(children[0]).unwrap(), Parent(parent)); - assert_eq!(*world.get::(children[1]).unwrap(), Parent(parent)); - } - - #[test] - fn build_child() { - let mut world = World::default(); - let mut queue = CommandQueue::default(); - let mut commands = Commands::new(&mut queue, &world); - - let parent = commands.spawn(C(1)).id(); - commands.entity(parent).with_child(C(2)); - - queue.apply(&mut world); - assert_eq!(world.get::(parent).unwrap().0.len(), 1); - } - - #[test] - fn push_and_insert_and_remove_children_commands() { - let mut world = World::default(); - let entities = world - .spawn_batch(vec![C(1), C(2), C(3), C(4), C(5)]) - .collect::>(); - - let mut queue = CommandQueue::default(); - { - let mut commands = Commands::new(&mut queue, &world); - commands.entity(entities[0]).add_children(&entities[1..3]); - } - queue.apply(&mut world); - - let parent = entities[0]; - let child1 = entities[1]; - let child2 = entities[2]; - let child3 = entities[3]; - let child4 = entities[4]; - - let expected_children: SmallVec<[Entity; 8]> = smallvec![child1, child2]; - assert_eq!( - world.get::(parent).unwrap().0.clone(), - expected_children - ); - assert_eq!(*world.get::(child1).unwrap(), Parent(parent)); - assert_eq!(*world.get::(child2).unwrap(), Parent(parent)); - - assert_eq!(*world.get::(child1).unwrap(), Parent(parent)); - assert_eq!(*world.get::(child2).unwrap(), Parent(parent)); - - { - let mut commands = Commands::new(&mut queue, &world); - commands.entity(parent).insert_children(1, &entities[3..]); - } - queue.apply(&mut world); - - let expected_children: SmallVec<[Entity; 8]> = smallvec![child1, child3, child4, child2]; - assert_eq!( - world.get::(parent).unwrap().0.clone(), - expected_children - ); - assert_eq!(*world.get::(child3).unwrap(), Parent(parent)); - assert_eq!(*world.get::(child4).unwrap(), Parent(parent)); - - let remove_children = [child1, child4]; - { - let mut commands = Commands::new(&mut queue, &world); - commands.entity(parent).remove_children(&remove_children); - } - queue.apply(&mut world); - - let expected_children: SmallVec<[Entity; 8]> = smallvec![child3, child2]; - assert_eq!( - world.get::(parent).unwrap().0.clone(), - expected_children - ); - assert!(world.get::(child1).is_none()); - assert!(world.get::(child4).is_none()); - } - - #[test] - fn push_and_clear_children_commands() { - let mut world = World::default(); - let entities = world - .spawn_batch(vec![C(1), C(2), C(3), C(4), C(5)]) - .collect::>(); - - let mut queue = CommandQueue::default(); - { - let mut commands = Commands::new(&mut queue, &world); - commands.entity(entities[0]).add_children(&entities[1..3]); - } - queue.apply(&mut world); - - let parent = entities[0]; - let child1 = entities[1]; - let child2 = entities[2]; - - let expected_children: SmallVec<[Entity; 8]> = smallvec![child1, child2]; - assert_eq!( - world.get::(parent).unwrap().0.clone(), - expected_children - ); - assert_eq!(*world.get::(child1).unwrap(), Parent(parent)); - assert_eq!(*world.get::(child2).unwrap(), Parent(parent)); - - { - let mut commands = Commands::new(&mut queue, &world); - commands.entity(parent).clear_children(); - } - queue.apply(&mut world); - - assert!(world.get::(parent).is_none()); - - assert!(world.get::(child1).is_none()); - assert!(world.get::(child2).is_none()); - } - - #[test] - fn push_and_replace_children_commands() { - let mut world = World::default(); - let entities = world - .spawn_batch(vec![C(1), C(2), C(3), C(4), C(5)]) - .collect::>(); - - let mut queue = CommandQueue::default(); - { - let mut commands = Commands::new(&mut queue, &world); - commands.entity(entities[0]).add_children(&entities[1..3]); - } - queue.apply(&mut world); - - let parent = entities[0]; - let child1 = entities[1]; - let child2 = entities[2]; - let child4 = entities[4]; - - let expected_children: SmallVec<[Entity; 8]> = smallvec![child1, child2]; - assert_eq!( - world.get::(parent).unwrap().0.clone(), - expected_children - ); - assert_eq!(*world.get::(child1).unwrap(), Parent(parent)); - assert_eq!(*world.get::(child2).unwrap(), Parent(parent)); - - let replace_children = [child1, child4]; - { - let mut commands = Commands::new(&mut queue, &world); - commands.entity(parent).replace_children(&replace_children); - } - queue.apply(&mut world); - - let expected_children: SmallVec<[Entity; 8]> = smallvec![child1, child4]; - assert_eq!( - world.get::(parent).unwrap().0.clone(), - expected_children - ); - assert_eq!(*world.get::(child1).unwrap(), Parent(parent)); - assert_eq!(*world.get::(child4).unwrap(), Parent(parent)); - assert!(world.get::(child2).is_none()); - } - - #[test] - fn push_and_insert_and_remove_children_world() { - let mut world = World::default(); - let entities = world - .spawn_batch(vec![C(1), C(2), C(3), C(4), C(5)]) - .collect::>(); - - world.entity_mut(entities[0]).add_children(&entities[1..3]); - - let parent = entities[0]; - let child1 = entities[1]; - let child2 = entities[2]; - let child3 = entities[3]; - let child4 = entities[4]; - - let expected_children: SmallVec<[Entity; 8]> = smallvec![child1, child2]; - assert_eq!( - world.get::(parent).unwrap().0.clone(), - expected_children - ); - assert_eq!(*world.get::(child1).unwrap(), Parent(parent)); - assert_eq!(*world.get::(child2).unwrap(), Parent(parent)); - - world.entity_mut(parent).insert_children(1, &entities[3..]); - let expected_children: SmallVec<[Entity; 8]> = smallvec![child1, child3, child4, child2]; - assert_eq!( - world.get::(parent).unwrap().0.clone(), - expected_children - ); - assert_eq!(*world.get::(child3).unwrap(), Parent(parent)); - assert_eq!(*world.get::(child4).unwrap(), Parent(parent)); - - let remove_children = [child1, child4]; - world.entity_mut(parent).remove_children(&remove_children); - let expected_children: SmallVec<[Entity; 8]> = smallvec![child3, child2]; - assert_eq!( - world.get::(parent).unwrap().0.clone(), - expected_children - ); - assert!(world.get::(child1).is_none()); - assert!(world.get::(child4).is_none()); - } - - #[test] - fn push_and_insert_and_clear_children_world() { - let mut world = World::default(); - let entities = world - .spawn_batch(vec![C(1), C(2), C(3)]) - .collect::>(); - - world.entity_mut(entities[0]).add_children(&entities[1..3]); - - let parent = entities[0]; - let child1 = entities[1]; - let child2 = entities[2]; - - let expected_children: SmallVec<[Entity; 8]> = smallvec![child1, child2]; - assert_eq!( - world.get::(parent).unwrap().0.clone(), - expected_children - ); - assert_eq!(*world.get::(child1).unwrap(), Parent(parent)); - assert_eq!(*world.get::(child2).unwrap(), Parent(parent)); - - world.entity_mut(parent).clear_children(); - assert!(world.get::(parent).is_none()); - assert!(world.get::(child1).is_none()); - assert!(world.get::(child2).is_none()); - } - - #[test] - fn push_and_replace_children_world() { - let mut world = World::default(); - let entities = world - .spawn_batch(vec![C(1), C(2), C(3), C(4), C(5)]) - .collect::>(); - - world.entity_mut(entities[0]).add_children(&entities[1..3]); - - let parent = entities[0]; - let child1 = entities[1]; - let child2 = entities[2]; - let child3 = entities[3]; - let child4 = entities[4]; - - let expected_children: SmallVec<[Entity; 8]> = smallvec![child1, child2]; - assert_eq!( - world.get::(parent).unwrap().0.clone(), - expected_children - ); - assert_eq!(*world.get::(child1).unwrap(), Parent(parent)); - assert_eq!(*world.get::(child2).unwrap(), Parent(parent)); - - world.entity_mut(parent).replace_children(&entities[2..]); - let expected_children: SmallVec<[Entity; 8]> = smallvec![child2, child3, child4]; - assert_eq!( - world.get::(parent).unwrap().0.clone(), - expected_children - ); - assert!(world.get::(child1).is_none()); - assert_eq!(*world.get::(child2).unwrap(), Parent(parent)); - assert_eq!(*world.get::(child3).unwrap(), Parent(parent)); - assert_eq!(*world.get::(child4).unwrap(), Parent(parent)); - } - - /// Tests what happens when all children are removed from a parent using world functions - #[test] - fn children_removed_when_empty_world() { - let mut world = World::default(); - let entities = world - .spawn_batch(vec![C(1), C(2), C(3)]) - .collect::>(); - - let parent1 = entities[0]; - let parent2 = entities[1]; - let child = entities[2]; - - // add child into parent1 - world.entity_mut(parent1).add_children(&[child]); - assert_eq!( - world.get::(parent1).unwrap().0.as_slice(), - &[child] - ); - - // move only child from parent1 with `add_children` - world.entity_mut(parent2).add_children(&[child]); - assert!(world.get::(parent1).is_none()); - - // move only child from parent2 with `insert_children` - world.entity_mut(parent1).insert_children(0, &[child]); - assert!(world.get::(parent2).is_none()); - - // remove only child from parent1 with `remove_children` - world.entity_mut(parent1).remove_children(&[child]); - assert!(world.get::(parent1).is_none()); - } - - /// Tests what happens when all children are removed form a parent using commands - #[test] - fn children_removed_when_empty_commands() { - let mut world = World::default(); - let entities = world - .spawn_batch(vec![C(1), C(2), C(3)]) - .collect::>(); - - let parent1 = entities[0]; - let parent2 = entities[1]; - let child = entities[2]; - - let mut queue = CommandQueue::default(); - - // add child into parent1 - { - let mut commands = Commands::new(&mut queue, &world); - commands.entity(parent1).add_children(&[child]); - queue.apply(&mut world); - } - assert_eq!( - world.get::(parent1).unwrap().0.as_slice(), - &[child] - ); - - // move only child from parent1 with `add_children` - { - let mut commands = Commands::new(&mut queue, &world); - commands.entity(parent2).add_children(&[child]); - queue.apply(&mut world); - } - assert!(world.get::(parent1).is_none()); - - // move only child from parent2 with `insert_children` - { - let mut commands = Commands::new(&mut queue, &world); - commands.entity(parent1).insert_children(0, &[child]); - queue.apply(&mut world); - } - assert!(world.get::(parent2).is_none()); - - // move only child from parent1 with `add_child` - { - let mut commands = Commands::new(&mut queue, &world); - commands.entity(parent2).add_child(child); - queue.apply(&mut world); - } - assert!(world.get::(parent1).is_none()); - - // remove only child from parent2 with `remove_children` - { - let mut commands = Commands::new(&mut queue, &world); - commands.entity(parent2).remove_children(&[child]); - queue.apply(&mut world); - } - assert!(world.get::(parent2).is_none()); - } - - #[test] - fn regression_add_children_same_archetype() { - let mut world = World::new(); - let child = world.spawn_empty().id(); - world.spawn_empty().add_children(&[child]); - } - - #[test] - fn add_children_idempotent() { - let mut world = World::new(); - let child = world.spawn_empty().id(); - let parent = world - .spawn_empty() - .add_children(&[child]) - .add_children(&[child]) - .id(); - - let mut query = world.query::<&Children>(); - let children = query.get(&world, parent).unwrap(); - assert_eq!(**children, [child]); - } - - #[test] - fn add_children_does_not_insert_empty_children() { - let mut world = World::new(); - let parent = world.spawn_empty().add_children(&[]).id(); - - let mut query = world.query::<&Children>(); - let children = query.get(&world, parent); - assert!(children.is_err()); - } - - #[test] - fn with_child() { - let world = &mut World::new(); - world.insert_resource(Events::::default()); - - let a = world.spawn_empty().id(); - let b = (); - let c = (); - let d = (); - - world.entity_mut(a).with_child(b); - - assert_num_children(world, a, 1); - - world.entity_mut(a).with_child(c).with_child(d); - - assert_num_children(world, a, 3); - } -} diff --git a/crates/bevy_hierarchy/src/components/children.rs b/crates/bevy_hierarchy/src/components/children.rs deleted file mode 100644 index 4780d31eb2e67..0000000000000 --- a/crates/bevy_hierarchy/src/components/children.rs +++ /dev/null @@ -1,177 +0,0 @@ -#[cfg(feature = "reflect")] -use bevy_ecs::reflect::{ - ReflectComponent, ReflectFromWorld, ReflectMapEntities, ReflectVisitEntities, - ReflectVisitEntitiesMut, -}; -use bevy_ecs::{ - component::{Component, ComponentCloneHandler, Mutable, StorageType}, - entity::{Entity, VisitEntitiesMut}, - prelude::FromWorld, - world::World, -}; -use core::{ops::Deref, slice}; -use smallvec::SmallVec; - -/// Contains references to the child entities of this entity. -/// -/// Each child must contain a [`Parent`] component that points back to this entity. -/// This component rarely needs to be created manually, -/// consider using higher level utilities like [`BuildChildren::with_children`] -/// which are safer and easier to use. -/// -/// See [`HierarchyQueryExt`] for hierarchy related methods on [`Query`]. -/// -/// [`HierarchyQueryExt`]: crate::query_extension::HierarchyQueryExt -/// [`Query`]: bevy_ecs::system::Query -/// [`Parent`]: crate::components::parent::Parent -/// [`BuildChildren::with_children`]: crate::child_builder::BuildChildren::with_children -#[derive(Debug, VisitEntitiesMut)] -#[cfg_attr(feature = "reflect", derive(bevy_reflect::Reflect))] -#[cfg_attr( - feature = "reflect", - reflect( - Component, - MapEntities, - VisitEntities, - VisitEntitiesMut, - Debug, - FromWorld - ) -)] -pub struct Children(pub(crate) SmallVec<[Entity; 8]>); - -impl Component for Children { - const STORAGE_TYPE: StorageType = StorageType::Table; - type Mutability = Mutable; - - fn get_component_clone_handler() -> ComponentCloneHandler { - ComponentCloneHandler::ignore() - } -} - -// TODO: We need to impl either FromWorld or Default so Children can be registered as Reflect. -// This is because Reflect deserialize by creating an instance and apply a patch on top. -// However Children should only ever be set with a real user-defined entities. Its worth looking -// into better ways to handle cases like this. -impl FromWorld for Children { - #[inline] - fn from_world(_world: &mut World) -> Self { - Children(SmallVec::new()) - } -} - -impl Children { - /// Constructs a [`Children`] component with the given entities. - #[inline] - pub(crate) fn from_entities(entities: &[Entity]) -> Self { - Self(SmallVec::from_slice(entities)) - } - - /// Swaps the child at `a_index` with the child at `b_index`. - #[inline] - pub fn swap(&mut self, a_index: usize, b_index: usize) { - self.0.swap(a_index, b_index); - } - - /// Sorts children [stably](https://en.wikipedia.org/wiki/Sorting_algorithm#Stability) - /// in place using the provided comparator function. - /// - /// For the underlying implementation, see [`slice::sort_by`]. - /// - /// For the unstable version, see [`sort_unstable_by`](Children::sort_unstable_by). - /// - /// See also [`sort_by_key`](Children::sort_by_key), [`sort_by_cached_key`](Children::sort_by_cached_key). - #[inline] - pub fn sort_by(&mut self, compare: F) - where - F: FnMut(&Entity, &Entity) -> core::cmp::Ordering, - { - self.0.sort_by(compare); - } - - /// Sorts children [stably](https://en.wikipedia.org/wiki/Sorting_algorithm#Stability) - /// in place using the provided key extraction function. - /// - /// For the underlying implementation, see [`slice::sort_by_key`]. - /// - /// For the unstable version, see [`sort_unstable_by_key`](Children::sort_unstable_by_key). - /// - /// See also [`sort_by`](Children::sort_by), [`sort_by_cached_key`](Children::sort_by_cached_key). - #[inline] - pub fn sort_by_key(&mut self, compare: F) - where - F: FnMut(&Entity) -> K, - K: Ord, - { - self.0.sort_by_key(compare); - } - - /// Sorts children [stably](https://en.wikipedia.org/wiki/Sorting_algorithm#Stability) - /// in place using the provided key extraction function. Only evaluates each key at most - /// once per sort, caching the intermediate results in memory. - /// - /// For the underlying implementation, see [`slice::sort_by_cached_key`]. - /// - /// See also [`sort_by`](Children::sort_by), [`sort_by_key`](Children::sort_by_key). - #[inline] - pub fn sort_by_cached_key(&mut self, compare: F) - where - F: FnMut(&Entity) -> K, - K: Ord, - { - self.0.sort_by_cached_key(compare); - } - - /// Sorts children [unstably](https://en.wikipedia.org/wiki/Sorting_algorithm#Stability) - /// in place using the provided comparator function. - /// - /// For the underlying implementation, see [`slice::sort_unstable_by`]. - /// - /// For the stable version, see [`sort_by`](Children::sort_by). - /// - /// See also [`sort_unstable_by_key`](Children::sort_unstable_by_key). - #[inline] - pub fn sort_unstable_by(&mut self, compare: F) - where - F: FnMut(&Entity, &Entity) -> core::cmp::Ordering, - { - self.0.sort_unstable_by(compare); - } - - /// Sorts children [unstably](https://en.wikipedia.org/wiki/Sorting_algorithm#Stability) - /// in place using the provided key extraction function. - /// - /// For the underlying implementation, see [`slice::sort_unstable_by_key`]. - /// - /// For the stable version, see [`sort_by_key`](Children::sort_by_key). - /// - /// See also [`sort_unstable_by`](Children::sort_unstable_by). - #[inline] - pub fn sort_unstable_by_key(&mut self, compare: F) - where - F: FnMut(&Entity) -> K, - K: Ord, - { - self.0.sort_unstable_by_key(compare); - } -} - -impl Deref for Children { - type Target = [Entity]; - - #[inline(always)] - fn deref(&self) -> &Self::Target { - &self.0[..] - } -} - -impl<'a> IntoIterator for &'a Children { - type Item = ::Item; - - type IntoIter = slice::Iter<'a, Entity>; - - #[inline(always)] - fn into_iter(self) -> Self::IntoIter { - self.0.iter() - } -} diff --git a/crates/bevy_hierarchy/src/components/mod.rs b/crates/bevy_hierarchy/src/components/mod.rs deleted file mode 100644 index 3c8b544850382..0000000000000 --- a/crates/bevy_hierarchy/src/components/mod.rs +++ /dev/null @@ -1,5 +0,0 @@ -mod children; -mod parent; - -pub use children::Children; -pub use parent::Parent; diff --git a/crates/bevy_hierarchy/src/components/parent.rs b/crates/bevy_hierarchy/src/components/parent.rs deleted file mode 100644 index 4fc97aa914a24..0000000000000 --- a/crates/bevy_hierarchy/src/components/parent.rs +++ /dev/null @@ -1,100 +0,0 @@ -#[cfg(feature = "reflect")] -use bevy_ecs::reflect::{ - ReflectComponent, ReflectFromWorld, ReflectMapEntities, ReflectVisitEntities, - ReflectVisitEntitiesMut, -}; -use bevy_ecs::{ - component::{Component, ComponentCloneHandler, Mutable, StorageType}, - entity::{Entity, VisitEntities, VisitEntitiesMut}, - traversal::Traversal, - world::{FromWorld, World}, -}; -use core::ops::Deref; - -/// Holds a reference to the parent entity of this entity. -/// This component should only be present on entities that actually have a parent entity. -/// -/// Parent entity must have this entity stored in its [`Children`] component. -/// It is hard to set up parent/child relationships manually, -/// consider using higher level utilities like [`BuildChildren::with_children`]. -/// -/// See [`HierarchyQueryExt`] for hierarchy related methods on [`Query`]. -/// -/// [`HierarchyQueryExt`]: crate::query_extension::HierarchyQueryExt -/// [`Query`]: bevy_ecs::system::Query -/// [`Children`]: super::children::Children -/// [`BuildChildren::with_children`]: crate::child_builder::BuildChildren::with_children -#[derive(Debug, Eq, PartialEq, VisitEntities, VisitEntitiesMut)] -#[cfg_attr(feature = "reflect", derive(bevy_reflect::Reflect))] -#[cfg_attr( - feature = "reflect", - reflect( - Component, - MapEntities, - VisitEntities, - VisitEntitiesMut, - PartialEq, - Debug, - FromWorld - ) -)] -pub struct Parent(pub(crate) Entity); - -impl Component for Parent { - const STORAGE_TYPE: StorageType = StorageType::Table; - type Mutability = Mutable; - - fn get_component_clone_handler() -> ComponentCloneHandler { - ComponentCloneHandler::ignore() - } -} - -impl Parent { - /// Gets the [`Entity`] ID of the parent. - #[inline(always)] - pub fn get(&self) -> Entity { - self.0 - } - - /// Gets the parent [`Entity`] as a slice of length 1. - /// - /// Useful for making APIs that require a type or homogeneous storage - /// for both [`Children`] & [`Parent`] that is agnostic to edge direction. - /// - /// [`Children`]: super::children::Children - #[inline(always)] - pub fn as_slice(&self) -> &[Entity] { - core::slice::from_ref(&self.0) - } -} - -// TODO: We need to impl either FromWorld or Default so Parent can be registered as Reflect. -// This is because Reflect deserialize by creating an instance and apply a patch on top. -// However Parent should only ever be set with a real user-defined entity. Its worth looking into -// better ways to handle cases like this. -impl FromWorld for Parent { - #[inline(always)] - fn from_world(_world: &mut World) -> Self { - Parent(Entity::PLACEHOLDER) - } -} - -impl Deref for Parent { - type Target = Entity; - - #[inline(always)] - fn deref(&self) -> &Self::Target { - &self.0 - } -} - -/// This provides generalized hierarchy traversal for use in [event propagation]. -/// -/// `Parent::traverse` will never form loops in properly-constructed hierarchies. -/// -/// [event propagation]: bevy_ecs::observer::Trigger::propagate -impl Traversal for &Parent { - fn traverse(item: Self::Item<'_>, _data: &D) -> Option { - Some(item.0) - } -} diff --git a/crates/bevy_hierarchy/src/events.rs b/crates/bevy_hierarchy/src/events.rs deleted file mode 100644 index 5a667fef7789b..0000000000000 --- a/crates/bevy_hierarchy/src/events.rs +++ /dev/null @@ -1,34 +0,0 @@ -use bevy_ecs::{event::Event, prelude::Entity}; -#[cfg(feature = "reflect")] -use bevy_reflect::Reflect; - -/// An [`Event`] that is fired whenever there is a change in the world's hierarchy. -/// -/// [`Event`]: bevy_ecs::event::Event -#[derive(Event, Debug, Clone, PartialEq, Eq)] -#[cfg_attr(feature = "reflect", derive(Reflect), reflect(Debug, PartialEq))] -pub enum HierarchyEvent { - /// Fired whenever an [`Entity`] is added as a child to a parent. - ChildAdded { - /// The child that was added - child: Entity, - /// The parent the child was added to - parent: Entity, - }, - /// Fired whenever a child [`Entity`] is removed from its parent. - ChildRemoved { - /// The child that was removed - child: Entity, - /// The parent the child was removed from - parent: Entity, - }, - /// Fired whenever a child [`Entity`] is moved to a new parent. - ChildMoved { - /// The child that was moved - child: Entity, - /// The parent the child was removed from - previous_parent: Entity, - /// The parent the child was added to - new_parent: Entity, - }, -} diff --git a/crates/bevy_hierarchy/src/hierarchy.rs b/crates/bevy_hierarchy/src/hierarchy.rs deleted file mode 100644 index 5b89149154c0b..0000000000000 --- a/crates/bevy_hierarchy/src/hierarchy.rs +++ /dev/null @@ -1,505 +0,0 @@ -use crate::{ - components::{Children, Parent}, - BuildChildren, -}; -use bevy_ecs::{ - component::ComponentCloneHandler, - entity::{ComponentCloneCtx, Entity, EntityCloneBuilder}, - system::{error_handler, EntityCommands}, - world::{DeferredWorld, EntityWorldMut, World}, -}; -use log::debug; - -/// Function for despawning an entity and all its children -pub fn despawn_with_children_recursive(world: &mut World, entity: Entity, warn: bool) { - // first, make the entity's own parent forget about it - if let Some(parent) = world.get::(entity).map(|parent| parent.0) { - if let Some(mut children) = world.get_mut::(parent) { - children.0.retain(|c| *c != entity); - } - } - - // then despawn the entity and all of its children - despawn_with_children_recursive_inner(world, entity, warn); -} - -// Should only be called by `despawn_with_children_recursive` and `despawn_children_recursive`! -fn despawn_with_children_recursive_inner(world: &mut World, entity: Entity, warn: bool) { - if let Some(mut children) = world.get_mut::(entity) { - for e in core::mem::take(&mut children.0) { - despawn_with_children_recursive_inner(world, e, warn); - } - } - - if warn { - if !world.despawn(entity) { - debug!("Failed to despawn entity {}", entity); - } - } else if !world.try_despawn(entity) { - debug!("Failed to despawn entity {}", entity); - } -} - -fn despawn_children_recursive(world: &mut World, entity: Entity, warn: bool) { - if let Some(children) = world.entity_mut(entity).take::() { - for e in children.0 { - despawn_with_children_recursive_inner(world, e, warn); - } - } -} - -/// Trait that holds functions for despawning recursively down the transform hierarchy -pub trait DespawnRecursiveExt { - /// Despawns the provided entity alongside all descendants. - fn despawn_recursive(self); - - /// Despawns all descendants of the given entity. - fn despawn_descendants(&mut self) -> &mut Self; - - /// Similar to [`Self::despawn_recursive`] but does not emit warnings - fn try_despawn_recursive(self); - - /// Similar to [`Self::despawn_descendants`] but does not emit warnings - fn try_despawn_descendants(&mut self) -> &mut Self; -} - -impl DespawnRecursiveExt for EntityCommands<'_> { - /// Despawns the provided entity and its children. - /// This will emit warnings for any entity that does not exist. - fn despawn_recursive(mut self) { - let warn = true; - self.queue_handled( - move |mut entity: EntityWorldMut| { - let id = entity.id(); - #[cfg(feature = "trace")] - let _span = tracing::info_span!( - "command", - name = "DespawnRecursive", - entity = tracing::field::debug(id), - warn = tracing::field::debug(warn) - ) - .entered(); - entity.world_scope(|world| { - despawn_with_children_recursive(world, id, warn); - }); - }, - error_handler::warn(), - ); - } - - fn despawn_descendants(&mut self) -> &mut Self { - let warn = true; - self.queue_handled( - move |mut entity: EntityWorldMut| { - let id = entity.id(); - #[cfg(feature = "trace")] - let _span = tracing::info_span!( - "command", - name = "DespawnChildrenRecursive", - entity = tracing::field::debug(id), - warn = tracing::field::debug(warn) - ) - .entered(); - entity.world_scope(|world| { - despawn_children_recursive(world, id, warn); - }); - }, - error_handler::warn(), - ); - self - } - - /// Despawns the provided entity and its children. - /// This will never emit warnings. - fn try_despawn_recursive(mut self) { - let warn = false; - self.queue_handled( - move |mut entity: EntityWorldMut| { - let id = entity.id(); - #[cfg(feature = "trace")] - let _span = tracing::info_span!( - "command", - name = "TryDespawnRecursive", - entity = tracing::field::debug(id), - warn = tracing::field::debug(warn) - ) - .entered(); - entity.world_scope(|world| { - despawn_with_children_recursive(world, id, warn); - }); - }, - error_handler::silent(), - ); - } - - fn try_despawn_descendants(&mut self) -> &mut Self { - let warn = false; - self.queue_handled( - move |mut entity: EntityWorldMut| { - let id = entity.id(); - #[cfg(feature = "trace")] - let _span = tracing::info_span!( - "command", - name = "TryDespawnChildrenRecursive", - entity = tracing::field::debug(id), - warn = tracing::field::debug(warn) - ) - .entered(); - entity.world_scope(|world| { - despawn_children_recursive(world, id, warn); - }); - }, - error_handler::silent(), - ); - self - } -} - -fn despawn_recursive_inner(world: EntityWorldMut, warn: bool) { - let entity = world.id(); - - #[cfg(feature = "trace")] - let _span = tracing::info_span!( - "despawn_recursive", - entity = tracing::field::debug(entity), - warn = tracing::field::debug(warn) - ) - .entered(); - - despawn_with_children_recursive(world.into_world_mut(), entity, warn); -} - -fn despawn_descendants_inner<'v, 'w>( - world: &'v mut EntityWorldMut<'w>, - warn: bool, -) -> &'v mut EntityWorldMut<'w> { - let entity = world.id(); - - #[cfg(feature = "trace")] - let _span = tracing::info_span!( - "despawn_descendants", - entity = tracing::field::debug(entity), - warn = tracing::field::debug(warn) - ) - .entered(); - - world.world_scope(|world| { - despawn_children_recursive(world, entity, warn); - }); - world -} - -impl<'w> DespawnRecursiveExt for EntityWorldMut<'w> { - /// Despawns the provided entity and its children. - /// This will emit warnings for any entity that does not exist. - fn despawn_recursive(self) { - despawn_recursive_inner(self, true); - } - - fn despawn_descendants(&mut self) -> &mut Self { - despawn_descendants_inner(self, true) - } - - /// Despawns the provided entity and its children. - /// This will not emit warnings. - fn try_despawn_recursive(self) { - despawn_recursive_inner(self, false); - } - - fn try_despawn_descendants(&mut self) -> &mut Self { - despawn_descendants_inner(self, false) - } -} - -/// Trait that holds functions for cloning entities recursively down the hierarchy -pub trait CloneEntityHierarchyExt { - /// Sets the option to recursively clone entities. - /// When set to true all children will be cloned with the same options as the parent. - fn recursive(&mut self, recursive: bool) -> &mut Self; - /// Sets the option to add cloned entity as a child to the parent entity. - fn as_child(&mut self, as_child: bool) -> &mut Self; -} - -impl CloneEntityHierarchyExt for EntityCloneBuilder<'_> { - fn recursive(&mut self, recursive: bool) -> &mut Self { - if recursive { - self.override_component_clone_handler::( - ComponentCloneHandler::custom_handler(component_clone_children), - ) - } else { - self.remove_component_clone_handler_override::() - } - } - fn as_child(&mut self, as_child: bool) -> &mut Self { - if as_child { - self.override_component_clone_handler::(ComponentCloneHandler::custom_handler( - component_clone_parent, - )) - } else { - self.remove_component_clone_handler_override::() - } - } -} - -/// Clone handler for the [`Children`] component. Allows to clone the entity recursively. -fn component_clone_children(world: &mut DeferredWorld, ctx: &mut ComponentCloneCtx) { - let children = ctx - .read_source_component::() - .expect("Source entity must have Children component") - .iter(); - let parent = ctx.target(); - for child in children { - let child_clone = world.commands().spawn_empty().id(); - let mut clone_entity = ctx - .entity_cloner() - .with_source_and_target(*child, child_clone); - world.commands().queue(move |world: &mut World| { - clone_entity.clone_entity(world); - world.entity_mut(child_clone).set_parent(parent); - }); - } -} - -/// Clone handler for the [`Parent`] component. Allows to add clone as a child to the parent entity. -fn component_clone_parent(world: &mut DeferredWorld, ctx: &mut ComponentCloneCtx) { - let parent = ctx - .read_source_component::() - .map(|p| p.0) - .expect("Source entity must have Parent component"); - world.commands().entity(ctx.target()).set_parent(parent); -} - -#[cfg(test)] -mod tests { - use alloc::{borrow::ToOwned, string::String, vec, vec::Vec}; - use bevy_ecs::{ - component::Component, - system::Commands, - world::{CommandQueue, World}, - }; - - use super::DespawnRecursiveExt; - use crate::{ - child_builder::{BuildChildren, ChildBuild}, - components::Children, - CloneEntityHierarchyExt, - }; - - #[derive(Component, Clone, Copy, PartialEq, Eq, Ord, PartialOrd, Debug)] - struct Idx(u32); - - #[derive(Component, Clone, PartialEq, Eq, Ord, PartialOrd, Debug)] - struct N(String); - - #[test] - fn despawn_recursive() { - let mut world = World::default(); - let mut queue = CommandQueue::default(); - let grandparent_entity; - { - let mut commands = Commands::new(&mut queue, &world); - - commands - .spawn((N("Another parent".to_owned()), Idx(0))) - .with_children(|parent| { - parent.spawn((N("Another child".to_owned()), Idx(1))); - }); - - // Create a grandparent entity which will _not_ be deleted - grandparent_entity = commands.spawn((N("Grandparent".to_owned()), Idx(2))).id(); - commands.entity(grandparent_entity).with_children(|parent| { - // Add a child to the grandparent (the "parent"), which will get deleted - parent - .spawn((N("Parent, to be deleted".to_owned()), Idx(3))) - // All descendants of the "parent" should also be deleted. - .with_children(|parent| { - parent - .spawn((N("First Child, to be deleted".to_owned()), Idx(4))) - .with_children(|parent| { - // child - parent.spawn(( - N("First grand child, to be deleted".to_owned()), - Idx(5), - )); - }); - parent.spawn((N("Second child, to be deleted".to_owned()), Idx(6))); - }); - }); - - commands.spawn((N("An innocent bystander".to_owned()), Idx(7))); - } - queue.apply(&mut world); - - let parent_entity = world.get::(grandparent_entity).unwrap()[0]; - - { - let mut commands = Commands::new(&mut queue, &world); - commands.entity(parent_entity).despawn_recursive(); - // despawning the same entity twice should not panic - commands.entity(parent_entity).despawn_recursive(); - } - queue.apply(&mut world); - - let mut results = world - .query::<(&N, &Idx)>() - .iter(&world) - .map(|(a, b)| (a.clone(), *b)) - .collect::>(); - results.sort_unstable_by_key(|(_, index)| *index); - - { - let children = world.get::(grandparent_entity).unwrap(); - assert!( - !children.iter().any(|&i| i == parent_entity), - "grandparent should no longer know about its child which has been removed" - ); - } - - assert_eq!( - results, - vec![ - (N("Another parent".to_owned()), Idx(0)), - (N("Another child".to_owned()), Idx(1)), - (N("Grandparent".to_owned()), Idx(2)), - (N("An innocent bystander".to_owned()), Idx(7)) - ] - ); - } - - #[test] - fn despawn_descendants() { - let mut world = World::default(); - let mut queue = CommandQueue::default(); - let mut commands = Commands::new(&mut queue, &world); - - let parent = commands.spawn_empty().id(); - let child = commands.spawn_empty().id(); - - commands - .entity(parent) - .add_child(child) - .despawn_descendants(); - - queue.apply(&mut world); - - // The parent's Children component should be removed. - assert!(world.entity(parent).get::().is_none()); - // The child should be despawned. - assert!(world.get_entity(child).is_err()); - } - - #[test] - fn spawn_children_after_despawn_descendants() { - let mut world = World::default(); - let mut queue = CommandQueue::default(); - let mut commands = Commands::new(&mut queue, &world); - - let parent = commands.spawn_empty().id(); - let child = commands.spawn_empty().id(); - - commands - .entity(parent) - .add_child(child) - .despawn_descendants() - .with_children(|parent| { - parent.spawn_empty(); - parent.spawn_empty(); - }); - - queue.apply(&mut world); - - // The parent's Children component should still have two children. - let children = world.entity(parent).get::(); - assert!(children.is_some()); - assert_eq!(children.unwrap().len(), 2_usize); - // The original child should be despawned. - assert!(world.get_entity(child).is_err()); - } - - #[test] - fn clone_entity_recursive() { - #[derive(Component, PartialEq, Eq, Clone)] - struct Component1 { - field: usize, - } - - let parent_component = Component1 { field: 10 }; - let child1_component = Component1 { field: 20 }; - let child1_1_component = Component1 { field: 30 }; - let child2_component = Component1 { field: 21 }; - let child2_1_component = Component1 { field: 31 }; - - let mut world = World::default(); - - let mut queue = CommandQueue::default(); - let e_clone = { - let mut commands = Commands::new(&mut queue, &world); - let e = commands - .spawn(parent_component.clone()) - .with_children(|children| { - children - .spawn(child1_component.clone()) - .with_children(|children| { - children.spawn(child1_1_component.clone()); - }); - children - .spawn(child2_component.clone()) - .with_children(|children| { - children.spawn(child2_1_component.clone()); - }); - }) - .id(); - let e_clone = commands - .entity(e) - .clone_and_spawn_with(|builder| { - builder.recursive(true); - }) - .id(); - e_clone - }; - queue.apply(&mut world); - - assert!(world - .get::(e_clone) - .is_some_and(|c| *c == parent_component)); - - let children = world.get::(e_clone).unwrap(); - for (child, (component1, component2)) in children.iter().zip([ - (child1_component, child1_1_component), - (child2_component, child2_1_component), - ]) { - assert!(world - .get::(*child) - .is_some_and(|c| *c == component1)); - for child2 in world.get::(*child).unwrap().iter() { - assert!(world - .get::(*child2) - .is_some_and(|c| *c == component2)); - } - } - } - - #[test] - fn clone_entity_as_child() { - let mut world = World::default(); - let mut queue = CommandQueue::default(); - let mut commands = Commands::new(&mut queue, &world); - - let child = commands.spawn_empty().id(); - let parent = commands.spawn_empty().add_child(child).id(); - - let child_clone = commands - .entity(child) - .clone_and_spawn_with(|builder| { - builder.as_child(true); - }) - .id(); - - queue.apply(&mut world); - - assert!(world - .entity(parent) - .get::() - .is_some_and(|c| c.contains(&child_clone))); - } -} diff --git a/crates/bevy_hierarchy/src/lib.rs b/crates/bevy_hierarchy/src/lib.rs deleted file mode 100644 index 2e8beea501f7d..0000000000000 --- a/crates/bevy_hierarchy/src/lib.rs +++ /dev/null @@ -1,110 +0,0 @@ -#![cfg_attr(docsrs, feature(doc_auto_cfg))] -#![forbid(unsafe_code)] -#![doc( - html_logo_url = "https://bevyengine.org/assets/icon.png", - html_favicon_url = "https://bevyengine.org/assets/icon.png" -)] -#![no_std] - -//! Parent-child relationships for Bevy entities. -//! -//! You should use the tools in this crate -//! whenever you want to organize your entities in a hierarchical fashion, -//! to make groups of entities more manageable, -//! or to propagate properties throughout the entity hierarchy. -//! -//! This crate introduces various tools, including a [plugin] -//! for managing parent-child relationships between entities. -//! It provides two components, [`Parent`] and [`Children`], -//! to store references to related entities. -//! It also provides [command and world] API extensions -//! to set and clear those relationships. -//! -//! More advanced users may also appreciate -//! [query extension methods] to traverse hierarchies, -//! and [events] to notify hierarchical changes. -//! There is also a [diagnostic plugin] to validate property propagation. -//! -//! # Hierarchy management -//! -//! The methods defined in this crate fully manage -//! the components responsible for defining the entity hierarchy. -//! Mutating these components manually may result in hierarchy invalidation. -//! -//! Hierarchical relationships are always managed symmetrically. -//! For example, assigning a child to an entity -//! will always set the parent in the other, -//! and vice versa. -//! Similarly, unassigning a child in the parent -//! will always unassign the parent in the child. -//! -//! ## Despawning entities -//! -//! The commands and methods provided by `bevy_ecs` to despawn entities -//! are not capable of automatically despawning hierarchies of entities. -//! In most cases, these operations will invalidate the hierarchy. -//! Instead, you should use the provided [hierarchical despawn extension methods]. -//! -//! [command and world]: BuildChildren -//! [diagnostic plugin]: ValidParentCheckPlugin -//! [events]: HierarchyEvent -//! [hierarchical despawn extension methods]: DespawnRecursiveExt -//! [plugin]: HierarchyPlugin -//! [query extension methods]: HierarchyQueryExt - -#[cfg(feature = "std")] -extern crate std; - -extern crate alloc; - -mod components; -pub use components::*; - -mod hierarchy; -pub use hierarchy::*; - -mod child_builder; -pub use child_builder::*; - -mod events; -pub use events::*; - -mod valid_parent_check_plugin; -pub use valid_parent_check_plugin::*; - -mod query_extension; -pub use query_extension::*; - -/// The hierarchy prelude. -/// -/// This includes the most common types in this crate, re-exported for your convenience. -pub mod prelude { - #[doc(hidden)] - pub use crate::{child_builder::*, components::*, hierarchy::*, query_extension::*}; - - #[doc(hidden)] - #[cfg(feature = "bevy_app")] - pub use crate::{HierarchyPlugin, ValidParentCheckPlugin}; -} - -#[cfg(feature = "bevy_app")] -use bevy_app::prelude::*; - -/// Provides hierarchy functionality to a Bevy app. -/// -/// Check the [crate-level documentation] for all the features. -/// -/// [crate-level documentation]: crate -#[cfg(feature = "bevy_app")] -#[derive(Default)] -pub struct HierarchyPlugin; - -#[cfg(feature = "bevy_app")] -impl Plugin for HierarchyPlugin { - fn build(&self, app: &mut App) { - #[cfg(feature = "reflect")] - app.register_type::().register_type::(); - - app.add_event::(); - } -} diff --git a/crates/bevy_hierarchy/src/query_extension.rs b/crates/bevy_hierarchy/src/query_extension.rs deleted file mode 100644 index 1ab3ec9385fea..0000000000000 --- a/crates/bevy_hierarchy/src/query_extension.rs +++ /dev/null @@ -1,435 +0,0 @@ -use alloc::collections::VecDeque; - -use bevy_ecs::{ - entity::Entity, - query::{QueryData, QueryFilter, WorldQuery}, - system::Query, -}; -use smallvec::SmallVec; - -use crate::{Children, Parent}; - -/// An extension trait for [`Query`] that adds hierarchy related methods. -pub trait HierarchyQueryExt<'w, 's, D: QueryData, F: QueryFilter> { - /// Returns the parent [`Entity`] of the given `entity`, if any. - fn parent(&'w self, entity: Entity) -> Option - where - D::ReadOnly: WorldQuery = &'w Parent>; - - /// Returns a slice over the [`Children`] of the given `entity`. - /// - /// This may be empty if the `entity` has no children. - fn children(&'w self, entity: Entity) -> &'w [Entity] - where - D::ReadOnly: WorldQuery = &'w Children>; - - /// Returns the topmost ancestor of the given `entity`. - /// - /// This may be the entity itself if it has no parent. - fn root_ancestor(&'w self, entity: Entity) -> Entity - where - D::ReadOnly: WorldQuery = &'w Parent>; - - /// Returns an [`Iterator`] of [`Entity`]s over the leaves of the hierarchy that are underneath this `entity`. - /// - /// Only entities which have no children are considered leaves. - /// This will not include the entity itself, and will not include any entities which are not descendants of the entity, - /// even if they are leaves in the same hierarchical tree. - /// - /// Traverses the hierarchy depth-first. - fn iter_leaves(&'w self, entity: Entity) -> impl Iterator + 'w - where - D::ReadOnly: WorldQuery = &'w Children>; - - /// Returns an [`Iterator`] of [`Entity`]s over the `entity`s immediate siblings, who share the same parent. - /// - /// The entity itself is not included in the iterator. - fn iter_siblings(&'w self, entity: Entity) -> impl Iterator - where - D::ReadOnly: WorldQuery = (Option<&'w Parent>, Option<&'w Children>)>; - - /// Returns an [`Iterator`] of [`Entity`]s over all of `entity`s descendants. - /// - /// Can only be called on a [`Query`] of [`Children`] (i.e. `Query<&Children>`). - /// - /// Traverses the hierarchy breadth-first and does not include the entity itself. - /// - /// # Examples - /// ``` - /// # use bevy_ecs::prelude::*; - /// # use bevy_hierarchy::prelude::*; - /// # #[derive(Component)] - /// # struct Marker; - /// fn system(entity: Single>, children_query: Query<&Children>) { - /// for descendant in children_query.iter_descendants(*entity) { - /// // Do something! - /// } - /// } - /// # bevy_ecs::system::assert_is_system(system); - /// ``` - fn iter_descendants(&'w self, entity: Entity) -> DescendantIter<'w, 's, D, F> - where - D::ReadOnly: WorldQuery = &'w Children>; - - /// Returns an [`Iterator`] of [`Entity`]s over all of `entity`s descendants. - /// - /// Can only be called on a [`Query`] of [`Children`] (i.e. `Query<&Children>`). - /// - /// This is a depth-first alternative to [`HierarchyQueryExt::iter_descendants`]. - fn iter_descendants_depth_first( - &'w self, - entity: Entity, - ) -> DescendantDepthFirstIter<'w, 's, D, F> - where - D::ReadOnly: WorldQuery = &'w Children>; - - /// Returns an [`Iterator`] of [`Entity`]s over all of `entity`s ancestors. - /// - /// Does not include the entity itself. - /// Can only be called on a [`Query`] of [`Parent`] (i.e. `Query<&Parent>`). - /// - /// # Examples - /// ``` - /// # use bevy_ecs::prelude::*; - /// # use bevy_hierarchy::prelude::*; - /// # #[derive(Component)] - /// # struct Marker; - /// fn system(entity: Single>, parent_query: Query<&Parent>) { - /// for ancestor in parent_query.iter_ancestors(*entity) { - /// // Do something! - /// } - /// } - /// # bevy_ecs::system::assert_is_system(system); - /// ``` - fn iter_ancestors(&'w self, entity: Entity) -> AncestorIter<'w, 's, D, F> - where - D::ReadOnly: WorldQuery = &'w Parent>; -} - -impl<'w, 's, D: QueryData, F: QueryFilter> HierarchyQueryExt<'w, 's, D, F> for Query<'w, 's, D, F> { - fn parent(&'w self, entity: Entity) -> Option - where - ::ReadOnly: WorldQuery = &'w Parent>, - { - self.get(entity).map(Parent::get).ok() - } - - fn children(&'w self, entity: Entity) -> &'w [Entity] - where - ::ReadOnly: WorldQuery = &'w Children>, - { - self.get(entity) - .map_or(&[] as &[Entity], |children| children) - } - - fn root_ancestor(&'w self, entity: Entity) -> Entity - where - ::ReadOnly: WorldQuery = &'w Parent>, - { - // Recursively search up the tree until we're out of parents - match self.get(entity) { - Ok(parent) => self.root_ancestor(parent.get()), - Err(_) => entity, - } - } - - fn iter_leaves(&'w self, entity: Entity) -> impl Iterator - where - ::ReadOnly: WorldQuery = &'w Children>, - { - self.iter_descendants_depth_first(entity).filter(|entity| { - self.get(*entity) - .ok() - // These are leaf nodes if they have the `Children` component but it's empty - // Or if they don't have the `Children` component at all - .is_none_or(|children| children.is_empty()) - }) - } - - fn iter_siblings(&'w self, entity: Entity) -> impl Iterator - where - D::ReadOnly: WorldQuery = (Option<&'w Parent>, Option<&'w Children>)>, - { - self.get(entity) - .ok() - .and_then(|(maybe_parent, _)| maybe_parent.map(Parent::get)) - .and_then(|parent| self.get(parent).ok()) - .and_then(|(_, maybe_children)| maybe_children) - .into_iter() - .flat_map(move |children| children.iter().filter(move |child| **child != entity)) - .copied() - } - - fn iter_descendants(&'w self, entity: Entity) -> DescendantIter<'w, 's, D, F> - where - D::ReadOnly: WorldQuery = &'w Children>, - { - DescendantIter::new(self, entity) - } - - fn iter_descendants_depth_first( - &'w self, - entity: Entity, - ) -> DescendantDepthFirstIter<'w, 's, D, F> - where - D::ReadOnly: WorldQuery = &'w Children>, - { - DescendantDepthFirstIter::new(self, entity) - } - - fn iter_ancestors(&'w self, entity: Entity) -> AncestorIter<'w, 's, D, F> - where - D::ReadOnly: WorldQuery = &'w Parent>, - { - AncestorIter::new(self, entity) - } -} - -/// An [`Iterator`] of [`Entity`]s over the descendants of an [`Entity`]. -/// -/// Traverses the hierarchy breadth-first. -pub struct DescendantIter<'w, 's, D: QueryData, F: QueryFilter> -where - D::ReadOnly: WorldQuery = &'w Children>, -{ - children_query: &'w Query<'w, 's, D, F>, - vecdeque: VecDeque, -} - -impl<'w, 's, D: QueryData, F: QueryFilter> DescendantIter<'w, 's, D, F> -where - D::ReadOnly: WorldQuery = &'w Children>, -{ - /// Returns a new [`DescendantIter`]. - pub fn new(children_query: &'w Query<'w, 's, D, F>, entity: Entity) -> Self { - DescendantIter { - children_query, - vecdeque: children_query - .get(entity) - .into_iter() - .flatten() - .copied() - .collect(), - } - } -} - -impl<'w, 's, D: QueryData, F: QueryFilter> Iterator for DescendantIter<'w, 's, D, F> -where - D::ReadOnly: WorldQuery = &'w Children>, -{ - type Item = Entity; - - fn next(&mut self) -> Option { - let entity = self.vecdeque.pop_front()?; - - if let Ok(children) = self.children_query.get(entity) { - self.vecdeque.extend(children); - } - - Some(entity) - } -} - -/// An [`Iterator`] of [`Entity`]s over the descendants of an [`Entity`]. -/// -/// Traverses the hierarchy depth-first. -pub struct DescendantDepthFirstIter<'w, 's, D: QueryData, F: QueryFilter> -where - D::ReadOnly: WorldQuery = &'w Children>, -{ - children_query: &'w Query<'w, 's, D, F>, - stack: SmallVec<[Entity; 8]>, -} - -impl<'w, 's, D: QueryData, F: QueryFilter> DescendantDepthFirstIter<'w, 's, D, F> -where - D::ReadOnly: WorldQuery = &'w Children>, -{ - /// Returns a new [`DescendantDepthFirstIter`]. - pub fn new(children_query: &'w Query<'w, 's, D, F>, entity: Entity) -> Self { - DescendantDepthFirstIter { - children_query, - stack: children_query - .get(entity) - .map_or(SmallVec::new(), |children| { - children.iter().rev().copied().collect() - }), - } - } -} - -impl<'w, 's, D: QueryData, F: QueryFilter> Iterator for DescendantDepthFirstIter<'w, 's, D, F> -where - D::ReadOnly: WorldQuery = &'w Children>, -{ - type Item = Entity; - - fn next(&mut self) -> Option { - let entity = self.stack.pop()?; - - if let Ok(children) = self.children_query.get(entity) { - self.stack.extend(children.iter().rev().copied()); - } - - Some(entity) - } -} - -/// An [`Iterator`] of [`Entity`]s over the ancestors of an [`Entity`]. -pub struct AncestorIter<'w, 's, D: QueryData, F: QueryFilter> -where - D::ReadOnly: WorldQuery = &'w Parent>, -{ - parent_query: &'w Query<'w, 's, D, F>, - next: Option, -} - -impl<'w, 's, D: QueryData, F: QueryFilter> AncestorIter<'w, 's, D, F> -where - D::ReadOnly: WorldQuery = &'w Parent>, -{ - /// Returns a new [`AncestorIter`]. - pub fn new(parent_query: &'w Query<'w, 's, D, F>, entity: Entity) -> Self { - AncestorIter { - parent_query, - next: Some(entity), - } - } -} - -impl<'w, 's, D: QueryData, F: QueryFilter> Iterator for AncestorIter<'w, 's, D, F> -where - D::ReadOnly: WorldQuery = &'w Parent>, -{ - type Item = Entity; - - fn next(&mut self) -> Option { - self.next = self.parent_query.get(self.next?).ok().map(Parent::get); - self.next - } -} - -#[cfg(test)] -mod tests { - use alloc::vec::Vec; - use bevy_ecs::{ - prelude::Component, - system::{Query, SystemState}, - world::World, - }; - - use crate::{query_extension::HierarchyQueryExt, BuildChildren, Children, Parent}; - - #[derive(Component, PartialEq, Debug)] - struct A(usize); - - #[test] - fn descendant_iter() { - let world = &mut World::new(); - - let [a0, a1, a2, a3] = core::array::from_fn(|i| world.spawn(A(i)).id()); - - world.entity_mut(a0).add_children(&[a1, a2]); - world.entity_mut(a1).add_children(&[a3]); - - let mut system_state = SystemState::<(Query<&Children>, Query<&A>)>::new(world); - let (children_query, a_query) = system_state.get(world); - - let result: Vec<_> = a_query - .iter_many(children_query.iter_descendants(a0)) - .collect(); - - assert_eq!([&A(1), &A(2), &A(3)], result.as_slice()); - } - - #[test] - fn descendant_depth_first_iter() { - let world = &mut World::new(); - - let [a0, a1, a2, a3] = core::array::from_fn(|i| world.spawn(A(i)).id()); - - world.entity_mut(a0).add_children(&[a1, a2]); - world.entity_mut(a1).add_children(&[a3]); - - let mut system_state = SystemState::<(Query<&Children>, Query<&A>)>::new(world); - let (children_query, a_query) = system_state.get(world); - - let result: Vec<_> = a_query - .iter_many(children_query.iter_descendants_depth_first(a0)) - .collect(); - - assert_eq!([&A(1), &A(3), &A(2)], result.as_slice()); - } - - #[test] - fn ancestor_iter() { - let world = &mut World::new(); - - let [a0, a1, a2] = core::array::from_fn(|i| world.spawn(A(i)).id()); - - world.entity_mut(a0).add_children(&[a1]); - world.entity_mut(a1).add_children(&[a2]); - - let mut system_state = SystemState::<(Query<&Parent>, Query<&A>)>::new(world); - let (parent_query, a_query) = system_state.get(world); - - let result: Vec<_> = a_query.iter_many(parent_query.iter_ancestors(a2)).collect(); - - assert_eq!([&A(1), &A(0)], result.as_slice()); - } - - #[test] - fn root_ancestor() { - let world = &mut World::new(); - - let [a0, a1, a2] = core::array::from_fn(|i| world.spawn(A(i)).id()); - - world.entity_mut(a0).add_children(&[a1]); - world.entity_mut(a1).add_children(&[a2]); - - let mut system_state = SystemState::>::new(world); - let parent_query = system_state.get(world); - - assert_eq!(a0, parent_query.root_ancestor(a2)); - assert_eq!(a0, parent_query.root_ancestor(a1)); - assert_eq!(a0, parent_query.root_ancestor(a0)); - } - - #[test] - fn leaf_iter() { - let world = &mut World::new(); - - let [a0, a1, a2, a3] = core::array::from_fn(|i| world.spawn(A(i)).id()); - - world.entity_mut(a0).add_children(&[a1, a2]); - world.entity_mut(a1).add_children(&[a3]); - - let mut system_state = SystemState::<(Query<&Children>, Query<&A>)>::new(world); - let (children_query, a_query) = system_state.get(world); - - let result: Vec<_> = a_query.iter_many(children_query.iter_leaves(a0)).collect(); - - assert_eq!([&A(3), &A(2)], result.as_slice()); - } - - #[test] - fn siblings() { - let world = &mut World::new(); - - let [a0, a1, a2, a3, a4] = core::array::from_fn(|i| world.spawn(A(i)).id()); - - world.entity_mut(a0).add_children(&[a1, a2, a3]); - world.entity_mut(a2).add_children(&[a4]); - - let mut system_state = - SystemState::<(Query<(Option<&Parent>, Option<&Children>)>, Query<&A>)>::new(world); - let (hierarchy_query, a_query) = system_state.get(world); - - let result: Vec<_> = a_query - .iter_many(hierarchy_query.iter_siblings(a1)) - .collect(); - - assert_eq!([&A(2), &A(3)], result.as_slice()); - } -} diff --git a/crates/bevy_hierarchy/src/valid_parent_check_plugin.rs b/crates/bevy_hierarchy/src/valid_parent_check_plugin.rs deleted file mode 100644 index c17059c3f3b83..0000000000000 --- a/crates/bevy_hierarchy/src/valid_parent_check_plugin.rs +++ /dev/null @@ -1,104 +0,0 @@ -use core::marker::PhantomData; - -use bevy_ecs::prelude::*; - -#[cfg(feature = "bevy_app")] -use {crate::Parent, alloc::format, bevy_utils::HashSet, disqualified::ShortName}; - -/// When enabled, runs [`check_hierarchy_component_has_valid_parent`]. -/// -/// This resource is added by [`ValidParentCheckPlugin`]. -/// It is enabled on debug builds and disabled in release builds by default, -/// you can update this resource at runtime to change the default behavior. -#[derive(Resource)] -pub struct ReportHierarchyIssue { - /// Whether to run [`check_hierarchy_component_has_valid_parent`]. - pub enabled: bool, - _comp: PhantomData, -} - -impl ReportHierarchyIssue { - /// Constructs a new object - pub fn new(enabled: bool) -> Self { - ReportHierarchyIssue { - enabled, - _comp: Default::default(), - } - } -} - -impl PartialEq for ReportHierarchyIssue { - fn eq(&self, other: &Self) -> bool { - self.enabled == other.enabled - } -} - -impl Default for ReportHierarchyIssue { - fn default() -> Self { - Self { - enabled: cfg!(debug_assertions), - _comp: PhantomData, - } - } -} - -#[cfg(feature = "bevy_app")] -/// System to print a warning for each [`Entity`] with a `T` component -/// which parent hasn't a `T` component. -/// -/// Hierarchy propagations are top-down, and limited only to entities -/// with a specific component (such as `InheritedVisibility` and `GlobalTransform`). -/// This means that entities with one of those component -/// and a parent without the same component is probably a programming error. -/// (See B0004 explanation linked in warning message) -pub fn check_hierarchy_component_has_valid_parent( - parent_query: Query< - (Entity, &Parent, Option<&Name>), - (With, Or<(Changed, Added)>), - >, - component_query: Query<(), With>, - mut already_diagnosed: Local>, -) { - for (entity, parent, name) in &parent_query { - let parent = parent.get(); - if !component_query.contains(parent) && !already_diagnosed.contains(&entity) { - already_diagnosed.insert(entity); - log::warn!( - "warning[B0004]: {name} with the {ty_name} component has a parent without {ty_name}.\n\ - This will cause inconsistent behaviors! See: https://bevyengine.org/learn/errors/b0004", - ty_name = ShortName::of::(), - name = name.map_or_else(|| format!("Entity {}", entity), |s| format!("The {s} entity")), - ); - } - } -} - -/// Run criteria that only allows running when [`ReportHierarchyIssue`] is enabled. -pub fn on_hierarchy_reports_enabled(report: Res>) -> bool -where - T: Component, -{ - report.enabled -} - -/// Print a warning for each `Entity` with a `T` component -/// whose parent doesn't have a `T` component. -/// -/// See [`check_hierarchy_component_has_valid_parent`] for details. -pub struct ValidParentCheckPlugin(PhantomData T>); -impl Default for ValidParentCheckPlugin { - fn default() -> Self { - Self(PhantomData) - } -} - -#[cfg(feature = "bevy_app")] -impl bevy_app::Plugin for ValidParentCheckPlugin { - fn build(&self, app: &mut bevy_app::App) { - app.init_resource::>().add_systems( - bevy_app::Last, - check_hierarchy_component_has_valid_parent:: - .run_if(resource_equals(ReportHierarchyIssue::::new(true))), - ); - } -} diff --git a/crates/bevy_input_focus/Cargo.toml b/crates/bevy_input_focus/Cargo.toml index 628d50e4bf060..96c82a0507367 100644 --- a/crates/bevy_input_focus/Cargo.toml +++ b/crates/bevy_input_focus/Cargo.toml @@ -25,7 +25,6 @@ bevy_reflect = [ bevy_app = { path = "../bevy_app", version = "0.16.0-dev", default-features = false } bevy_ecs = { path = "../bevy_ecs", version = "0.16.0-dev", default-features = false } bevy_input = { path = "../bevy_input", version = "0.16.0-dev", default-features = false } -bevy_hierarchy = { path = "../bevy_hierarchy", version = "0.16.0-dev", default-features = false } bevy_math = { path = "../bevy_math", version = "0.16.0-dev", default-features = false } bevy_window = { path = "../bevy_window", version = "0.16.0-dev", default-features = false } bevy_reflect = { path = "../bevy_reflect", version = "0.16.0-dev", features = [ diff --git a/crates/bevy_input_focus/src/lib.rs b/crates/bevy_input_focus/src/lib.rs index 2e63ad339c830..c894c6f3d77f6 100644 --- a/crates/bevy_input_focus/src/lib.rs +++ b/crates/bevy_input_focus/src/lib.rs @@ -26,7 +26,6 @@ pub use autofocus::*; use bevy_app::{App, Plugin, PreUpdate, Startup}; use bevy_ecs::{prelude::*, query::QueryData, system::SystemParam, traversal::Traversal}; -use bevy_hierarchy::{HierarchyQueryExt, Parent}; use bevy_input::{gamepad::GamepadButtonChangedEvent, keyboard::KeyboardInput, mouse::MouseWheel}; #[cfg(feature = "bevy_reflect")] use bevy_reflect::{prelude::*, Reflect}; @@ -356,7 +355,6 @@ mod tests { use bevy_ecs::{ component::ComponentId, observer::Trigger, system::RunSystemOnce, world::DeferredWorld, }; - use bevy_hierarchy::BuildChildren; use bevy_input::{ keyboard::{Key, KeyCode}, ButtonState, InputPlugin, diff --git a/crates/bevy_input_focus/src/tab_navigation.rs b/crates/bevy_input_focus/src/tab_navigation.rs index 47a939cd1913d..8039938649adf 100644 --- a/crates/bevy_input_focus/src/tab_navigation.rs +++ b/crates/bevy_input_focus/src/tab_navigation.rs @@ -29,11 +29,11 @@ use bevy_ecs::prelude::ReflectComponent; use bevy_ecs::{ component::Component, entity::Entity, + hierarchy::{Children, Parent}, observer::Trigger, query::{With, Without}, system::{Commands, Query, Res, ResMut, SystemParam}, }; -use bevy_hierarchy::{Children, HierarchyQueryExt, Parent}; use bevy_input::{ keyboard::{KeyCode, KeyboardInput}, ButtonInput, ButtonState, @@ -362,7 +362,6 @@ pub fn handle_tab_navigation( #[cfg(test)] mod tests { use bevy_ecs::system::SystemState; - use bevy_hierarchy::BuildChildren; use super::*; @@ -371,10 +370,9 @@ mod tests { let mut app = App::new(); let world = app.world_mut(); - let tab_entity_1 = world.spawn(TabIndex(0)).id(); - let tab_entity_2 = world.spawn(TabIndex(1)).id(); - let mut tab_group_entity = world.spawn(TabGroup::new(0)); - tab_group_entity.replace_children(&[tab_entity_1, tab_entity_2]); + let tab_group_entity = world.spawn(TabGroup::new(0)).id(); + let tab_entity_1 = world.spawn((TabIndex(0), Parent(tab_group_entity))).id(); + let tab_entity_2 = world.spawn((TabIndex(1), Parent(tab_group_entity))).id(); let mut system_state: SystemState = SystemState::new(world); let tab_navigation = system_state.get(world); diff --git a/crates/bevy_internal/Cargo.toml b/crates/bevy_internal/Cargo.toml index d3c66ee4c5291..6011046b59d2d 100644 --- a/crates/bevy_internal/Cargo.toml +++ b/crates/bevy_internal/Cargo.toml @@ -18,7 +18,6 @@ trace = [ "bevy_log/trace", "bevy_pbr?/trace", "bevy_render?/trace", - "bevy_hierarchy/trace", "bevy_winit?/trace", ] trace_chrome = ["bevy_log/tracing-chrome"] @@ -273,7 +272,6 @@ bevy_derive = { path = "../bevy_derive", version = "0.16.0-dev" } bevy_diagnostic = { path = "../bevy_diagnostic", version = "0.16.0-dev" } bevy_ecs = { path = "../bevy_ecs", version = "0.16.0-dev" } bevy_state = { path = "../bevy_state", optional = true, version = "0.16.0-dev" } -bevy_hierarchy = { path = "../bevy_hierarchy", version = "0.16.0-dev" } bevy_input = { path = "../bevy_input", version = "0.16.0-dev" } bevy_input_focus = { path = "../bevy_input_focus", version = "0.16.0-dev" } bevy_log = { path = "../bevy_log", version = "0.16.0-dev" } diff --git a/crates/bevy_internal/src/default_plugins.rs b/crates/bevy_internal/src/default_plugins.rs index fe3cf6f9cf830..d9aee3017a2ee 100644 --- a/crates/bevy_internal/src/default_plugins.rs +++ b/crates/bevy_internal/src/default_plugins.rs @@ -9,7 +9,6 @@ plugin_group! { bevy_diagnostic:::FrameCountPlugin, bevy_time:::TimePlugin, bevy_transform:::TransformPlugin, - bevy_hierarchy:::HierarchyPlugin, bevy_diagnostic:::DiagnosticsPlugin, bevy_input:::InputPlugin, #[custom(cfg(not(feature = "bevy_window")))] diff --git a/crates/bevy_internal/src/lib.rs b/crates/bevy_internal/src/lib.rs index e9d25adbdf8bb..8750c566d010b 100644 --- a/crates/bevy_internal/src/lib.rs +++ b/crates/bevy_internal/src/lib.rs @@ -36,7 +36,6 @@ pub use bevy_gilrs as gilrs; pub use bevy_gizmos as gizmos; #[cfg(feature = "bevy_gltf")] pub use bevy_gltf as gltf; -pub use bevy_hierarchy as hierarchy; #[cfg(feature = "bevy_image")] pub use bevy_image as image; pub use bevy_input as input; diff --git a/crates/bevy_internal/src/prelude.rs b/crates/bevy_internal/src/prelude.rs index 42c84f134a124..1c19c7ccc14a0 100644 --- a/crates/bevy_internal/src/prelude.rs +++ b/crates/bevy_internal/src/prelude.rs @@ -1,8 +1,8 @@ #[doc(hidden)] pub use crate::{ - app::prelude::*, ecs::prelude::*, hierarchy::prelude::*, input::prelude::*, log::prelude::*, - math::prelude::*, reflect::prelude::*, time::prelude::*, transform::prelude::*, - utils::prelude::*, DefaultPlugins, MinimalPlugins, + app::prelude::*, ecs::prelude::*, input::prelude::*, log::prelude::*, math::prelude::*, + reflect::prelude::*, time::prelude::*, transform::prelude::*, utils::prelude::*, + DefaultPlugins, MinimalPlugins, }; #[doc(hidden)] diff --git a/crates/bevy_picking/Cargo.toml b/crates/bevy_picking/Cargo.toml index 011f55c995080..4f22e0b27dd85 100644 --- a/crates/bevy_picking/Cargo.toml +++ b/crates/bevy_picking/Cargo.toml @@ -17,7 +17,6 @@ bevy_app = { path = "../bevy_app", version = "0.16.0-dev" } bevy_asset = { path = "../bevy_asset", version = "0.16.0-dev" } bevy_derive = { path = "../bevy_derive", version = "0.16.0-dev" } bevy_ecs = { path = "../bevy_ecs", version = "0.16.0-dev" } -bevy_hierarchy = { path = "../bevy_hierarchy", version = "0.16.0-dev" } bevy_input = { path = "../bevy_input", version = "0.16.0-dev" } bevy_math = { path = "../bevy_math", version = "0.16.0-dev" } bevy_mesh = { path = "../bevy_mesh", version = "0.16.0-dev", optional = true } diff --git a/crates/bevy_picking/src/events.rs b/crates/bevy_picking/src/events.rs index ae3d879e9a47d..c580c1e59493a 100644 --- a/crates/bevy_picking/src/events.rs +++ b/crates/bevy_picking/src/events.rs @@ -40,7 +40,6 @@ use core::{fmt::Debug, time::Duration}; use bevy_ecs::{prelude::*, query::QueryData, system::SystemParam, traversal::Traversal}; -use bevy_hierarchy::Parent; use bevy_math::Vec2; use bevy_reflect::prelude::*; use bevy_render::camera::NormalizedRenderTarget; diff --git a/crates/bevy_picking/src/input.rs b/crates/bevy_picking/src/input.rs index 94e195e4b3c78..3c03ba11dd525 100644 --- a/crates/bevy_picking/src/input.rs +++ b/crates/bevy_picking/src/input.rs @@ -13,7 +13,6 @@ use bevy_app::prelude::*; use bevy_ecs::prelude::*; -use bevy_hierarchy::DespawnRecursiveExt; use bevy_input::{ prelude::*, touch::{TouchInput, TouchPhase}, @@ -268,6 +267,6 @@ pub fn deactivate_touch_pointers( // A hash set is used to prevent despawning the same entity twice. for (entity, pointer) in despawn_list.drain() { debug!("Despawning pointer {:?}", pointer); - commands.entity(entity).despawn_recursive(); + commands.entity(entity).despawn(); } } diff --git a/crates/bevy_remote/Cargo.toml b/crates/bevy_remote/Cargo.toml index 92a261ea7993a..c56be57330d58 100644 --- a/crates/bevy_remote/Cargo.toml +++ b/crates/bevy_remote/Cargo.toml @@ -19,7 +19,6 @@ bevy_derive = { path = "../bevy_derive", version = "0.16.0-dev" } bevy_ecs = { path = "../bevy_ecs", version = "0.16.0-dev", features = [ "serialize", ] } -bevy_hierarchy = { path = "../bevy_hierarchy", version = "0.16.0-dev" } bevy_reflect = { path = "../bevy_reflect", version = "0.16.0-dev" } bevy_tasks = { path = "../bevy_tasks", version = "0.16.0-dev" } bevy_utils = { path = "../bevy_utils", version = "0.16.0-dev" } diff --git a/crates/bevy_remote/src/builtin_methods.rs b/crates/bevy_remote/src/builtin_methods.rs index a36c2d92e8c1b..3e5c64d81dc7b 100644 --- a/crates/bevy_remote/src/builtin_methods.rs +++ b/crates/bevy_remote/src/builtin_methods.rs @@ -7,13 +7,13 @@ use bevy_ecs::{ component::ComponentId, entity::Entity, event::EventCursor, + hierarchy::Parent, query::QueryBuilder, reflect::{AppTypeRegistry, ReflectComponent, ReflectResource}, removal_detection::RemovedComponentEntity, system::{In, Local}, world::{EntityRef, EntityWorldMut, FilteredEntityRef, World}, }; -use bevy_hierarchy::BuildChildren as _; use bevy_reflect::{ prelude::ReflectDefault, serde::{ReflectSerializer, TypedReflectDeserializer}, @@ -841,7 +841,7 @@ pub fn process_remote_reparent_request( // If `None`, remove the entities' parents. else { for entity in entities { - get_entity_mut(world, entity)?.remove_parent(); + get_entity_mut(world, entity)?.remove::(); } } diff --git a/crates/bevy_render/Cargo.toml b/crates/bevy_render/Cargo.toml index 577ff685bb4c1..91c79a4a67869 100644 --- a/crates/bevy_render/Cargo.toml +++ b/crates/bevy_render/Cargo.toml @@ -44,7 +44,6 @@ bevy_derive = { path = "../bevy_derive", version = "0.16.0-dev" } bevy_diagnostic = { path = "../bevy_diagnostic", version = "0.16.0-dev" } bevy_ecs = { path = "../bevy_ecs", version = "0.16.0-dev" } bevy_encase_derive = { path = "../bevy_encase_derive", version = "0.16.0-dev" } -bevy_hierarchy = { path = "../bevy_hierarchy", version = "0.16.0-dev" } bevy_math = { path = "../bevy_math", version = "0.16.0-dev" } bevy_reflect = { path = "../bevy_reflect", version = "0.16.0-dev", features = [ "bevy", diff --git a/crates/bevy_render/src/lib.rs b/crates/bevy_render/src/lib.rs index 32bc6d0305278..b267d4bf5a379 100644 --- a/crates/bevy_render/src/lib.rs +++ b/crates/bevy_render/src/lib.rs @@ -72,7 +72,6 @@ use bevy_ecs::schedule::ScheduleBuildSettings; use bevy_utils::prelude::default; pub use extract_param::Extract; -use bevy_hierarchy::ValidParentCheckPlugin; use bevy_window::{PrimaryWindow, RawHandleWrapperHolder}; use extract_resource::ExtractResourcePlugin; use globals::GlobalsPlugin; @@ -349,7 +348,6 @@ impl Plugin for RenderPlugin { }; app.add_plugins(( - ValidParentCheckPlugin::::default(), WindowRenderPlugin, CameraPlugin, ViewPlugin, diff --git a/crates/bevy_render/src/mesh/mod.rs b/crates/bevy_render/src/mesh/mod.rs index 703333675da74..8725c0e3343cf 100644 --- a/crates/bevy_render/src/mesh/mod.rs +++ b/crates/bevy_render/src/mesh/mod.rs @@ -1,4 +1,3 @@ -use bevy_hierarchy::Children; use bevy_math::Vec3; pub use bevy_mesh::*; use morph::{MeshMorphWeights, MorphWeights}; @@ -16,13 +15,7 @@ use allocator::MeshAllocatorPlugin; use bevy_app::{App, Plugin, PostUpdate}; use bevy_asset::{AssetApp, AssetId, RenderAssetUsages}; use bevy_ecs::{ - entity::Entity, - query::{Changed, With}, - schedule::IntoSystemConfigs, - system::Query, -}; -use bevy_ecs::{ - query::Without, + prelude::*, system::{ lifetimeless::{SRes, SResMut}, SystemParamItem, diff --git a/crates/bevy_render/src/view/visibility/mod.rs b/crates/bevy_render/src/view/visibility/mod.rs index b3b7c15bb2687..d0c84bd70ad19 100644 --- a/crates/bevy_render/src/view/visibility/mod.rs +++ b/crates/bevy_render/src/view/visibility/mod.rs @@ -12,19 +12,18 @@ pub use render_layers::*; use bevy_app::{Plugin, PostUpdate}; use bevy_asset::Assets; -use bevy_ecs::prelude::*; -use bevy_hierarchy::{Children, Parent}; +use bevy_ecs::{hierarchy::validate_parent_has_component, prelude::*}; use bevy_reflect::{std_traits::ReflectDefault, Reflect}; use bevy_transform::{components::GlobalTransform, TransformSystem}; use bevy_utils::{Parallel, TypeIdMap}; use smallvec::SmallVec; use super::NoCpuCulling; -use crate::{camera::Projection, sync_world::MainEntity}; use crate::{ - camera::{Camera, CameraProjection}, + camera::{Camera, CameraProjection, Projection}, mesh::{Mesh, Mesh3d, MeshAabb}, primitives::{Aabb, Frustum, Sphere}, + sync_world::MainEntity, }; /// User indication of whether an entity is visible. Propagates down the entity hierarchy. @@ -111,6 +110,7 @@ impl PartialEq<&Visibility> for Visibility { /// [`VisibilityPropagate`]: VisibilitySystems::VisibilityPropagate #[derive(Component, Deref, Debug, Default, Clone, Copy, Reflect, PartialEq, Eq)] #[reflect(Component, Default, Debug, PartialEq)] +#[component(on_insert = validate_parent_has_component::)] pub struct InheritedVisibility(bool); impl InheritedVisibility { @@ -316,7 +316,7 @@ pub enum VisibilitySystems { /// Label for [`update_frusta`] in [`CameraProjectionPlugin`](crate::camera::CameraProjectionPlugin). UpdateFrusta, /// Label for the system propagating the [`InheritedVisibility`] in a - /// [`hierarchy`](bevy_hierarchy). + /// [`Parent`] / [`Children`] hierarchy. VisibilityPropagate, /// Label for the [`check_visibility`] system updating [`ViewVisibility`] /// of each entity and the [`VisibleEntities`] of each view.\ @@ -645,7 +645,6 @@ where mod test { use super::*; use bevy_app::prelude::*; - use bevy_hierarchy::BuildChildren; #[test] fn visibility_propagation() { @@ -762,7 +761,7 @@ mod test { .entity_mut(parent2) .insert(Visibility::Visible); // Simulate a change in the parent component - app.world_mut().entity_mut(child2).set_parent(parent2); // example of changing parent + app.world_mut().entity_mut(child2).insert(Parent(parent2)); // example of changing parent // Run the system again to propagate changes app.update(); diff --git a/crates/bevy_render/src/view/window/screenshot.rs b/crates/bevy_render/src/view/window/screenshot.rs index 01460efcad59b..81adc30486cd0 100644 --- a/crates/bevy_render/src/view/window/screenshot.rs +++ b/crates/bevy_render/src/view/window/screenshot.rs @@ -22,7 +22,6 @@ use bevy_derive::{Deref, DerefMut}; use bevy_ecs::{ entity::EntityHashMap, event::event_update_system, prelude::*, system::SystemState, }; -use bevy_hierarchy::DespawnRecursiveExt; use bevy_image::{Image, TextureFormatPixelInfo}; use bevy_reflect::Reflect; use bevy_tasks::AsyncComputeTaskPool; @@ -185,7 +184,7 @@ pub fn save_to_disk(path: impl AsRef) -> impl FnMut(Trigger>) { for entity in screenshots.iter() { - commands.entity(entity).despawn_recursive(); + commands.entity(entity).despawn(); } } @@ -240,7 +239,7 @@ fn extract_screenshots( entity, render_target ); // If we don't despawn the entity here, it will be captured again in the next frame - commands.entity(entity).despawn_recursive(); + commands.entity(entity).despawn(); continue; } seen_targets.insert(render_target.clone()); diff --git a/crates/bevy_scene/Cargo.toml b/crates/bevy_scene/Cargo.toml index 69a288fe5c77b..60004b2662c89 100644 --- a/crates/bevy_scene/Cargo.toml +++ b/crates/bevy_scene/Cargo.toml @@ -21,7 +21,6 @@ bevy_ecs = { path = "../bevy_ecs", version = "0.16.0-dev" } bevy_reflect = { path = "../bevy_reflect", version = "0.16.0-dev", features = [ "bevy", ] } -bevy_hierarchy = { path = "../bevy_hierarchy", version = "0.16.0-dev" } bevy_transform = { path = "../bevy_transform", version = "0.16.0-dev" } bevy_utils = { path = "../bevy_utils", version = "0.16.0-dev" } bevy_render = { path = "../bevy_render", version = "0.16.0-dev", optional = true } diff --git a/crates/bevy_scene/src/dynamic_scene.rs b/crates/bevy_scene/src/dynamic_scene.rs index f29c925f7e4d4..f8adb6d0704f1 100644 --- a/crates/bevy_scene/src/dynamic_scene.rs +++ b/crates/bevy_scene/src/dynamic_scene.rs @@ -202,11 +202,11 @@ mod tests { entity::{ Entity, EntityHashMap, EntityMapper, MapEntities, VisitEntities, VisitEntitiesMut, }, + hierarchy::Parent, reflect::{AppTypeRegistry, ReflectComponent, ReflectMapEntities, ReflectResource}, system::Resource, world::World, }; - use bevy_hierarchy::{BuildChildren, Parent}; use bevy_reflect::Reflect; use crate::dynamic_scene::DynamicScene; @@ -296,7 +296,7 @@ mod tests { // We then reload the scene to make sure that from_scene_parent_entity's parent component // isn't updated with the entity map, since this component isn't defined in the scene. - // With bevy_hierarchy, this can cause serious errors and malformed hierarchies. + // With [`bevy_ecs::hierarchy`], this can cause serious errors and malformed hierarchies. scene.write_to_world(&mut world, &mut entity_map).unwrap(); assert_eq!( diff --git a/crates/bevy_scene/src/scene_spawner.rs b/crates/bevy_scene/src/scene_spawner.rs index 0f47710446141..9d3c99996d15a 100644 --- a/crates/bevy_scene/src/scene_spawner.rs +++ b/crates/bevy_scene/src/scene_spawner.rs @@ -3,11 +3,11 @@ use bevy_asset::{AssetEvent, AssetId, Assets, Handle}; use bevy_ecs::{ entity::{Entity, EntityHashMap}, event::{Event, EventCursor, Events}, + hierarchy::Parent, reflect::AppTypeRegistry, system::Resource, world::{Mut, World}, }; -use bevy_hierarchy::{BuildChildren, DespawnRecursiveExt, Parent}; use bevy_reflect::Reflect; use bevy_utils::{HashMap, HashSet}; use thiserror::Error; @@ -201,9 +201,8 @@ impl SceneSpawner { pub fn despawn_instance_sync(&mut self, world: &mut World, instance_id: &InstanceId) { if let Some(instance) = self.spawned_instances.remove(instance_id) { for &entity in instance.entity_map.values() { - if let Ok(mut entity_mut) = world.get_entity_mut(entity) { - entity_mut.remove_parent(); - entity_mut.despawn_recursive(); + if let Ok(entity_mut) = world.get_entity_mut(entity) { + entity_mut.despawn(); }; } } @@ -519,6 +518,7 @@ mod tests { use bevy_asset::{AssetPlugin, AssetServer, Handle}; use bevy_ecs::{ component::Component, + hierarchy::Children, observer::Trigger, prelude::ReflectComponent, query::With, @@ -536,7 +536,6 @@ mod tests { entity::Entity, prelude::{AppTypeRegistry, World}, }; - use bevy_hierarchy::{Children, HierarchyPlugin}; #[derive(Component, Reflect, Default)] #[reflect(Component)] @@ -550,7 +549,6 @@ mod tests { let mut app = App::new(); app.add_plugins(ScheduleRunnerPlugin::default()) - .add_plugins(HierarchyPlugin) .add_plugins(AssetPlugin::default()) .add_plugins(ScenePlugin) .register_type::(); @@ -854,7 +852,7 @@ mod tests { .run_system_once( |mut commands: Commands, query: Query>| { for entity in query.iter() { - commands.entity(entity).despawn_recursive(); + commands.entity(entity).despawn(); } }, ) diff --git a/crates/bevy_state/Cargo.toml b/crates/bevy_state/Cargo.toml index bc32d18f6dd6f..18f75afec8d0c 100644 --- a/crates/bevy_state/Cargo.toml +++ b/crates/bevy_state/Cargo.toml @@ -9,7 +9,7 @@ license = "MIT OR Apache-2.0" keywords = ["bevy"] [features] -default = ["std", "bevy_reflect", "bevy_app", "bevy_hierarchy"] +default = ["std", "bevy_reflect", "bevy_app"] # Functionality @@ -17,28 +17,18 @@ default = ["std", "bevy_reflect", "bevy_app", "bevy_hierarchy"] bevy_reflect = [ "dep:bevy_reflect", "bevy_ecs/bevy_reflect", - "bevy_hierarchy?/reflect", "bevy_app?/bevy_reflect", ] ## Adds integration with the `bevy_app` plugin API. -bevy_app = ["dep:bevy_app", "bevy_hierarchy?/bevy_app"] - -## Adds integration with the `bevy_hierarchy` `Parent` and `Children` API. -bevy_hierarchy = ["dep:bevy_hierarchy"] +bevy_app = ["dep:bevy_app"] # Platform Compatibility ## Allows access to the `std` crate. Enabling this feature will prevent compilation ## on `no_std` targets, but provides access to certain additional features on ## supported platforms. -std = [ - "bevy_ecs/std", - "bevy_utils/std", - "bevy_reflect?/std", - "bevy_app?/std", - "bevy_hierarchy?/std", -] +std = ["bevy_ecs/std", "bevy_utils/std", "bevy_reflect?/std", "bevy_app?/std"] ## `critical-section` provides the building blocks for synchronization primitives ## on all platforms, including `no_std`. @@ -63,7 +53,6 @@ bevy_state_macros = { path = "macros", version = "0.16.0-dev" } bevy_utils = { path = "../bevy_utils", version = "0.16.0-dev", default-features = false } bevy_reflect = { path = "../bevy_reflect", version = "0.16.0-dev", default-features = false, optional = true } bevy_app = { path = "../bevy_app", version = "0.16.0-dev", default-features = false, optional = true } -bevy_hierarchy = { path = "../bevy_hierarchy", version = "0.16.0-dev", default-features = false, optional = true } variadics_please = "1.1" # other diff --git a/crates/bevy_state/src/state_scoped.rs b/crates/bevy_state/src/state_scoped.rs index 3e9f49f51734d..3344ccc32652a 100644 --- a/crates/bevy_state/src/state_scoped.rs +++ b/crates/bevy_state/src/state_scoped.rs @@ -6,8 +6,6 @@ use bevy_ecs::{ event::EventReader, system::{Commands, Query}, }; -#[cfg(feature = "bevy_hierarchy")] -use bevy_hierarchy::DespawnRecursiveExt; #[cfg(feature = "bevy_reflect")] use bevy_reflect::prelude::*; @@ -19,8 +17,6 @@ use crate::state::{StateTransitionEvent, States}; /// To enable this feature remember to add the attribute `#[states(scoped_entities)]` when deriving [`States`]. /// It's also possible to enable it when adding the state to an app with [`enable_state_scoped_entities`](crate::app::AppExtStates::enable_state_scoped_entities). /// -/// If `bevy_hierarchy` feature is enabled, which it is by default, the despawn will be recursive. -/// /// ``` /// use bevy_state::prelude::*; /// use bevy_ecs::prelude::*; @@ -62,8 +58,6 @@ pub struct StateScoped(pub S); /// Removes entities marked with [`StateScoped`] /// when their state no longer matches the world state. -/// -/// If `bevy_hierarchy` feature is enabled, which it is by default, the despawn will be recursive. pub fn clear_state_scoped_entities( mut commands: Commands, mut transitions: EventReader>, @@ -83,9 +77,6 @@ pub fn clear_state_scoped_entities( }; for (entity, binding) in &query { if binding.0 == *exited { - #[cfg(feature = "bevy_hierarchy")] - commands.entity(entity).despawn_recursive(); - #[cfg(not(feature = "bevy_hierarchy"))] commands.entity(entity).despawn(); } } diff --git a/crates/bevy_text/Cargo.toml b/crates/bevy_text/Cargo.toml index 2dcee021df8e1..dd4ed912a6f9d 100644 --- a/crates/bevy_text/Cargo.toml +++ b/crates/bevy_text/Cargo.toml @@ -18,7 +18,6 @@ bevy_asset = { path = "../bevy_asset", version = "0.16.0-dev" } bevy_color = { path = "../bevy_color", version = "0.16.0-dev" } bevy_derive = { path = "../bevy_derive", version = "0.16.0-dev" } bevy_ecs = { path = "../bevy_ecs", version = "0.16.0-dev" } -bevy_hierarchy = { path = "../bevy_hierarchy", version = "0.16.0-dev" } bevy_image = { path = "../bevy_image", version = "0.16.0-dev" } bevy_math = { path = "../bevy_math", version = "0.16.0-dev" } bevy_reflect = { path = "../bevy_reflect", version = "0.16.0-dev", features = [ diff --git a/crates/bevy_text/src/text.rs b/crates/bevy_text/src/text.rs index 06f8adf86c38f..0555d7a33884e 100644 --- a/crates/bevy_text/src/text.rs +++ b/crates/bevy_text/src/text.rs @@ -8,7 +8,6 @@ use bevy_asset::Handle; use bevy_color::Color; use bevy_derive::{Deref, DerefMut}; use bevy_ecs::{prelude::*, reflect::ReflectComponent}; -use bevy_hierarchy::{Children, Parent}; use bevy_reflect::prelude::*; use bevy_utils::once; use cosmic_text::{Buffer, Metrics}; @@ -169,7 +168,6 @@ impl TextLayout { /// # use bevy_color::palettes::basic::{RED, BLUE}; /// # use bevy_ecs::world::World; /// # use bevy_text::{Font, TextLayout, TextFont, TextSpan, TextColor}; -/// # use bevy_hierarchy::BuildChildren; /// /// # let font_handle: Handle = Default::default(); /// # let mut world = World::default(); @@ -510,7 +508,7 @@ pub fn detect_text_needs_rerender( )); continue; }; - let mut parent: Entity = **span_parent; + let mut parent: Entity = span_parent.0; // Search for the nearest ancestor with ComputedTextBlock. // Note: We assume the perf cost from duplicate visits in the case that multiple spans in a block are visited @@ -541,7 +539,7 @@ pub fn detect_text_needs_rerender( )); break; }; - parent = **next_parent; + parent = next_parent.0; } } } diff --git a/crates/bevy_text/src/text_access.rs b/crates/bevy_text/src/text_access.rs index 84943ea5667fa..7aafa28ef63e2 100644 --- a/crates/bevy_text/src/text_access.rs +++ b/crates/bevy_text/src/text_access.rs @@ -4,7 +4,6 @@ use bevy_ecs::{ prelude::*, system::{Query, SystemParam}, }; -use bevy_hierarchy::Children; use crate::{TextColor, TextFont, TextSpan}; diff --git a/crates/bevy_transform/Cargo.toml b/crates/bevy_transform/Cargo.toml index c9072ddf82283..706f38b682cdd 100644 --- a/crates/bevy_transform/Cargo.toml +++ b/crates/bevy_transform/Cargo.toml @@ -12,9 +12,6 @@ keywords = ["bevy"] # bevy bevy_app = { path = "../bevy_app", version = "0.16.0-dev", default-features = false, optional = true } bevy_ecs = { path = "../bevy_ecs", version = "0.16.0-dev", default-features = false, optional = true } -bevy_hierarchy = { path = "../bevy_hierarchy", version = "0.16.0-dev", default-features = false, features = [ - "bevy_app", -], optional = true } bevy_math = { path = "../bevy_math", version = "0.16.0-dev", default-features = false } bevy_reflect = { path = "../bevy_reflect", version = "0.16.0-dev", default-features = false, optional = true } serde = { version = "1", default-features = false, features = [ @@ -41,7 +38,7 @@ default = ["std", "bevy-support", "bevy_reflect"] ## systems for transform propagation and more. ## This exists because it allows opting out of all of this, leaving only a bare-bones transform struct, ## which enables users to depend on that without needing the larger Bevy dependency tree. -bevy-support = ["alloc", "dep:bevy_app", "dep:bevy_ecs", "dep:bevy_hierarchy"] +bevy-support = ["alloc", "dep:bevy_app", "dep:bevy_ecs"] ## Adds serialization support through `serde`. serialize = ["dep:serde", "bevy_math/serialize"] @@ -64,7 +61,6 @@ std = [ "alloc", "bevy_app?/std", "bevy_ecs?/std", - "bevy_hierarchy?/std", "bevy_math/std", "bevy_reflect?/std", "serde?/std", diff --git a/crates/bevy_transform/src/commands.rs b/crates/bevy_transform/src/commands.rs index dab624566910f..a3bc817ae2f94 100644 --- a/crates/bevy_transform/src/commands.rs +++ b/crates/bevy_transform/src/commands.rs @@ -1,17 +1,16 @@ -//! Extension to [`EntityCommands`] to modify `bevy_hierarchy` hierarchies +//! Extension to [`EntityCommands`] to modify [`bevy_ecs::hierarchy`] hierarchies. //! while preserving [`GlobalTransform`]. use crate::prelude::{GlobalTransform, Transform}; -use bevy_ecs::{entity::Entity, system::EntityCommands, world::EntityWorldMut}; -use bevy_hierarchy::BuildChildren; +use bevy_ecs::{entity::Entity, hierarchy::Parent, system::EntityCommands, world::EntityWorldMut}; -/// Collection of methods similar to [`BuildChildren`], but preserving each +/// Collection of methods similar to the built-in parenting methods on [`EntityWorldMut`] and [`EntityCommands`], but preserving each /// entity's [`GlobalTransform`]. pub trait BuildChildrenTransformExt { /// Change this entity's parent while preserving this entity's [`GlobalTransform`] /// by updating its [`Transform`]. /// - /// See [`BuildChildren::set_parent`] for a method that doesn't update the [`Transform`]. + /// Insert the [`Parent`] component directly if you don't want to also update the [`Transform`]. /// /// Note that both the hierarchy and transform updates will only execute /// the next time commands are applied @@ -21,7 +20,7 @@ pub trait BuildChildrenTransformExt { /// Make this entity parentless while preserving this entity's [`GlobalTransform`] /// by updating its [`Transform`] to be equal to its current [`GlobalTransform`]. /// - /// See [`BuildChildren::remove_parent`] for a method that doesn't update the [`Transform`]. + /// See [`EntityWorldMut::remove_parent`] or [`EntityCommands::remove_parent`] for a method that doesn't update the [`Transform`]. /// /// Note that both the hierarchy and transform updates will only execute /// the next time commands are applied @@ -65,7 +64,7 @@ impl BuildChildrenTransformExt for EntityWorldMut<'_> { fn remove_parent_in_place(&mut self) -> &mut Self { let child = self.id(); self.world_scope(|world| { - world.entity_mut(child).remove_parent(); + world.entity_mut(child).remove::(); // FIXME: Replace this closure with a `try` block. See: https://github.com/rust-lang/rust/issues/31436. let mut update_transform = || { let child_global = *world.get_entity(child).ok()?.get::()?; diff --git a/crates/bevy_transform/src/components/global_transform.rs b/crates/bevy_transform/src/components/global_transform.rs index eb244cae8e911..86858a20fbefa 100644 --- a/crates/bevy_transform/src/components/global_transform.rs +++ b/crates/bevy_transform/src/components/global_transform.rs @@ -8,7 +8,7 @@ use derive_more::derive::From; use bevy_reflect::{ReflectDeserialize, ReflectSerialize}; #[cfg(feature = "bevy-support")] -use bevy_ecs::component::Component; +use bevy_ecs::{component::Component, hierarchy::validate_parent_has_component}; #[cfg(feature = "bevy_reflect")] use { @@ -28,7 +28,7 @@ use { /// ## [`Transform`] and [`GlobalTransform`] /// /// [`Transform`] transforms an entity relative to its parent's reference frame, or relative to world space coordinates, -/// if it doesn't have a [`Parent`](bevy_hierarchy::Parent). +/// if it doesn't have a [`Parent`](bevy_ecs::hierarchy::Parent). /// /// [`GlobalTransform`] is managed by Bevy; it is computed by successively applying the [`Transform`] of each ancestor /// entity which has a Transform. This is done automatically by Bevy-internal systems in the system set @@ -45,7 +45,11 @@ use { /// [transform_example]: https://github.com/bevyengine/bevy/blob/latest/examples/transforms/transform.rs #[derive(Debug, PartialEq, Clone, Copy, From)] #[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))] -#[cfg_attr(feature = "bevy-support", derive(Component))] +#[cfg_attr( + feature = "bevy-support", + derive(Component), + component(on_insert = validate_parent_has_component::) +)] #[cfg_attr( feature = "bevy_reflect", derive(Reflect), @@ -156,7 +160,6 @@ impl GlobalTransform { /// ``` /// # use bevy_transform::prelude::{GlobalTransform, Transform}; /// # use bevy_ecs::prelude::{Entity, Query, Component, Commands}; - /// # use bevy_hierarchy::{prelude::Parent, BuildChildren}; /// #[derive(Component)] /// struct ToReparent { /// new_parent: Entity, diff --git a/crates/bevy_transform/src/components/transform.rs b/crates/bevy_transform/src/components/transform.rs index 718ca118de4e5..2b25f0b1cf39e 100644 --- a/crates/bevy_transform/src/components/transform.rs +++ b/crates/bevy_transform/src/components/transform.rs @@ -19,7 +19,7 @@ use {bevy_ecs::reflect::ReflectComponent, bevy_reflect::prelude::*}; /// ## [`Transform`] and [`GlobalTransform`] /// /// [`Transform`] is the position of an entity relative to its parent position, or the reference -/// frame if it doesn't have a [`Parent`](bevy_hierarchy::Parent). +/// frame if it doesn't have a [`Parent`](bevy_ecs::hierarchy::Parent). /// /// [`GlobalTransform`] is the position of an entity relative to the reference frame. /// diff --git a/crates/bevy_transform/src/helper.rs b/crates/bevy_transform/src/helper.rs index 75e9e313d32fb..acff381eac267 100644 --- a/crates/bevy_transform/src/helper.rs +++ b/crates/bevy_transform/src/helper.rs @@ -1,11 +1,11 @@ //! System parameter for computing up-to-date [`GlobalTransform`]s. use bevy_ecs::{ + hierarchy::Parent, prelude::Entity, query::QueryEntityError, system::{Query, SystemParam}, }; -use bevy_hierarchy::{HierarchyQueryExt, Parent}; use thiserror::Error; use crate::components::{GlobalTransform, Transform}; @@ -84,8 +84,7 @@ mod tests { use core::f32::consts::TAU; use bevy_app::App; - use bevy_ecs::system::SystemState; - use bevy_hierarchy::BuildChildren; + use bevy_ecs::{hierarchy::Parent, system::SystemState}; use bevy_math::{Quat, Vec3}; use crate::{ @@ -125,7 +124,7 @@ mod tests { let mut e = app.world_mut().spawn(transform); if let Some(entity) = entity { - e.set_parent(entity); + e.insert(Parent(entity)); } entity = Some(e.id()); diff --git a/crates/bevy_transform/src/plugins.rs b/crates/bevy_transform/src/plugins.rs index e1339962ef7f0..1add9713a0be9 100644 --- a/crates/bevy_transform/src/plugins.rs +++ b/crates/bevy_transform/src/plugins.rs @@ -1,23 +1,15 @@ +use crate::systems::{propagate_transforms, sync_simple_transforms}; use bevy_app::{App, Plugin, PostStartup, PostUpdate}; use bevy_ecs::schedule::{IntoSystemConfigs, IntoSystemSetConfigs, SystemSet}; -use bevy_hierarchy::ValidParentCheckPlugin; - -use crate::{ - components::GlobalTransform, - systems::{propagate_transforms, sync_simple_transforms}, -}; - -#[cfg(feature = "bevy_reflect")] -use crate::components::Transform; /// Set enum for the systems relating to transform propagation #[derive(Debug, Hash, PartialEq, Eq, Clone, SystemSet)] pub enum TransformSystem { - /// Propagates changes in transform to children's [`GlobalTransform`] + /// Propagates changes in transform to children's [`GlobalTransform`](crate::components::GlobalTransform) TransformPropagate, } -/// The base plugin for handling [`Transform`] components +/// The base plugin for handling [`Transform`](crate::components::Transform) components #[derive(Default)] pub struct TransformPlugin; @@ -29,39 +21,38 @@ impl Plugin for TransformPlugin { struct PropagateTransformsSet; #[cfg(feature = "bevy_reflect")] - app.register_type::() - .register_type::(); + app.register_type::() + .register_type::(); - app.add_plugins(ValidParentCheckPlugin::::default()) - .configure_sets( - PostStartup, - PropagateTransformsSet.in_set(TransformSystem::TransformPropagate), - ) - // add transform systems to startup so the first update is "correct" - .add_systems( - PostStartup, - ( - sync_simple_transforms - .in_set(TransformSystem::TransformPropagate) - // FIXME: https://github.com/bevyengine/bevy/issues/4381 - // These systems cannot access the same entities, - // due to subtle query filtering that is not yet correctly computed in the ambiguity detector - .ambiguous_with(PropagateTransformsSet), - propagate_transforms.in_set(PropagateTransformsSet), - ), - ) - .configure_sets( - PostUpdate, - PropagateTransformsSet.in_set(TransformSystem::TransformPropagate), - ) - .add_systems( - PostUpdate, - ( - sync_simple_transforms - .in_set(TransformSystem::TransformPropagate) - .ambiguous_with(PropagateTransformsSet), - propagate_transforms.in_set(PropagateTransformsSet), - ), - ); + app.configure_sets( + PostStartup, + PropagateTransformsSet.in_set(TransformSystem::TransformPropagate), + ) + // add transform systems to startup so the first update is "correct" + .add_systems( + PostStartup, + ( + sync_simple_transforms + .in_set(TransformSystem::TransformPropagate) + // FIXME: https://github.com/bevyengine/bevy/issues/4381 + // These systems cannot access the same entities, + // due to subtle query filtering that is not yet correctly computed in the ambiguity detector + .ambiguous_with(PropagateTransformsSet), + propagate_transforms.in_set(PropagateTransformsSet), + ), + ) + .configure_sets( + PostUpdate, + PropagateTransformsSet.in_set(TransformSystem::TransformPropagate), + ) + .add_systems( + PostUpdate, + ( + sync_simple_transforms + .in_set(TransformSystem::TransformPropagate) + .ambiguous_with(PropagateTransformsSet), + propagate_transforms.in_set(PropagateTransformsSet), + ), + ); } } diff --git a/crates/bevy_transform/src/systems.rs b/crates/bevy_transform/src/systems.rs index e3f544d8658c0..1db3be25a1263 100644 --- a/crates/bevy_transform/src/systems.rs +++ b/crates/bevy_transform/src/systems.rs @@ -1,13 +1,6 @@ use crate::components::{GlobalTransform, Transform}; use alloc::vec::Vec; -use bevy_ecs::{ - change_detection::Ref, - prelude::{Changed, DetectChanges, Entity, Query, With, Without}, - query::{Added, Or}, - removal_detection::RemovedComponents, - system::{Local, ParamSet}, -}; -use bevy_hierarchy::{Children, Parent}; +use bevy_ecs::prelude::*; /// Update [`GlobalTransform`] component of entities that aren't in the hierarchy /// @@ -193,7 +186,6 @@ mod test { use bevy_tasks::{ComputeTaskPool, TaskPool}; use crate::systems::*; - use bevy_hierarchy::{BuildChildren, ChildBuild}; #[test] fn correct_parent_removed() { @@ -211,8 +203,8 @@ mod test { let root = commands.spawn(offset_transform(3.3)).id(); let parent = commands.spawn(offset_transform(4.4)).id(); let child = commands.spawn(offset_transform(5.5)).id(); - commands.entity(parent).set_parent(root); - commands.entity(child).set_parent(parent); + commands.entity(parent).insert(Parent(root)); + commands.entity(child).insert(Parent(parent)); command_queue.apply(&mut world); schedule.run(&mut world); @@ -225,7 +217,7 @@ mod test { // Remove parent of `parent` let mut command_queue = CommandQueue::default(); let mut commands = Commands::new(&mut command_queue, &world); - commands.entity(parent).remove_parent(); + commands.entity(parent).remove::(); command_queue.apply(&mut world); schedule.run(&mut world); @@ -238,7 +230,7 @@ mod test { // Remove parent of `child` let mut command_queue = CommandQueue::default(); let mut commands = Commands::new(&mut command_queue, &world); - commands.entity(child).remove_parent(); + commands.entity(child).remove::(); command_queue.apply(&mut world); schedule.run(&mut world); @@ -462,8 +454,29 @@ mod test { .spawn(Transform::IDENTITY) .add_children(&[child]); core::mem::swap( - &mut *app.world_mut().get_mut::(child).unwrap(), - &mut *temp.get_mut::(grandchild).unwrap(), + #[expect( + unsafe_code, + reason = "Parent is not mutable but this is for a test to produce a scenario that cannot happen" + )] + // SAFETY: Parent is not mutable but this is for a test to produce a scenario that cannot happen + unsafe { + &mut *app + .world_mut() + .entity_mut(child) + .get_mut_assume_mutable::() + .unwrap() + }, + // SAFETY: Parent is not mutable but this is for a test to produce a scenario that cannot happen + #[expect( + unsafe_code, + reason = "Parent is not mutable but this is for a test to produce a scenario that cannot happen" + )] + unsafe { + &mut *temp + .entity_mut(grandchild) + .get_mut_assume_mutable::() + .unwrap() + }, ); app.update(); @@ -501,7 +514,7 @@ mod test { .abs_diff_eq(2. * translation, 0.1)); // Reparent child - world.entity_mut(child).remove_parent(); + world.entity_mut(child).remove::(); world.entity_mut(parent).add_child(child); // Run schedule to propagate transforms diff --git a/crates/bevy_ui/Cargo.toml b/crates/bevy_ui/Cargo.toml index 5cad6baa4a473..9ab0dadfdac2a 100644 --- a/crates/bevy_ui/Cargo.toml +++ b/crates/bevy_ui/Cargo.toml @@ -17,7 +17,6 @@ bevy_color = { path = "../bevy_color", version = "0.16.0-dev" } bevy_core_pipeline = { path = "../bevy_core_pipeline", version = "0.16.0-dev" } bevy_derive = { path = "../bevy_derive", version = "0.16.0-dev" } bevy_ecs = { path = "../bevy_ecs", version = "0.16.0-dev" } -bevy_hierarchy = { path = "../bevy_hierarchy", version = "0.16.0-dev" } bevy_image = { path = "../bevy_image", version = "0.16.0-dev" } bevy_input = { path = "../bevy_input", version = "0.16.0-dev" } bevy_math = { path = "../bevy_math", version = "0.16.0-dev" } diff --git a/crates/bevy_ui/src/experimental/ghost_hierarchy.rs b/crates/bevy_ui/src/experimental/ghost_hierarchy.rs index 75444ce8e3049..28b07c49039ce 100644 --- a/crates/bevy_ui/src/experimental/ghost_hierarchy.rs +++ b/crates/bevy_ui/src/experimental/ghost_hierarchy.rs @@ -2,14 +2,11 @@ use crate::Node; use bevy_ecs::{prelude::*, system::SystemParam}; -use bevy_hierarchy::{Children, Parent}; use bevy_reflect::prelude::*; use bevy_render::view::Visibility; use bevy_transform::prelude::Transform; use core::marker::PhantomData; -#[cfg(feature = "ghost_nodes")] -use bevy_hierarchy::HierarchyQueryExt; #[cfg(feature = "ghost_nodes")] use smallvec::SmallVec; @@ -169,10 +166,7 @@ impl<'w, 's> UiChildren<'w, 's> { /// Returns the UI parent of the provided entity. pub fn get_parent(&'s self, entity: Entity) -> Option { - self.parents_query - .get(entity) - .ok() - .map(|parent| parent.entity()) + self.parents_query.get(entity).ok().map(|parent| parent.0) } /// Given an entity in the UI hierarchy, check if its set of children has changed, e.g if children has been added/removed or if the order has changed. @@ -222,7 +216,6 @@ mod tests { system::{Query, SystemState}, world::World, }; - use bevy_hierarchy::{BuildChildren, ChildBuild}; use super::{GhostNode, Node, UiChildren, UiRootNodes}; diff --git a/crates/bevy_ui/src/layout/mod.rs b/crates/bevy_ui/src/layout/mod.rs index 1397b1c7553a2..3fa17fd9987ed 100644 --- a/crates/bevy_ui/src/layout/mod.rs +++ b/crates/bevy_ui/src/layout/mod.rs @@ -4,15 +4,10 @@ use crate::{ OverflowAxis, ScrollPosition, TargetCamera, UiScale, Val, }; use bevy_ecs::{ - change_detection::{DetectChanges, DetectChangesMut}, - entity::{Entity, EntityBorrow, EntityHashMap, EntityHashSet}, - event::EventReader, - query::With, - removal_detection::RemovedComponents, - system::{Commands, Local, Query, Res, ResMut, SystemParam}, - world::Ref, + entity::{EntityHashMap, EntityHashSet}, + prelude::*, + system::SystemParam, }; -use bevy_hierarchy::{Children, Parent}; use bevy_math::{UVec2, Vec2}; use bevy_render::camera::{Camera, NormalizedRenderTarget}; use bevy_sprite::BorderRect; @@ -473,18 +468,7 @@ mod tests { use bevy_asset::{AssetEvent, Assets}; use bevy_core_pipeline::core_2d::Camera2d; - use bevy_ecs::{ - entity::Entity, - event::Events, - prelude::{Commands, Component, In, Query, With}, - query::Without, - schedule::{ApplyDeferred, IntoSystemConfigs, Schedule}, - system::RunSystemOnce, - world::World, - }; - use bevy_hierarchy::{ - despawn_with_children_recursive, BuildChildren, ChildBuild, Children, Parent, - }; + use bevy_ecs::{prelude::*, system::RunSystemOnce}; use bevy_image::Image; use bevy_math::{Rect, UVec2, Vec2}; use bevy_render::{camera::ManualTextureViews, prelude::Camera}; @@ -781,7 +765,7 @@ mod tests { } // despawn the parent entity and its descendants - despawn_with_children_recursive(&mut world, ui_parent_entity, true); + world.entity_mut(ui_parent_entity).despawn(); ui_schedule.run(&mut world); diff --git a/crates/bevy_ui/src/stack.rs b/crates/bevy_ui/src/stack.rs index b8031df371f69..fdfc2fd9c6c7f 100644 --- a/crates/bevy_ui/src/stack.rs +++ b/crates/bevy_ui/src/stack.rs @@ -132,7 +132,6 @@ mod tests { system::Commands, world::{CommandQueue, World}, }; - use bevy_hierarchy::{BuildChildren, ChildBuild}; use crate::{GlobalZIndex, Node, UiStack, ZIndex}; diff --git a/crates/bevy_winit/Cargo.toml b/crates/bevy_winit/Cargo.toml index 602063990c4aa..fc0cb747bb513 100644 --- a/crates/bevy_winit/Cargo.toml +++ b/crates/bevy_winit/Cargo.toml @@ -26,7 +26,6 @@ bevy_a11y = { path = "../bevy_a11y", version = "0.16.0-dev" } bevy_app = { path = "../bevy_app", version = "0.16.0-dev" } bevy_derive = { path = "../bevy_derive", version = "0.16.0-dev" } bevy_ecs = { path = "../bevy_ecs", version = "0.16.0-dev" } -bevy_hierarchy = { path = "../bevy_hierarchy", version = "0.16.0-dev" } bevy_input = { path = "../bevy_input", version = "0.16.0-dev" } bevy_input_focus = { path = "../bevy_input_focus", version = "0.16.0-dev" } bevy_log = { path = "../bevy_log", version = "0.16.0-dev" } diff --git a/crates/bevy_winit/src/accessibility.rs b/crates/bevy_winit/src/accessibility.rs index 57672ec3b4932..939916f6eb401 100644 --- a/crates/bevy_winit/src/accessibility.rs +++ b/crates/bevy_winit/src/accessibility.rs @@ -15,15 +15,7 @@ use bevy_a11y::{ }; use bevy_app::{App, Plugin, PostUpdate}; use bevy_derive::{Deref, DerefMut}; -use bevy_ecs::{ - change_detection::DetectChanges, - entity::EntityHashMap, - prelude::{Entity, EventReader, EventWriter}, - query::With, - schedule::IntoSystemConfigs, - system::{NonSendMut, Query, Res, ResMut, Resource}, -}; -use bevy_hierarchy::{Children, Parent}; +use bevy_ecs::{entity::EntityHashMap, prelude::*}; use bevy_window::{PrimaryWindow, Window, WindowClosed}; /// Maps window entities to their `AccessKit` [`Adapter`]s. diff --git a/examples/3d/color_grading.rs b/examples/3d/color_grading.rs index 60195426e8020..0b2616c1879b5 100644 --- a/examples/3d/color_grading.rs +++ b/examples/3d/color_grading.rs @@ -164,7 +164,7 @@ fn add_buttons(commands: &mut Commands, font: &Handle, color_grading: &Col /// Adds the buttons for the global controls (those that control the scene as a /// whole as opposed to shadows, midtones, or highlights). fn add_buttons_for_global_controls( - parent: &mut ChildBuilder, + parent: &mut ChildSpawnerCommands, color_grading: &ColorGrading, font: &Handle, ) { @@ -196,7 +196,7 @@ fn add_buttons_for_global_controls( /// Adds the buttons that control color grading for individual sections /// (highlights, midtones, shadows). fn add_buttons_for_section( - parent: &mut ChildBuilder, + parent: &mut ChildSpawnerCommands, section: SelectedColorGradingSection, color_grading: &ColorGrading, font: &Handle, @@ -234,7 +234,7 @@ fn add_buttons_for_section( /// Adds a button that controls one of the color grading values. fn add_button_for_value( - parent: &mut ChildBuilder, + parent: &mut ChildSpawnerCommands, option: SelectedColorGradingOption, color_grading: &ColorGrading, font: &Handle, @@ -315,7 +315,7 @@ fn add_help_text( /// Adds some text to the scene. fn add_text<'a>( - parent: &'a mut ChildBuilder, + parent: &'a mut ChildSpawnerCommands, label: &str, font: &Handle, color: Color, diff --git a/examples/3d/mixed_lighting.rs b/examples/3d/mixed_lighting.rs index 80d139714a6f4..378070afc3e5a 100644 --- a/examples/3d/mixed_lighting.rs +++ b/examples/3d/mixed_lighting.rs @@ -460,7 +460,7 @@ fn move_sphere( }; // Grab its transform. - let Ok(mut transform) = transforms.get_mut(**parent) else { + let Ok(mut transform) = transforms.get_mut(parent.0) else { return; }; diff --git a/examples/3d/order_independent_transparency.rs b/examples/3d/order_independent_transparency.rs index 74e8edc4d18b2..62854957f2f3a 100644 --- a/examples/3d/order_independent_transparency.rs +++ b/examples/3d/order_independent_transparency.rs @@ -104,7 +104,7 @@ fn cycle_scenes( if keyboard_input.just_pressed(KeyCode::KeyC) { // despawn current scene for e in &q { - commands.entity(e).despawn_recursive(); + commands.entity(e).despawn(); } // increment scene_id *scene_id = (*scene_id + 1) % 2; diff --git a/examples/3d/split_screen.rs b/examples/3d/split_screen.rs index 2faf8a1e68d8e..a37727d1e93f0 100644 --- a/examples/3d/split_screen.rs +++ b/examples/3d/split_screen.rs @@ -105,7 +105,7 @@ fn setup( }); } - fn buttons_panel(parent: &mut ChildBuilder) { + fn buttons_panel(parent: &mut ChildSpawnerCommands) { parent .spawn(Node { position_type: PositionType::Absolute, @@ -124,7 +124,7 @@ fn setup( }); } - fn rotate_button(parent: &mut ChildBuilder, caption: &str, direction: Direction) { + fn rotate_button(parent: &mut ChildSpawnerCommands, caption: &str, direction: Direction) { parent .spawn(( RotateCamera(direction), diff --git a/examples/3d/visibility_range.rs b/examples/3d/visibility_range.rs index 17bc26439b189..d8d485784653c 100644 --- a/examples/3d/visibility_range.rs +++ b/examples/3d/visibility_range.rs @@ -187,7 +187,7 @@ fn set_visibility_ranges( break; } match parent { - Some(parent) => current = **parent, + Some(parent) => current = parent.0, None => break, } } diff --git a/examples/animation/animated_ui.rs b/examples/animation/animated_ui.rs index 3dc80f3eba6b0..f31b2ccd5eb4b 100644 --- a/examples/animation/animated_ui.rs +++ b/examples/animation/animated_ui.rs @@ -141,7 +141,7 @@ fn setup( )) .with_children(|builder| { // Build the text node. - let player = builder.parent_entity(); + let player = builder.target_entity(); builder .spawn(( Text::new("Bevy"), diff --git a/examples/animation/animation_masks.rs b/examples/animation/animation_masks.rs index 1bde5c909f54f..72408260d62f6 100644 --- a/examples/animation/animation_masks.rs +++ b/examples/animation/animation_masks.rs @@ -223,8 +223,13 @@ fn setup_ui(mut commands: Commands) { // Adds a button that allows the user to toggle a mask group on and off. // // The button will automatically become a child of the parent that owns the -// given `ChildBuilder`. -fn add_mask_group_control(parent: &mut ChildBuilder, label: &str, width: Val, mask_group_id: u32) { +// given `ChildSpawnerCommands`. +fn add_mask_group_control( + parent: &mut ChildSpawnerCommands, + label: &str, + width: Val, + mask_group_id: u32, +) { let button_text_style = ( TextFont { font_size: 14.0, diff --git a/examples/asset/multi_asset_sync.rs b/examples/asset/multi_asset_sync.rs index 0df4f71aec8ed..5ec34b0d46a81 100644 --- a/examples/asset/multi_asset_sync.rs +++ b/examples/asset/multi_asset_sync.rs @@ -268,7 +268,7 @@ fn get_async_loading_state( fn despawn_loading_state_entities(mut commands: Commands, loading: Query>) { // Despawn entities in the loading phase. for entity in loading.iter() { - commands.entity(entity).despawn_recursive(); + commands.entity(entity).despawn(); } // Despawn resources used in the loading phase. diff --git a/examples/audio/soundtrack.rs b/examples/audio/soundtrack.rs index c5fce88570939..13a8f5425cf49 100644 --- a/examples/audio/soundtrack.rs +++ b/examples/audio/soundtrack.rs @@ -134,7 +134,7 @@ fn fade_out( let current_volume = audio.volume(); audio.set_volume(current_volume - time.delta_secs() / FADE_TIME); if audio.volume() <= 0.0 { - commands.entity(entity).despawn_recursive(); + commands.entity(entity).despawn(); } } } diff --git a/examples/ecs/generic_system.rs b/examples/ecs/generic_system.rs index dbd5b63432b3a..76209cd109252 100644 --- a/examples/ecs/generic_system.rs +++ b/examples/ecs/generic_system.rs @@ -84,6 +84,6 @@ fn transition_to_in_game_system( // Here, the `Component` trait is a trait bound on T, our generic type fn cleanup_system(mut commands: Commands, query: Query>) { for e in &query { - commands.entity(e).despawn_recursive(); + commands.entity(e).despawn(); } } diff --git a/examples/ecs/hierarchy.rs b/examples/ecs/hierarchy.rs index 2206a5f101e7d..e302ab6e27d82 100644 --- a/examples/ecs/hierarchy.rs +++ b/examples/ecs/hierarchy.rs @@ -24,7 +24,7 @@ fn setup(mut commands: Commands, asset_server: Res) { )) // With that entity as a parent, run a lambda that spawns its children .with_children(|parent| { - // parent is a ChildBuilder, which has a similar API to Commands + // parent is a ChildSpawnerCommands, which has a similar API to Commands parent.spawn(( Transform::from_xyz(250.0, 0.0, 0.0).with_scale(Vec3::splat(0.75)), Sprite { @@ -77,13 +77,13 @@ fn rotate( // To demonstrate removing children, we'll remove a child after a couple of seconds. if time.elapsed_secs() >= 2.0 && children.len() == 2 { let child = children.last().unwrap(); - commands.entity(*child).despawn_recursive(); + commands.entity(*child).despawn(); } if time.elapsed_secs() >= 4.0 { // This will remove the entity from its parent's list of children, as well as despawn // any children the entity has. - commands.entity(parent).despawn_recursive(); + commands.entity(parent).despawn(); } } } diff --git a/examples/ecs/observer_propagation.rs b/examples/ecs/observer_propagation.rs index 15f1ca54835cb..3e05fe2b5522d 100644 --- a/examples/ecs/observer_propagation.rs +++ b/examples/ecs/observer_propagation.rs @@ -117,7 +117,7 @@ fn take_damage( info!("{} has {:.1} HP", name, hp.0); } else { warn!("💀 {} has died a gruesome death", name); - commands.entity(trigger.target()).despawn_recursive(); + commands.entity(trigger.target()).despawn(); app_exit.send(AppExit::Success); } diff --git a/examples/games/alien_cake_addict.rs b/examples/games/alien_cake_addict.rs index c8a91ab5fd0cf..8749fe988bf7e 100644 --- a/examples/games/alien_cake_addict.rs +++ b/examples/games/alien_cake_addict.rs @@ -257,7 +257,7 @@ fn move_player( if game.player.i == game.bonus.i && game.player.j == game.bonus.j { game.score += 2; game.cake_eaten += 1; - commands.entity(entity).despawn_recursive(); + commands.entity(entity).despawn(); game.bonus.entity = None; } } @@ -321,7 +321,7 @@ fn spawn_bonus( if let Some(entity) = game.bonus.entity { game.score -= 3; - commands.entity(entity).despawn_recursive(); + commands.entity(entity).despawn(); game.bonus.entity = None; if game.score <= -5 { next_state.set(GameState::GameOver); diff --git a/examples/games/game_menu.rs b/examples/games/game_menu.rs index ef12bb352cb91..ad6bb4604251e 100644 --- a/examples/games/game_menu.rs +++ b/examples/games/game_menu.rs @@ -760,6 +760,6 @@ mod menu { // Generic system that takes a component as a parameter, and will despawn all entities with that component fn despawn_screen(to_despawn: Query>, mut commands: Commands) { for entity in &to_despawn { - commands.entity(entity).despawn_recursive(); + commands.entity(entity).despawn(); } } diff --git a/examples/games/loading_screen.rs b/examples/games/loading_screen.rs index 40f9f96776376..8ca179f55ec81 100644 --- a/examples/games/loading_screen.rs +++ b/examples/games/loading_screen.rs @@ -124,7 +124,7 @@ fn unload_current_level( ) { *loading_state = LoadingState::LevelLoading; for entity in entities.iter() { - commands.entity(entity).despawn_recursive(); + commands.entity(entity).despawn(); } } diff --git a/examples/helpers/widgets.rs b/examples/helpers/widgets.rs index ae8e5626aff8c..3c47650949390 100644 --- a/examples/helpers/widgets.rs +++ b/examples/helpers/widgets.rs @@ -41,7 +41,7 @@ pub fn main_ui_node() -> Node { /// The type parameter specifies the value that will be packaged up and sent in /// a [`WidgetClickEvent`] when the radio button is clicked. pub fn spawn_option_button( - parent: &mut ChildBuilder, + parent: &mut ChildSpawnerCommands, option_value: T, option_name: &str, is_selected: bool, @@ -91,8 +91,11 @@ pub fn spawn_option_button( /// The user may change the setting to any one of the labeled `options`. The /// value of the given type parameter will be packaged up and sent as a /// [`WidgetClickEvent`] when one of the radio buttons is clicked. -pub fn spawn_option_buttons(parent: &mut ChildBuilder, title: &str, options: &[(T, &str)]) -where +pub fn spawn_option_buttons( + parent: &mut ChildSpawnerCommands, + title: &str, + options: &[(T, &str)], +) where T: Clone + Send + Sync + 'static, { // Add the parent node for the row. @@ -125,7 +128,7 @@ where /// Returns the `EntityCommands`, which allow further customization of the text /// style. pub fn spawn_ui_text<'a>( - parent: &'a mut ChildBuilder, + parent: &'a mut ChildSpawnerCommands, label: &str, color: Color, ) -> EntityCommands<'a> { diff --git a/examples/state/computed_states.rs b/examples/state/computed_states.rs index e6fc7e41e6ccd..4c5ea224e33e2 100644 --- a/examples/state/computed_states.rs +++ b/examples/state/computed_states.rs @@ -409,7 +409,7 @@ mod ui { } pub fn cleanup_menu(mut commands: Commands, menu_data: Res) { - commands.entity(menu_data.root_entity).despawn_recursive(); + commands.entity(menu_data.root_entity).despawn(); } pub fn setup_game(mut commands: Commands, asset_server: Res) { diff --git a/examples/state/custom_transitions.rs b/examples/state/custom_transitions.rs index 820c140462599..f5b1415d3e8e5 100644 --- a/examples/state/custom_transitions.rs +++ b/examples/state/custom_transitions.rs @@ -163,7 +163,7 @@ fn menu( } fn cleanup_menu(mut commands: Commands, menu_data: Res) { - commands.entity(menu_data.button_entity).despawn_recursive(); + commands.entity(menu_data.button_entity).despawn(); } const SPEED: f32 = 100.0; diff --git a/examples/state/states.rs b/examples/state/states.rs index 3e83b6fda9129..69f81b64b9cfa 100644 --- a/examples/state/states.rs +++ b/examples/state/states.rs @@ -113,7 +113,7 @@ fn menu( } fn cleanup_menu(mut commands: Commands, menu_data: Res) { - commands.entity(menu_data.button_entity).despawn_recursive(); + commands.entity(menu_data.button_entity).despawn(); } fn setup_game(mut commands: Commands, asset_server: Res) { diff --git a/examples/state/sub_states.rs b/examples/state/sub_states.rs index a09f4f81dc1f3..767e3fb05d12f 100644 --- a/examples/state/sub_states.rs +++ b/examples/state/sub_states.rs @@ -84,7 +84,7 @@ fn menu( } fn cleanup_menu(mut commands: Commands, menu_data: Res) { - commands.entity(menu_data.button_entity).despawn_recursive(); + commands.entity(menu_data.button_entity).despawn(); } const SPEED: f32 = 100.0; diff --git a/examples/stress_tests/many_buttons.rs b/examples/stress_tests/many_buttons.rs index 314252c0b5a6f..15620d3a52734 100644 --- a/examples/stress_tests/many_buttons.rs +++ b/examples/stress_tests/many_buttons.rs @@ -248,7 +248,7 @@ fn setup_grid(mut commands: Commands, asset_server: Res, args: Res< } fn spawn_button( - commands: &mut ChildBuilder, + commands: &mut ChildSpawnerCommands, background_color: Color, buttons: f32, column: usize, @@ -296,5 +296,5 @@ fn spawn_button( } fn despawn_ui(mut commands: Commands, root_node: Single, Without)>) { - commands.entity(*root_node).despawn_recursive(); + commands.entity(*root_node).despawn(); } diff --git a/examples/ui/display_and_visibility.rs b/examples/ui/display_and_visibility.rs index 48cf17c767953..239b9814dbcc5 100644 --- a/examples/ui/display_and_visibility.rs +++ b/examples/ui/display_and_visibility.rs @@ -2,7 +2,7 @@ use bevy::{ color::palettes::css::{DARK_CYAN, DARK_GRAY, YELLOW}, - ecs::component::Mutable, + ecs::{component::Mutable, hierarchy::ChildSpawnerCommands}, prelude::*, winit::WinitSettings, }; @@ -166,7 +166,7 @@ fn setup(mut commands: Commands, asset_server: Res) { }); } -fn spawn_left_panel(builder: &mut ChildBuilder, palette: &[Color; 4]) -> Vec { +fn spawn_left_panel(builder: &mut ChildSpawnerCommands, palette: &[Color; 4]) -> Vec { let mut target_ids = vec![]; builder .spawn(( @@ -261,12 +261,12 @@ fn spawn_left_panel(builder: &mut ChildBuilder, palette: &[Color; 4]) -> Vec, ) { - let spawn_buttons = |parent: &mut ChildBuilder, target_id| { + let spawn_buttons = |parent: &mut ChildSpawnerCommands, target_id| { spawn_button::(parent, text_font.clone(), target_id); spawn_button::(parent, text_font.clone(), target_id); }; @@ -376,7 +376,7 @@ fn spawn_right_panel( }); } -fn spawn_button(parent: &mut ChildBuilder, text_font: TextFont, target: Entity) +fn spawn_button(parent: &mut ChildSpawnerCommands, text_font: TextFont, target: Entity) where T: Default + std::fmt::Debug + Send + Sync + 'static, Target: TargetUpdate, diff --git a/examples/ui/flex_layout.rs b/examples/ui/flex_layout.rs index 901a4af124a0a..2a155eafb1016 100644 --- a/examples/ui/flex_layout.rs +++ b/examples/ui/flex_layout.rs @@ -109,7 +109,7 @@ fn spawn_layout(mut commands: Commands, asset_server: Res) { } fn spawn_child_node( - builder: &mut ChildBuilder, + builder: &mut ChildSpawnerCommands, font: Handle, align_items: AlignItems, justify_content: JustifyContent, @@ -145,7 +145,7 @@ fn spawn_child_node( } fn spawn_nested_text_bundle( - builder: &mut ChildBuilder, + builder: &mut ChildSpawnerCommands, font: Handle, background_color: Color, margin: UiRect, diff --git a/examples/ui/grid.rs b/examples/ui/grid.rs index 6315283bec040..60a95c8e9f75c 100644 --- a/examples/ui/grid.rs +++ b/examples/ui/grid.rs @@ -182,7 +182,7 @@ fn spawn_layout(mut commands: Commands, asset_server: Res) { /// Create a colored rectangle node. The node has size as it is assumed that it will be /// spawned as a child of a Grid container with `AlignItems::Stretch` and `JustifyItems::Stretch` /// which will allow it to take its size from the size of the grid area it occupies. -fn item_rect(builder: &mut ChildBuilder, color: Srgba) { +fn item_rect(builder: &mut ChildSpawnerCommands, color: Srgba) { builder .spawn(( Node { @@ -197,7 +197,7 @@ fn item_rect(builder: &mut ChildBuilder, color: Srgba) { }); } -fn spawn_nested_text_bundle(builder: &mut ChildBuilder, font: Handle, text: &str) { +fn spawn_nested_text_bundle(builder: &mut ChildSpawnerCommands, font: Handle, text: &str) { builder.spawn(( Text::new(text), TextFont { font, ..default() }, diff --git a/examples/ui/overflow_debug.rs b/examples/ui/overflow_debug.rs index cfa1fb4348b40..400c257166926 100644 --- a/examples/ui/overflow_debug.rs +++ b/examples/ui/overflow_debug.rs @@ -135,7 +135,7 @@ fn setup(mut commands: Commands, asset_server: Res) { } fn spawn_image( - parent: &mut ChildBuilder, + parent: &mut ChildSpawnerCommands, asset_server: &Res, update_transform: impl UpdateTransform + Component, ) { @@ -154,7 +154,7 @@ fn spawn_image( } fn spawn_text( - parent: &mut ChildBuilder, + parent: &mut ChildSpawnerCommands, asset_server: &Res, update_transform: impl UpdateTransform + Component, ) { @@ -171,9 +171,9 @@ fn spawn_text( } fn spawn_container( - parent: &mut ChildBuilder, + parent: &mut ChildSpawnerCommands, update_transform: impl UpdateTransform + Component, - spawn_children: impl FnOnce(&mut ChildBuilder), + spawn_children: impl FnOnce(&mut ChildSpawnerCommands), ) { let mut transform = Transform::default(); diff --git a/examples/ui/scroll.rs b/examples/ui/scroll.rs index c98f8a500a322..d0bcc6af33155 100644 --- a/examples/ui/scroll.rs +++ b/examples/ui/scroll.rs @@ -93,7 +93,7 @@ fn setup(mut commands: Commands, asset_server: Res) { mut commands: Commands | { if trigger.event().button == PointerButton::Primary { - commands.entity(trigger.target()).despawn_recursive(); + commands.entity(trigger.target()).despawn(); } }); } diff --git a/examples/ui/size_constraints.rs b/examples/ui/size_constraints.rs index 6c8dc504c4dba..d3e608ca2c8c2 100644 --- a/examples/ui/size_constraints.rs +++ b/examples/ui/size_constraints.rs @@ -107,7 +107,7 @@ fn setup(mut commands: Commands, asset_server: Res) { }); } -fn spawn_bar(parent: &mut ChildBuilder) { +fn spawn_bar(parent: &mut ChildSpawnerCommands) { parent .spawn(( Node { @@ -137,7 +137,7 @@ fn spawn_bar(parent: &mut ChildBuilder) { } fn spawn_button_row( - parent: &mut ChildBuilder, + parent: &mut ChildSpawnerCommands, constraint: Constraint, text_style: (TextFont, TextColor), ) { @@ -204,7 +204,7 @@ fn spawn_button_row( } fn spawn_button( - parent: &mut ChildBuilder, + parent: &mut ChildSpawnerCommands, constraint: Constraint, action: ButtonValue, label: String, diff --git a/examples/ui/tab_navigation.rs b/examples/ui/tab_navigation.rs index 94ef68b751a8d..c6060bd848e07 100644 --- a/examples/ui/tab_navigation.rs +++ b/examples/ui/tab_navigation.rs @@ -184,7 +184,7 @@ fn setup(mut commands: Commands, asset_server: Res) { }); } -fn create_button(parent: &mut ChildBuilder<'_>, asset_server: &AssetServer) { +fn create_button(parent: &mut ChildSpawnerCommands<'_>, asset_server: &AssetServer) { parent .spawn(( Button, diff --git a/examples/window/monitor_info.rs b/examples/window/monitor_info.rs index 930aca48f90ba..9e64399f0dcaa 100644 --- a/examples/window/monitor_info.rs +++ b/examples/window/monitor_info.rs @@ -82,7 +82,7 @@ fn update( for monitor_entity in monitors_removed.read() { for (ref_entity, monitor_ref) in monitor_refs.iter() { if monitor_ref.0 == monitor_entity { - commands.entity(ref_entity).despawn_recursive(); + commands.entity(ref_entity).despawn(); } } } diff --git a/tests/how_to_test_systems.rs b/tests/how_to_test_systems.rs index a2cb86a680dfe..660d4574953b7 100644 --- a/tests/how_to_test_systems.rs +++ b/tests/how_to_test_systems.rs @@ -26,7 +26,7 @@ fn despawn_dead_enemies( ) { for (entity, enemy) in &enemies { if enemy.hit_points == 0 { - commands.entity(entity).despawn_recursive(); + commands.entity(entity).despawn(); dead_enemies.send(EnemyDied(enemy.score_value)); } } diff --git a/tools/ci/src/commands/compile_check_no_std.rs b/tools/ci/src/commands/compile_check_no_std.rs index 83c9aec0d062e..b76194d1c22db 100644 --- a/tools/ci/src/commands/compile_check_no_std.rs +++ b/tools/ci/src/commands/compile_check_no_std.rs @@ -110,14 +110,6 @@ impl Prepare for CompileCheckNoStdCommand { "Please fix compiler errors in output above for bevy_app no_std compatibility.", )); - commands.push(PreparedCommand::new::( - cmd!( - sh, - "cargo check -p bevy_hierarchy --no-default-features --features bevy_app,reflect --target {target}" - ), - "Please fix compiler errors in output above for bevy_hierarchy no_std compatibility.", - )); - commands.push(PreparedCommand::new::( cmd!( sh, @@ -129,7 +121,7 @@ impl Prepare for CompileCheckNoStdCommand { commands.push(PreparedCommand::new::( cmd!( sh, - "cargo check -p bevy_state --no-default-features --features bevy_reflect,bevy_app,bevy_hierarchy --target {target}" + "cargo check -p bevy_state --no-default-features --features bevy_reflect,bevy_app --target {target}" ), "Please fix compiler errors in output above for bevy_state no_std compatibility.", )); diff --git a/tools/publish.sh b/tools/publish.sh index ae6e869c98616..45169abb242b8 100644 --- a/tools/publish.sh +++ b/tools/publish.sh @@ -20,7 +20,6 @@ crates=( bevy_asset bevy_audio bevy_diagnostic - bevy_hierarchy bevy_transform bevy_window bevy_encase_derive