From f401fb3493a58564bdc14906389dcda5bdb21199 Mon Sep 17 00:00:00 2001 From: Carter Anderson Date: Fri, 10 Jan 2025 15:22:35 -0800 Subject: [PATCH 01/38] Fragmenting Relations --- benches/Cargo.toml | 1 - benches/benches/bevy_ecs/entity_cloning.rs | 5 +- .../benches/bevy_ecs/observers/propagation.rs | 6 +- .../bevy_ecs/world/despawn_recursive.rs | 5 +- crates/bevy_animation/Cargo.toml | 1 - crates/bevy_audio/Cargo.toml | 1 - crates/bevy_audio/src/audio_output.rs | 5 +- crates/bevy_dev_tools/Cargo.toml | 1 - crates/bevy_dev_tools/src/fps_overlay.rs | 1 - crates/bevy_ecs/src/entity/clone_entities.rs | 54 +- crates/bevy_ecs/src/hierarchy.rs | 355 +++++ crates/bevy_ecs/src/lib.rs | 3 + crates/bevy_ecs/src/relationship.rs | 494 +++++++ crates/bevy_ecs/src/traversal.rs | 13 +- crates/bevy_gltf/Cargo.toml | 1 - crates/bevy_gltf/src/loader.rs | 6 +- crates/bevy_hierarchy/Cargo.toml | 66 - crates/bevy_hierarchy/README.md | 7 - crates/bevy_hierarchy/src/child_builder.rs | 1197 ----------------- .../bevy_hierarchy/src/components/children.rs | 177 --- crates/bevy_hierarchy/src/components/mod.rs | 5 - .../bevy_hierarchy/src/components/parent.rs | 100 -- crates/bevy_hierarchy/src/events.rs | 34 - crates/bevy_hierarchy/src/hierarchy.rs | 481 ------- crates/bevy_hierarchy/src/lib.rs | 110 -- crates/bevy_hierarchy/src/query_extension.rs | 435 ------ .../src/valid_parent_check_plugin.rs | 104 -- crates/bevy_input_focus/Cargo.toml | 1 - crates/bevy_input_focus/src/lib.rs | 2 - crates/bevy_input_focus/src/tab_navigation.rs | 3 +- crates/bevy_internal/Cargo.toml | 2 - crates/bevy_internal/src/default_plugins.rs | 1 - crates/bevy_internal/src/lib.rs | 1 - crates/bevy_internal/src/prelude.rs | 6 +- crates/bevy_picking/Cargo.toml | 1 - crates/bevy_picking/src/events.rs | 1 - crates/bevy_picking/src/input.rs | 3 +- crates/bevy_remote/Cargo.toml | 1 - crates/bevy_remote/src/builtin_methods.rs | 4 +- crates/bevy_render/Cargo.toml | 1 - crates/bevy_render/src/lib.rs | 2 - crates/bevy_render/src/mesh/mod.rs | 8 +- crates/bevy_render/src/view/visibility/mod.rs | 9 +- .../bevy_render/src/view/window/screenshot.rs | 5 +- crates/bevy_scene/Cargo.toml | 1 - crates/bevy_scene/src/bundle.rs | 3 +- crates/bevy_scene/src/dynamic_scene.rs | 2 +- crates/bevy_scene/src/scene_spawner.rs | 9 +- crates/bevy_state/Cargo.toml | 10 +- crates/bevy_state/src/state_scoped.rs | 5 - crates/bevy_text/Cargo.toml | 1 - crates/bevy_text/src/text.rs | 6 +- crates/bevy_text/src/text_access.rs | 1 - crates/bevy_transform/Cargo.toml | 6 +- crates/bevy_transform/src/commands.rs | 4 +- .../src/components/global_transform.rs | 9 +- crates/bevy_transform/src/helper.rs | 7 +- crates/bevy_transform/src/plugins.rs | 62 +- crates/bevy_transform/src/systems.rs | 32 +- crates/bevy_ui/Cargo.toml | 1 - .../src/experimental/ghost_hierarchy.rs | 4 +- crates/bevy_ui/src/layout/mod.rs | 26 +- crates/bevy_ui/src/stack.rs | 3 +- crates/bevy_winit/Cargo.toml | 1 - crates/bevy_winit/src/accessibility.rs | 10 +- examples/3d/color_grading.rs | 8 +- examples/3d/order_independent_transparency.rs | 2 +- examples/3d/split_screen.rs | 4 +- examples/3d/visibility_range.rs | 2 +- examples/animation/animated_ui.rs | 2 +- examples/animation/animation_masks.rs | 9 +- examples/asset/multi_asset_sync.rs | 2 +- examples/audio/soundtrack.rs | 2 +- examples/ecs/generic_system.rs | 2 +- examples/ecs/hierarchy.rs | 6 +- examples/ecs/observer_propagation.rs | 2 +- examples/games/alien_cake_addict.rs | 4 +- examples/games/game_menu.rs | 2 +- examples/games/loading_screen.rs | 2 +- examples/helpers/widgets.rs | 11 +- examples/state/computed_states.rs | 2 +- examples/state/custom_transitions.rs | 2 +- examples/state/states.rs | 2 +- examples/state/sub_states.rs | 2 +- examples/stress_tests/many_buttons.rs | 4 +- examples/ui/display_and_visibility.rs | 10 +- examples/ui/flex_layout.rs | 4 +- examples/ui/grid.rs | 4 +- examples/ui/overflow_debug.rs | 8 +- examples/ui/scroll.rs | 2 +- examples/ui/size_constraints.rs | 6 +- examples/ui/tab_navigation.rs | 2 +- examples/window/monitor_info.rs | 2 +- tests/how_to_test_systems.rs | 2 +- 94 files changed, 1074 insertions(+), 2960 deletions(-) create mode 100644 crates/bevy_ecs/src/hierarchy.rs create mode 100644 crates/bevy_ecs/src/relationship.rs delete mode 100644 crates/bevy_hierarchy/Cargo.toml delete mode 100644 crates/bevy_hierarchy/README.md delete mode 100644 crates/bevy_hierarchy/src/child_builder.rs delete mode 100644 crates/bevy_hierarchy/src/components/children.rs delete mode 100644 crates/bevy_hierarchy/src/components/mod.rs delete mode 100644 crates/bevy_hierarchy/src/components/parent.rs delete mode 100644 crates/bevy_hierarchy/src/events.rs delete mode 100644 crates/bevy_hierarchy/src/hierarchy.rs delete mode 100644 crates/bevy_hierarchy/src/lib.rs delete mode 100644 crates/bevy_hierarchy/src/query_extension.rs delete mode 100644 crates/bevy_hierarchy/src/valid_parent_check_plugin.rs diff --git a/benches/Cargo.toml b/benches/Cargo.toml index becb6cff4e68e..dd094acb1093d 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 b3312aa0ce2d7..e792baf84cd80 100644 --- a/crates/bevy_animation/Cargo.toml +++ b/crates/bevy_animation/Cargo.toml @@ -25,7 +25,6 @@ bevy_time = { path = "../bevy_time", version = "0.15.0-dev" } bevy_utils = { path = "../bevy_utils", version = "0.15.0-dev" } bevy_ecs = { path = "../bevy_ecs", version = "0.15.0-dev" } bevy_transform = { path = "../bevy_transform", version = "0.15.0-dev" } -bevy_hierarchy = { path = "../bevy_hierarchy", version = "0.15.0-dev" } # other petgraph = { version = "0.6", features = ["serde-1"] } diff --git a/crates/bevy_audio/Cargo.toml b/crates/bevy_audio/Cargo.toml index 7df10a1bcbd57..65f86a79eb1a9 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.15.0-dev" } bevy_asset = { path = "../bevy_asset", version = "0.15.0-dev" } bevy_ecs = { path = "../bevy_ecs", version = "0.15.0-dev" } -bevy_hierarchy = { path = "../bevy_hierarchy", version = "0.15.0-dev" } bevy_math = { path = "../bevy_math", version = "0.15.0-dev" } bevy_reflect = { path = "../bevy_reflect", version = "0.15.0-dev", features = [ "bevy", diff --git a/crates/bevy_audio/src/audio_output.rs b/crates/bevy_audio/src/audio_output.rs index ee65eaf01f605..26e344e834f28 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 bevy_utils::tracing::warn; @@ -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 1d426853cee80..1f302b0f6b174 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.15.0-dev" } bevy_color = { path = "../bevy_color", version = "0.15.0-dev" } bevy_diagnostic = { path = "../bevy_diagnostic", version = "0.15.0-dev" } bevy_ecs = { path = "../bevy_ecs", version = "0.15.0-dev" } -bevy_hierarchy = { path = "../bevy_hierarchy", version = "0.15.0-dev" } bevy_input = { path = "../bevy_input", version = "0.15.0-dev" } bevy_render = { path = "../bevy_render", version = "0.15.0-dev" } bevy_time = { path = "../bevy_time", version = "0.15.0-dev" } diff --git a/crates/bevy_dev_tools/src/fps_overlay.rs b/crates/bevy_dev_tools/src/fps_overlay.rs index f970fc8f434c8..62ee0a20f90de 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/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..4fa64f937c494 --- /dev/null +++ b/crates/bevy_ecs/src/hierarchy.rs @@ -0,0 +1,355 @@ +// TODO: REMOVE THIS +#![allow(missing_docs)] + +//! Parent-Child relationships for entities. + +use crate as bevy_ecs; +use crate::bundle::Bundle; +use crate::component::ComponentId; +use crate::relationship::{RelatedSpawner, RelatedSpawnerCommands}; +use crate::system::EntityCommands; +use crate::world::{DeferredWorld, EntityWorldMut}; +use crate::{ + component::{Component, ComponentCloneHandler, Mutable, StorageType}, + entity::{Entity, VisitEntities}, + reflect::{ + ReflectComponent, ReflectFromWorld, ReflectMapEntities, ReflectVisitEntities, + ReflectVisitEntitiesMut, + }, + relationship::{Relationship, RelationshipSources}, + world::{FromWorld, World}, +}; +use alloc::{format, string::String, vec::Vec}; +use bevy_ecs_macros::VisitEntitiesMut; +use bevy_reflect::Reflect; +use core::slice; +use disqualified::ShortName; + +#[derive(Component, Clone, Reflect, VisitEntities, VisitEntitiesMut, PartialEq, Eq, Debug)] +#[reflect( + Component, + MapEntities, + VisitEntities, + VisitEntitiesMut, + PartialEq, + Debug, + FromWorld +)] +#[component( + immutable, + on_insert = Self::on_insert, + on_remove = Self::on_remove, + on_replace = Self::on_replace, +)] +pub struct Parent(pub Entity); + +impl Parent { + pub fn get(&self) -> Entity { + self.0 + } +} + +impl Relationship for Parent { + type RelationshipSources = Children; + + #[inline(always)] + fn get(&self) -> Entity { + self.0 + } + + fn set(&mut self, entity: Entity) { + self.0 = entity; + } + + fn from(entity: Entity) -> Self { + Self(entity) + } +} + +// 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) + } +} + +#[derive(Default, Reflect, VisitEntitiesMut)] +#[reflect(Component, MapEntities, VisitEntities, VisitEntitiesMut)] +pub struct Children(Vec); + +impl Component for Children { + const STORAGE_TYPE: StorageType = StorageType::Table; + type Mutability = Mutable; + + fn register_component_hooks(hooks: &mut crate::component::ComponentHooks) { + hooks.on_remove(Self::on_remove); + } + + fn get_component_clone_handler() -> ComponentCloneHandler { + ComponentCloneHandler::ignore() + } +} + +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 std::ops::Deref for Children { + type Target = [Entity]; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl RelationshipSources for Children { + type Relationship = Parent; + type Collection = Vec; + + fn collection(&self) -> &Self::Collection { + &self.0 + } + + fn collection_mut(&mut self) -> &mut Self::Collection { + &mut self.0 + } + + fn from_collection(collection: Self::Collection) -> Self { + Self(collection) + } +} + +pub type ChildSpawner<'w> = RelatedSpawner<'w, Parent>; +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 + } + + #[deprecated(since = "0.16.0", note = "Use entity_mut.remove::()")] + pub fn remove_parent(&mut self) -> &mut Self { + self.remove::(); + self + } + + #[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 + } + + #[deprecated(since = "0.16.0", note = "Use entity_commands.remove::()")] + pub fn remove_parent(&mut self) -> &mut Self { + self.remove::(); + self + } + + #[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 + } +} + +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()) + .map_or(false, |e| e.contains::()) + { + // TODO: print name here once Name lives in bevy_ecs + let name: Option = None; + bevy_utils::tracing::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::RelationshipSources, + 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 id = world.spawn_empty().id(); + let id1 = world.spawn(Parent(id)).id(); + let id3 = world.spawn(Parent(id1)).id(); + let id2 = world.spawn(Parent(id)).id(); + + // Spawn + let hierarchy = get_hierarchy(&world, id); + assert_eq!( + hierarchy, + Node::new_with( + id, + vec![Node::new_with(id1, vec![Node::new(id3)]), Node::new(id2)] + ) + ); + + // Removal + world.entity_mut(id1).remove::(); + let hierarchy = get_hierarchy(&world, id); + assert_eq!(hierarchy, Node::new_with(id, vec![Node::new(id2)])); + + // Insert + world.entity_mut(id1).insert(Parent(id)); + let hierarchy = get_hierarchy(&world, id); + assert_eq!( + hierarchy, + Node::new_with( + id, + vec![Node::new(id2), Node::new_with(id1, vec![Node::new(id3)])] + ) + ); + + // Recursive Despawn + world.entity_mut(id).despawn(); + assert!(world.get_entity(id).is_err()); + assert!(world.get_entity(id1).is_err()); + assert!(world.get_entity(id2).is_err()); + assert!(world.get_entity(id3).is_err()); + } + + #[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" + ); + } +} diff --git a/crates/bevy_ecs/src/lib.rs b/crates/bevy_ecs/src/lib.rs index ef5db63779e1e..3b7b68771785e 100644 --- a/crates/bevy_ecs/src/lib.rs +++ b/crates/bevy_ecs/src/lib.rs @@ -33,6 +33,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; @@ -41,6 +42,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; @@ -63,6 +65,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/relationship.rs b/crates/bevy_ecs/src/relationship.rs new file mode 100644 index 0000000000000..7bd78df382452 --- /dev/null +++ b/crates/bevy_ecs/src/relationship.rs @@ -0,0 +1,494 @@ +// TODO: remove this +#![allow(missing_docs)] + +use crate::{ + bundle::Bundle, + component::{Component, ComponentId, Mutable}, + entity::Entity, + query::{QueryData, QueryFilter, WorldQuery}, + system::{Commands, EntityCommands, Query}, + world::{DeferredWorld, EntityWorldMut, World}, +}; +use alloc::{collections::VecDeque, vec::Vec}; +use bevy_utils::tracing::warn; +use core::marker::PhantomData; +use smallvec::SmallVec; + +// The "deprecated" state is used to prevent users from mutating the internal RelationshipSource collection. +// These internals are allowed to modify the internal RelationshipSource collection. +#[allow(deprecated)] +pub trait Relationship: Component + Sized { + type RelationshipSources: RelationshipSources; + /// Gets the [`Entity`] ID of the related entity. + fn get(&self) -> Entity; + fn set(&mut self, entity: Entity); + fn from(entity: Entity) -> Self; + fn on_insert(mut world: DeferredWorld, entity: Entity, _: ComponentId) { + let parent = world.entity(entity).get::().unwrap().get(); + if parent == entity { + warn!( + "The {}({parent:?}) relationship on entity {entity:?} points to itself. The invalid {} relationship has been removed.", + std::any::type_name::(), + std::any::type_name::() + ); + world.commands().entity(entity).remove::(); + } + if let Ok(mut parent_entity) = world.get_entity_mut(parent) { + if let Some(mut relationship_sources) = + parent_entity.get_mut::() + { + relationship_sources.collection_mut().add(entity); + } else { + let mut sources = + ::with_capacity(1); + sources.collection_mut().add(entity); + world.commands().entity(parent).insert(sources); + } + } else { + warn!( + "The {}({parent:?}) relationship on entity {entity:?} relates to an entity that does not exist. The invalid {} relationship has been removed.", + std::any::type_name::(), + std::any::type_name::() + ); + world.commands().entity(entity).remove::(); + } + } + + fn on_remove(mut world: DeferredWorld, entity: Entity, _: ComponentId) { + let parent = world.entity(entity).get::().unwrap().get(); + if let Ok(mut parent_entity) = world.get_entity_mut(parent) { + if let Some(mut relationship_sources) = + parent_entity.get_mut::() + { + relationship_sources.collection_mut().remove(entity); + if relationship_sources.len() == 0 { + world + .commands() + .entity(parent) + .remove::(); + } + } + } + } + + fn on_replace(mut world: DeferredWorld, entity: Entity, _: ComponentId) { + let parent = world.entity(entity).get::().unwrap().get(); + if let Ok(mut parent_entity) = world.get_entity_mut(parent) { + if let Some(mut relationship_sources) = + parent_entity.get_mut::() + { + relationship_sources.collection_mut().remove(entity); + } + } + } +} + +// The "deprecated" state is used to prevent users from mutating the internal RelationshipSource collection. +// These internals are allowed to modify the internal RelationshipSource collection. +#[allow(deprecated)] +pub trait RelationshipSources: Component + Sized { + type Relationship: Relationship; + type Collection: RelationshipSourceCollection; + + fn collection(&self) -> &Self::Collection; + #[deprecated = "Modifying the internal RelationshipSource collection should only be done by internals as it can invalidate relationships."] + fn collection_mut(&mut self) -> &mut Self::Collection; + #[deprecated = "Creating a relationship source manually should only be done by internals as it can invalidate relationships."] + fn from_collection(collection: Self::Collection) -> Self; + + // TODO: this should probably be an on_despawn hook to avoid accidentally despawning on removal + // on_despawn could also take a &mut EntityWorldMut, which would allow doing this without commands + fn on_remove(mut world: DeferredWorld, entity: Entity, _: ComponentId) { + let entities = world + .entity_mut(entity) + .get_mut::() + .unwrap() + .collection_mut() + .take(); + let mut commands = world.commands(); + for entity in entities { + if let Some(mut entity) = commands.get_entity(entity) { + entity.despawn(); + } else { + warn!("Tried to despawn non-existent entity {}", entity); + } + } + } + + fn with_capacity(capacity: usize) -> Self { + let collection = + ::with_capacity(capacity); + Self::from_collection(collection) + } + + #[inline] + fn iter(&self) -> impl DoubleEndedIterator { + self.collection().iter() + } + + #[inline] + fn len(&self) -> usize { + self.collection().len() + } +} + +pub trait RelationshipSourceCollection { + fn with_capacity(capacity: usize) -> Self; + fn add(&mut self, entity: Entity); + fn remove(&mut self, entity: Entity); + fn iter(&self) -> impl DoubleEndedIterator; + fn take(&mut self) -> Vec; + fn len(&self) -> usize; +} + +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) = self.into_iter().position(|e| *e == entity) { + Vec::remove(self, index); + } + } + + fn iter(&self) -> impl DoubleEndedIterator { + self.into_iter().copied() + } + + fn take(&mut self) -> Vec { + std::mem::take(self) + } + + fn len(&self) -> usize { + Vec::len(&self) + } +} + +impl<'w, 's, D: QueryData, F: QueryFilter> Query<'w, 's, D, F> { + pub fn related(&'w self, entity: Entity) -> Option + where + ::ReadOnly: WorldQuery = &'w R>, + { + self.get(entity).map(R::get).ok() + } + + pub fn relationship_sources( + &'w self, + entity: Entity, + ) -> impl Iterator + 'w + where + ::ReadOnly: WorldQuery = &'w S>, + { + self.get(entity) + .into_iter() + .flat_map(|children| children.iter()) + } + + 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, + } + } + + 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) + }) + } + + pub fn iter_siblings( + &'w self, + entity: Entity, + ) -> impl Iterator + 'w + where + D::ReadOnly: WorldQuery = (Option<&'w R>, Option<&'w R::RelationshipSources>)>, + { + 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)) + } + + pub fn iter_descendants( + &'w self, + entity: Entity, + ) -> DescendantIter<'w, 's, D, F, S> + where + D::ReadOnly: WorldQuery = &'w S>, + { + DescendantIter::new(self, entity) + } + + 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) + } + + 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: RelationshipSources> +where + D::ReadOnly: WorldQuery = &'w S>, +{ + children_query: &'w Query<'w, 's, D, F>, + vecdeque: VecDeque, +} + +impl<'w, 's, D: QueryData, F: QueryFilter, S: RelationshipSources> 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(|s| s.iter()) + .collect(), + } + } +} + +impl<'w, 's, D: QueryData, F: QueryFilter, S: RelationshipSources> 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: RelationshipSources> +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: RelationshipSources> + 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: RelationshipSources> 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 + } +} + +pub struct RelatedSpawner<'w, R: Relationship> { + target: Entity, + world: &'w mut World, + _marker: PhantomData, +} + +impl<'w, R: Relationship> RelatedSpawner<'w, R> { + pub fn new(world: &'w mut World, target: Entity) -> Self { + Self { + world, + target, + _marker: PhantomData, + } + } + + pub fn spawn(&mut self, bundle: impl Bundle) -> EntityWorldMut<'_> { + self.world.spawn((R::from(self.target), bundle)) + } + + pub fn spawn_empty(&mut self) -> EntityWorldMut<'_> { + self.world.spawn(R::from(self.target)) + } + + pub fn target_entity(&self) -> Entity { + self.target + } +} + +pub struct RelatedSpawnerCommands<'w, R: Relationship> { + target: Entity, + commands: Commands<'w, 'w>, + _marker: PhantomData, +} + +impl<'w, R: Relationship> RelatedSpawnerCommands<'w, R> { + pub fn new(commands: Commands<'w, 'w>, target: Entity) -> Self { + Self { + commands, + target, + _marker: PhantomData, + } + } + pub fn spawn(&mut self, bundle: impl Bundle) -> EntityCommands<'_> { + self.commands.spawn((R::from(self.target), bundle)) + } + + pub fn spawn_empty(&mut self) -> EntityCommands<'_> { + self.commands.spawn(R::from(self.target)) + } + + pub fn target_entity(&self) -> Entity { + self.target + } +} + +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 + } +} + +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 + } +} diff --git a/crates/bevy_ecs/src/traversal.rs b/crates/bevy_ecs/src/traversal.rs index a8605e94ec8d0..598a0e373f3de 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,14 @@ impl Traversal for () { None } } + +/// 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 &R { + fn traverse(item: Self::Item<'_>, _data: &D) -> Option { + Some(item.get()) + } +} diff --git a/crates/bevy_gltf/Cargo.toml b/crates/bevy_gltf/Cargo.toml index 9bc1ca3e7d047..92cab6bd88b14 100644 --- a/crates/bevy_gltf/Cargo.toml +++ b/crates/bevy_gltf/Cargo.toml @@ -24,7 +24,6 @@ bevy_asset = { path = "../bevy_asset", version = "0.15.0-dev" } bevy_color = { path = "../bevy_color", version = "0.15.0-dev" } bevy_core_pipeline = { path = "../bevy_core_pipeline", version = "0.15.0-dev" } bevy_ecs = { path = "../bevy_ecs", version = "0.15.0-dev" } -bevy_hierarchy = { path = "../bevy_hierarchy", version = "0.15.0-dev" } bevy_image = { path = "../bevy_image", version = "0.15.0-dev" } bevy_math = { path = "../bevy_math", version = "0.15.0-dev" } bevy_pbr = { path = "../bevy_pbr", version = "0.15.0-dev" } diff --git a/crates/bevy_gltf/src/loader.rs b/crates/bevy_gltf/src/loader.rs index 116ecb23c39a7..1477f2d9bb769 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, @@ -1375,7 +1375,7 @@ fn warn_on_differing_texture_transforms( #[allow(clippy::too_many_arguments, clippy::result_large_err)] fn load_node( gltf_node: &Node, - world_builder: &mut WorldChildBuilder, + child_spawner: &mut ChildSpawner, root_load_context: &LoadContext, load_context: &mut LoadContext, settings: &GltfLoaderSettings, @@ -1397,7 +1397,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 2e536798a3a87..0000000000000 --- a/crates/bevy_hierarchy/Cargo.toml +++ /dev/null @@ -1,66 +0,0 @@ -[package] -name = "bevy_hierarchy" -version = "0.15.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.15.0-dev", default-features = false, optional = true } -bevy_ecs = { path = "../bevy_ecs", version = "0.15.0-dev", default-features = false } -bevy_reflect = { path = "../bevy_reflect", version = "0.15.0-dev", features = [ - "smallvec", -], default-features = false, optional = true } -bevy_utils = { path = "../bevy_utils", version = "0.15.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 8dd9cd9e5d017..0000000000000 --- a/crates/bevy_hierarchy/src/child_builder.rs +++ /dev/null @@ -1,1197 +0,0 @@ -use crate::{Children, HierarchyEvent, Parent}; -use bevy_ecs::{ - bundle::Bundle, - entity::Entity, - event::Events, - system::{Commands, EntityCommands}, - world::{Command, 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. - /// - /// 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. - /// - /// 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. - /// - /// 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 - /// - /// 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. - /// - /// 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 will be removed if it exists, otherwise this does nothing. - fn clear_children(&mut self) -> &mut Self; - - /// Removes all current children from this entity, replacing them with the specified list of entities. - /// - /// 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 |entity: Entity, world: &mut World| { - world.entity_mut(entity).add_children(&children); - }) - } - - fn with_child(&mut self, bundle: B) -> &mut Self { - let child = self.commands().spawn(bundle).id(); - self.queue(move |entity: Entity, world: &mut World| { - world.entity_mut(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 |entity: Entity, world: &mut World| { - world.entity_mut(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 |entity: Entity, world: &mut World| { - world.entity_mut(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 |entity: Entity, world: &mut World| { - world.entity_mut(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 |entity: Entity, world: &mut World| { - world.entity_mut(entity).add_child(child); - }) - } - - fn clear_children(&mut self) -> &mut Self { - self.queue(move |entity: Entity, world: &mut World| { - world.entity_mut(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 |entity: Entity, world: &mut World| { - world.entity_mut(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 |entity: Entity, world: &mut World| { - world.entity_mut(parent).add_child(entity); - }) - } - - fn remove_parent(&mut self) -> &mut Self { - self.queue(move |entity: Entity, world: &mut World| { - world.entity_mut(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 { - command.apply(self.world); - self - } -} - -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, - }], - ); - } - - #[allow(dead_code)] - #[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 7ab31a588535c..0000000000000 --- a/crates/bevy_hierarchy/src/hierarchy.rs +++ /dev/null @@ -1,481 +0,0 @@ -use crate::{ - components::{Children, Parent}, - BuildChildren, -}; -use bevy_ecs::{ - component::ComponentCloneHandler, - entity::{ComponentCloneCtx, Entity, EntityCloneBuilder}, - system::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(move |entity: Entity, world: &mut World| { - #[cfg(feature = "trace")] - let _span = tracing::info_span!( - "command", - name = "DespawnRecursive", - entity = tracing::field::debug(entity), - warn = tracing::field::debug(warn) - ) - .entered(); - despawn_with_children_recursive(world, entity, warn); - }); - } - - fn despawn_descendants(&mut self) -> &mut Self { - let warn = true; - self.queue(move |entity: Entity, world: &mut World| { - #[cfg(feature = "trace")] - let _span = tracing::info_span!( - "command", - name = "DespawnChildrenRecursive", - entity = tracing::field::debug(entity), - warn = tracing::field::debug(warn) - ) - .entered(); - despawn_children_recursive(world, entity, 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(move |entity: Entity, world: &mut World| { - #[cfg(feature = "trace")] - let _span = tracing::info_span!( - "command", - name = "TryDespawnRecursive", - entity = tracing::field::debug(entity), - warn = tracing::field::debug(warn) - ) - .entered(); - despawn_with_children_recursive(world, entity, warn); - }); - } - - fn try_despawn_descendants(&mut self) -> &mut Self { - let warn = false; - self.queue(move |entity: Entity, world: &mut World| { - #[cfg(feature = "trace")] - let _span = tracing::info_span!( - "command", - name = "TryDespawnChildrenRecursive", - entity = tracing::field::debug(entity), - warn = tracing::field::debug(warn) - ) - .entered(); - despawn_children_recursive(world, entity, warn); - }); - 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 ed86ecdec09af..602880a394355 100644 --- a/crates/bevy_input_focus/Cargo.toml +++ b/crates/bevy_input_focus/Cargo.toml @@ -13,7 +13,6 @@ rust-version = "1.83.0" bevy_app = { path = "../bevy_app", version = "0.15.0-dev", default-features = false } bevy_ecs = { path = "../bevy_ecs", version = "0.15.0-dev", default-features = false } bevy_input = { path = "../bevy_input", version = "0.15.0-dev", default-features = false } -bevy_hierarchy = { path = "../bevy_hierarchy", version = "0.15.0-dev", default-features = false } bevy_utils = { path = "../bevy_utils", version = "0.15.0-dev", default-features = false } bevy_window = { path = "../bevy_window", version = "0.15.0-dev", default-features = false } diff --git a/crates/bevy_input_focus/src/lib.rs b/crates/bevy_input_focus/src/lib.rs index 4dff21644f3f3..2b8af59560fbe 100644 --- a/crates/bevy_input_focus/src/lib.rs +++ b/crates/bevy_input_focus/src/lib.rs @@ -25,7 +25,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}; use bevy_window::{PrimaryWindow, Window}; use core::fmt::Debug; @@ -339,7 +338,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 d47744a357820..ea1fefea74788 100644 --- a/crates/bevy_input_focus/src/tab_navigation.rs +++ b/crates/bevy_input_focus/src/tab_navigation.rs @@ -27,11 +27,11 @@ use bevy_app::{App, Plugin, Startup}; 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, @@ -346,7 +346,6 @@ pub fn handle_tab_navigation( #[cfg(test)] mod tests { use bevy_ecs::system::SystemState; - use bevy_hierarchy::BuildChildren; use super::*; diff --git a/crates/bevy_internal/Cargo.toml b/crates/bevy_internal/Cargo.toml index 41e974ffcff3f..8f97f70b057f7 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.15.0-dev" } bevy_diagnostic = { path = "../bevy_diagnostic", version = "0.15.0-dev" } bevy_ecs = { path = "../bevy_ecs", version = "0.15.0-dev" } bevy_state = { path = "../bevy_state", optional = true, version = "0.15.0-dev" } -bevy_hierarchy = { path = "../bevy_hierarchy", version = "0.15.0-dev" } bevy_input = { path = "../bevy_input", version = "0.15.0-dev" } bevy_input_focus = { path = "../bevy_input_focus", version = "0.15.0-dev" } bevy_log = { path = "../bevy_log", version = "0.15.0-dev" } diff --git a/crates/bevy_internal/src/default_plugins.rs b/crates/bevy_internal/src/default_plugins.rs index bbfa4b1a566bc..e081b43fa0a15 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 3deba7d21bcba..5cc9e96aa7306 100644 --- a/crates/bevy_picking/Cargo.toml +++ b/crates/bevy_picking/Cargo.toml @@ -16,7 +16,6 @@ bevy_app = { path = "../bevy_app", version = "0.15.0-dev" } bevy_asset = { path = "../bevy_asset", version = "0.15.0-dev" } bevy_derive = { path = "../bevy_derive", version = "0.15.0-dev" } bevy_ecs = { path = "../bevy_ecs", version = "0.15.0-dev" } -bevy_hierarchy = { path = "../bevy_hierarchy", version = "0.15.0-dev" } bevy_input = { path = "../bevy_input", version = "0.15.0-dev" } bevy_math = { path = "../bevy_math", version = "0.15.0-dev" } bevy_mesh = { path = "../bevy_mesh", version = "0.15.0-dev", optional = true } diff --git a/crates/bevy_picking/src/events.rs b/crates/bevy_picking/src/events.rs index 9d8bda32bda72..ad71f7997a5fb 100644 --- a/crates/bevy_picking/src/events.rs +++ b/crates/bevy_picking/src/events.rs @@ -40,7 +40,6 @@ use core::fmt::Debug; 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 321ed6b5e6da2..8db97659254f7 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}, @@ -267,6 +266,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 4a12f7742c997..ec98776ae9820 100644 --- a/crates/bevy_remote/Cargo.toml +++ b/crates/bevy_remote/Cargo.toml @@ -19,7 +19,6 @@ bevy_derive = { path = "../bevy_derive", version = "0.15.0-dev" } bevy_ecs = { path = "../bevy_ecs", version = "0.15.0-dev", features = [ "serialize", ] } -bevy_hierarchy = { path = "../bevy_hierarchy", version = "0.15.0-dev" } bevy_reflect = { path = "../bevy_reflect", version = "0.15.0-dev" } bevy_tasks = { path = "../bevy_tasks", version = "0.15.0-dev" } bevy_utils = { path = "../bevy_utils", version = "0.15.0-dev" } diff --git a/crates/bevy_remote/src/builtin_methods.rs b/crates/bevy_remote/src/builtin_methods.rs index 0a6391595aae3..5f2816b136b06 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}, @@ -752,7 +752,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 6e1407f326cbf..0a4e57750c3c7 100644 --- a/crates/bevy_render/Cargo.toml +++ b/crates/bevy_render/Cargo.toml @@ -44,7 +44,6 @@ bevy_derive = { path = "../bevy_derive", version = "0.15.0-dev" } bevy_diagnostic = { path = "../bevy_diagnostic", version = "0.15.0-dev" } bevy_ecs = { path = "../bevy_ecs", version = "0.15.0-dev" } bevy_encase_derive = { path = "../bevy_encase_derive", version = "0.15.0-dev" } -bevy_hierarchy = { path = "../bevy_hierarchy", version = "0.15.0-dev" } bevy_math = { path = "../bevy_math", version = "0.15.0-dev" } bevy_reflect = { path = "../bevy_reflect", version = "0.15.0-dev", features = [ "bevy", diff --git a/crates/bevy_render/src/lib.rs b/crates/bevy_render/src/lib.rs index 7e206ffd2fb25..fae994fb078c2 100644 --- a/crates/bevy_render/src/lib.rs +++ b/crates/bevy_render/src/lib.rs @@ -75,7 +75,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; @@ -352,7 +351,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 7a7829e0f4ef1..79a40b84bfd9d 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}; @@ -15,12 +14,7 @@ use allocator::MeshAllocatorPlugin; use bevy_app::{App, Plugin, PostUpdate}; use bevy_asset::{AssetApp, AssetId, RenderAssetUsages}; use bevy_ecs::{ - entity::Entity, - query::{Changed, With}, - 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 a004cf19e0450..58d6fd716f70b 100644 --- a/crates/bevy_render/src/view/visibility/mod.rs +++ b/crates/bevy_render/src/view/visibility/mod.rs @@ -14,19 +14,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. @@ -113,6 +112,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 { @@ -669,7 +669,6 @@ where mod test { use super::*; use bevy_app::prelude::*; - use bevy_hierarchy::BuildChildren; #[test] fn visibility_propagation() { diff --git a/crates/bevy_render/src/view/window/screenshot.rs b/crates/bevy_render/src/view/window/screenshot.rs index 1b7db5b4211e7..7d97a08e9515b 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; @@ -188,7 +187,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(); } } @@ -243,7 +242,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 91eecb563b69b..afe085431440e 100644 --- a/crates/bevy_scene/Cargo.toml +++ b/crates/bevy_scene/Cargo.toml @@ -21,7 +21,6 @@ bevy_ecs = { path = "../bevy_ecs", version = "0.15.0-dev" } bevy_reflect = { path = "../bevy_reflect", version = "0.15.0-dev", features = [ "bevy", ] } -bevy_hierarchy = { path = "../bevy_hierarchy", version = "0.15.0-dev" } bevy_transform = { path = "../bevy_transform", version = "0.15.0-dev" } bevy_utils = { path = "../bevy_utils", version = "0.15.0-dev" } bevy_render = { path = "../bevy_render", version = "0.15.0-dev", optional = true } diff --git a/crates/bevy_scene/src/bundle.rs b/crates/bevy_scene/src/bundle.rs index 0024b2f77729b..7ae335a27fb1f 100644 --- a/crates/bevy_scene/src/bundle.rs +++ b/crates/bevy_scene/src/bundle.rs @@ -116,9 +116,9 @@ mod tests { use bevy_ecs::{ component::Component, entity::Entity, + hierarchy::Children, prelude::{AppTypeRegistry, ReflectComponent, World}, }; - use bevy_hierarchy::{Children, HierarchyPlugin}; use bevy_reflect::Reflect; #[derive(Component, Reflect, Default)] @@ -133,7 +133,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::(); diff --git a/crates/bevy_scene/src/dynamic_scene.rs b/crates/bevy_scene/src/dynamic_scene.rs index f29c925f7e4d4..c3f45c36eae5b 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; diff --git a/crates/bevy_scene/src/scene_spawner.rs b/crates/bevy_scene/src/scene_spawner.rs index 7117542d83100..a440b18ee2ad6 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; @@ -194,9 +194,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(); }; } } @@ -736,7 +735,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 98f14fe7d604b..c3eda87548c21 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,15 +17,11 @@ 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 @@ -37,7 +33,6 @@ std = [ "bevy_utils/std", "bevy_reflect?/std", "bevy_app?/std", - "bevy_hierarchy?/std", ] ## `critical-section` provides the building blocks for synchronization primitives @@ -63,7 +58,6 @@ bevy_state_macros = { path = "macros", version = "0.15.0-dev" } bevy_utils = { path = "../bevy_utils", version = "0.15.0-dev", default-features = false } bevy_reflect = { path = "../bevy_reflect", version = "0.15.0-dev", default-features = false, optional = true } bevy_app = { path = "../bevy_app", version = "0.15.0-dev", default-features = false, optional = true } -bevy_hierarchy = { path = "../bevy_hierarchy", version = "0.15.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..083f5ed959838 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::*; @@ -83,9 +81,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 80f89a6e5d1e4..23fc9b138b0e8 100644 --- a/crates/bevy_text/Cargo.toml +++ b/crates/bevy_text/Cargo.toml @@ -18,7 +18,6 @@ bevy_asset = { path = "../bevy_asset", version = "0.15.0-dev" } bevy_color = { path = "../bevy_color", version = "0.15.0-dev" } bevy_derive = { path = "../bevy_derive", version = "0.15.0-dev" } bevy_ecs = { path = "../bevy_ecs", version = "0.15.0-dev" } -bevy_hierarchy = { path = "../bevy_hierarchy", version = "0.15.0-dev" } bevy_image = { path = "../bevy_image", version = "0.15.0-dev" } bevy_math = { path = "../bevy_math", version = "0.15.0-dev" } bevy_reflect = { path = "../bevy_reflect", version = "0.15.0-dev", features = [ diff --git a/crates/bevy_text/src/text.rs b/crates/bevy_text/src/text.rs index 9ac844c099a75..e6afd624a78ec 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::warn_once; use cosmic_text::{Buffer, Metrics}; @@ -168,7 +167,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(); @@ -470,7 +468,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 @@ -501,7 +499,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 672383dc445be..e1cacf08d38f9 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.15.0-dev", default-features = false, optional = true } bevy_ecs = { path = "../bevy_ecs", version = "0.15.0-dev", default-features = false, optional = true } -bevy_hierarchy = { path = "../bevy_hierarchy", version = "0.15.0-dev", default-features = false, features = [ - "bevy_app", -], optional = true } bevy_math = { path = "../bevy_math", version = "0.15.0-dev", default-features = false } bevy_reflect = { path = "../bevy_reflect", version = "0.15.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 4ea8f0d1f737a..8e7769a8f1149 100644 --- a/crates/bevy_transform/src/commands.rs +++ b/crates/bevy_transform/src/commands.rs @@ -4,10 +4,10 @@ use crate::prelude::{GlobalTransform, Transform}; use bevy_ecs::{ entity::Entity, + hierarchy::Parent, system::EntityCommands, world::{EntityWorldMut, World}, }; -use bevy_hierarchy::BuildChildren; /// Collection of methods similar to [`BuildChildren`], but preserving each /// entity's [`GlobalTransform`]. @@ -73,7 +73,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 0a04baadafd02..7650105f23ee6 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 { @@ -47,7 +47,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), @@ -158,7 +162,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/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..a2f951fea164b 100644 --- a/crates/bevy_transform/src/plugins.rs +++ b/crates/bevy_transform/src/plugins.rs @@ -1,6 +1,5 @@ use bevy_app::{App, Plugin, PostStartup, PostUpdate}; use bevy_ecs::schedule::{IntoSystemConfigs, IntoSystemSetConfigs, SystemSet}; -use bevy_hierarchy::ValidParentCheckPlugin; use crate::{ components::GlobalTransform, @@ -32,36 +31,35 @@ impl Plugin for TransformPlugin { 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..6257f2cfcb1ed 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); @@ -461,10 +453,14 @@ mod test { app.world_mut() .spawn(Transform::IDENTITY) .add_children(&[child]); - core::mem::swap( - &mut *app.world_mut().get_mut::(child).unwrap(), - &mut *temp.get_mut::(grandchild).unwrap(), - ); + let grandchild_parent = app.world().entity(grandchild).get::().unwrap().0; + let child_parent = app.world().entity(child).get::().unwrap().0; + app.world_mut() + .entity_mut(child) + .insert(Parent(grandchild_parent)); + app.world_mut() + .entity_mut(grandchild) + .insert(Parent(child_parent)); app.update(); } @@ -501,7 +497,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 e8548a405df2f..443353f65c78f 100644 --- a/crates/bevy_ui/Cargo.toml +++ b/crates/bevy_ui/Cargo.toml @@ -17,7 +17,6 @@ bevy_color = { path = "../bevy_color", version = "0.15.0-dev" } bevy_core_pipeline = { path = "../bevy_core_pipeline", version = "0.15.0-dev" } bevy_derive = { path = "../bevy_derive", version = "0.15.0-dev" } bevy_ecs = { path = "../bevy_ecs", version = "0.15.0-dev" } -bevy_hierarchy = { path = "../bevy_hierarchy", version = "0.15.0-dev" } bevy_image = { path = "../bevy_image", version = "0.15.0-dev" } bevy_input = { path = "../bevy_input", version = "0.15.0-dev" } bevy_math = { path = "../bevy_math", version = "0.15.0-dev" } diff --git a/crates/bevy_ui/src/experimental/ghost_hierarchy.rs b/crates/bevy_ui/src/experimental/ghost_hierarchy.rs index 42832c0483500..2513f42aeb70f 100644 --- a/crates/bevy_ui/src/experimental/ghost_hierarchy.rs +++ b/crates/bevy_ui/src/experimental/ghost_hierarchy.rs @@ -1,7 +1,6 @@ //! This module contains [`GhostNode`] and utilities to flatten the UI hierarchy, traversing past ghost nodes. use bevy_ecs::{prelude::*, system::SystemParam}; -use bevy_hierarchy::{Children, HierarchyQueryExt, Parent}; use bevy_reflect::prelude::*; use bevy_render::view::Visibility; use bevy_transform::prelude::Transform; @@ -168,8 +167,7 @@ mod tests { system::{Query, SystemState}, world::World, }; - use bevy_hierarchy::{BuildChildren, ChildBuild}; - + use super::{GhostNode, Node, UiChildren, UiRootNodes}; #[derive(Component, PartialEq, Debug)] diff --git a/crates/bevy_ui/src/layout/mod.rs b/crates/bevy_ui/src/layout/mod.rs index 4c24ca322e065..0bac705ab8caa 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; @@ -474,18 +469,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}; @@ -782,7 +766,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 a8283e0938ea7..6f8fb4eca48d0 100644 --- a/crates/bevy_ui/src/stack.rs +++ b/crates/bevy_ui/src/stack.rs @@ -133,8 +133,7 @@ mod tests { system::Commands, world::{CommandQueue, World}, }; - use bevy_hierarchy::{BuildChildren, ChildBuild}; - + use crate::{GlobalZIndex, Node, UiStack, ZIndex}; use super::ui_stack_system; diff --git a/crates/bevy_winit/Cargo.toml b/crates/bevy_winit/Cargo.toml index 02ff9f56a79b1..2a0f4c780b2b0 100644 --- a/crates/bevy_winit/Cargo.toml +++ b/crates/bevy_winit/Cargo.toml @@ -27,7 +27,6 @@ bevy_a11y = { path = "../bevy_a11y", version = "0.15.0-dev" } bevy_app = { path = "../bevy_app", version = "0.15.0-dev" } bevy_derive = { path = "../bevy_derive", version = "0.15.0-dev" } bevy_ecs = { path = "../bevy_ecs", version = "0.15.0-dev" } -bevy_hierarchy = { path = "../bevy_hierarchy", version = "0.15.0-dev" } bevy_input = { path = "../bevy_input", version = "0.15.0-dev" } bevy_input_focus = { path = "../bevy_input_focus", version = "0.15.0-dev" } bevy_log = { path = "../bevy_log", version = "0.15.0-dev" } diff --git a/crates/bevy_winit/src/accessibility.rs b/crates/bevy_winit/src/accessibility.rs index 078f47ae4377a..63eb351159b8e 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/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 69f150368e87f..b497643fac165 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 a7bd51a5dd56d..37b1e3b635e4b 100644 --- a/examples/stress_tests/many_buttons.rs +++ b/examples/stress_tests/many_buttons.rs @@ -249,7 +249,7 @@ fn setup_grid(mut commands: Commands, asset_server: Res, args: Res< #[allow(clippy::too_many_arguments)] fn spawn_button( - commands: &mut ChildBuilder, + commands: &mut ChildSpawnerCommands, background_color: Color, buttons: f32, column: usize, @@ -297,5 +297,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 16a41328935f5..363718897ac23 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)); } } From 961ea730b4b2054b382308683970abe7c9ceb600 Mon Sep 17 00:00:00 2001 From: Carter Anderson Date: Fri, 10 Jan 2025 16:30:02 -0800 Subject: [PATCH 02/38] OnDespawn --- crates/bevy_ecs/src/archetype.rs | 24 +++++++-- crates/bevy_ecs/src/component.rs | 24 +++++++++ crates/bevy_ecs/src/observer/mod.rs | 12 +++++ crates/bevy_ecs/src/relationship.rs | 54 +++++++++++++------ .../bevy_ecs/src/world/component_constants.rs | 9 ++++ crates/bevy_ecs/src/world/deferred_world.rs | 22 ++++++++ crates/bevy_ecs/src/world/entity_ref.rs | 6 ++- 7 files changed, 129 insertions(+), 22 deletions(-) diff --git a/crates/bevy_ecs/src/archetype.rs b/crates/bevy_ecs/src/archetype.rs index e0d85242f9206..9dd48c4335523 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/component.rs b/crates/bevy_ecs/src/component.rs index 66d5db98f8fcb..2ceec518d0d10 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`]. diff --git a/crates/bevy_ecs/src/observer/mod.rs b/crates/bevy_ecs/src/observer/mod.rs index 8194c1e04ea65..8c04843e98e0a 100644 --- a/crates/bevy_ecs/src/observer/mod.rs +++ b/crates/bevy_ecs/src/observer/mod.rs @@ -352,6 +352,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, } @@ -363,6 +364,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(), } } @@ -373,6 +375,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), } } @@ -447,6 +450,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, } } @@ -483,6 +487,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); + } } } diff --git a/crates/bevy_ecs/src/relationship.rs b/crates/bevy_ecs/src/relationship.rs index 7bd78df382452..530a91908f6c6 100644 --- a/crates/bevy_ecs/src/relationship.rs +++ b/crates/bevy_ecs/src/relationship.rs @@ -62,10 +62,9 @@ pub trait Relationship: Component + Sized { { relationship_sources.collection_mut().remove(entity); if relationship_sources.len() == 0 { - world - .commands() - .entity(parent) - .remove::(); + if let Some(mut entity) = world.commands().get_entity(parent) { + entity.remove::(); + } } } } @@ -98,19 +97,40 @@ pub trait RelationshipSources: Component + Sized { // TODO: this should probably be an on_despawn hook to avoid accidentally despawning on removal // on_despawn could also take a &mut EntityWorldMut, which would allow doing this without commands - fn on_remove(mut world: DeferredWorld, entity: Entity, _: ComponentId) { - let entities = world - .entity_mut(entity) - .get_mut::() - .unwrap() - .collection_mut() - .take(); - let mut commands = world.commands(); - for entity in entities { - if let Some(mut entity) = commands.get_entity(entity) { - entity.despawn(); - } else { - warn!("Tried to despawn non-existent entity {}", entity); + fn on_replace(mut world: DeferredWorld, entity: Entity, _: ComponentId) { + unsafe { + let world = world.as_unsafe_world_cell(); + let sources = world.get_entity(entity).unwrap().get::().unwrap(); + let mut commands = world.get_raw_command_queue(); + for source_entity in sources.iter() { + if world.get_entity(source_entity).is_some() { + commands.push( + entity_commands::remove(Self::Relationship) + .with_entity(source_entity) + .with_error_handler(error_handler::silent()), + ); + } else { + warn!("Tried to despawn non-existent entity {}", source_entity); + } + } + } + } + + fn on_despawn(mut world: DeferredWorld, entity: Entity, _: ComponentId) { + unsafe { + let world = world.as_unsafe_world_cell(); + let sources = world.get_entity(entity).unwrap().get::().unwrap(); + let mut commands = world.get_raw_command_queue(); + for source_entity in sources.iter() { + if world.has_entity(source_entity) { + commands.push( + entity_commands::despawn() + .with_entity(source_entity) + .with_error_handler(error_handler::silent()), + ); + } else { + warn!("Tried to despawn non-existent entity {}", source_entity); + } } } } diff --git a/crates/bevy_ecs/src/world/component_constants.rs b/crates/bevy_ecs/src/world/component_constants.rs index 5eea8dc6229ef..7147e27e6ae7d 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(3); /// 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 36b8176e3cb1a..44a38bf20c21f 100644 --- a/crates/bevy_ecs/src/world/deferred_world.rs +++ b/crates/bevy_ecs/src/world/deferred_world.rs @@ -588,6 +588,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 9080be1b9bba8..d862f2d61048f 100644 --- a/crates/bevy_ecs/src/world/entity_ref.rs +++ b/crates/bevy_ecs/src/world/entity_ref.rs @@ -12,7 +12,7 @@ use crate::{ removal_detection::RemovedComponentEvents, storage::Storages, system::IntoObserverSystem, - world::{error::EntityComponentError, DeferredWorld, Mut, World}, + world::{error::EntityComponentError, DeferredWorld, Mut, World, ON_DESPAWN}, }; use alloc::vec::Vec; use bevy_ptr::{OwningPtr, Ptr}; @@ -2056,6 +2056,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()); } From c383baf072ea9ccf390aeeb7ab1d313303ee1f71 Mon Sep 17 00:00:00 2001 From: Carter Anderson Date: Mon, 13 Jan 2025 18:31:28 -0800 Subject: [PATCH 03/38] Minor fixes and clippy --- crates/bevy_ecs/src/hierarchy.rs | 4 +- crates/bevy_ecs/src/relationship.rs | 51 ++++++++++--------- crates/bevy_input_focus/src/tab_navigation.rs | 7 ++- crates/bevy_ui/src/stack.rs | 2 +- 4 files changed, 33 insertions(+), 31 deletions(-) diff --git a/crates/bevy_ecs/src/hierarchy.rs b/crates/bevy_ecs/src/hierarchy.rs index 089d935eaa9f3..39d978b507f28 100644 --- a/crates/bevy_ecs/src/hierarchy.rs +++ b/crates/bevy_ecs/src/hierarchy.rs @@ -39,7 +39,6 @@ use log::warn; #[component( immutable, on_insert = Self::on_insert, - on_remove = Self::on_remove, on_replace = Self::on_replace, )] pub struct Parent(pub Entity); @@ -88,6 +87,7 @@ impl Component for Children { fn register_component_hooks(hooks: &mut crate::component::ComponentHooks) { hooks.on_replace(Self::on_replace); + hooks.on_despawn(Self::on_despawn); } fn get_component_clone_handler() -> ComponentCloneHandler { @@ -106,7 +106,7 @@ impl<'a> IntoIterator for &'a Children { } } -impl std::ops::Deref for Children { +impl core::ops::Deref for Children { type Target = [Entity]; fn deref(&self) -> &Self::Target { diff --git a/crates/bevy_ecs/src/relationship.rs b/crates/bevy_ecs/src/relationship.rs index 96479378b62a9..6e2655eb5e9ac 100644 --- a/crates/bevy_ecs/src/relationship.rs +++ b/crates/bevy_ecs/src/relationship.rs @@ -32,8 +32,8 @@ pub trait Relationship: Component + Sized { if parent == entity { warn!( "The {}({parent:?}) relationship on entity {entity:?} points to itself. The invalid {} relationship has been removed.", - std::any::type_name::(), - std::any::type_name::() + core::any::type_name::(), + core::any::type_name::() ); world.commands().entity(entity).remove::(); } @@ -51,14 +51,15 @@ pub trait Relationship: Component + Sized { } else { warn!( "The {}({parent:?}) relationship on entity {entity:?} relates to an entity that does not exist. The invalid {} relationship has been removed.", - std::any::type_name::(), - std::any::type_name::() + core::any::type_name::(), + core::any::type_name::() ); world.commands().entity(entity).remove::(); } } - fn on_remove(mut world: DeferredWorld, entity: Entity, _: ComponentId) { + // note: think of this as "on_drop" + fn on_replace(mut world: DeferredWorld, entity: Entity, _: ComponentId) { let parent = world.entity(entity).get::().unwrap().get(); if let Ok(mut parent_entity) = world.get_entity_mut(parent) { if let Some(mut relationship_sources) = @@ -73,17 +74,6 @@ pub trait Relationship: Component + Sized { } } } - - fn on_replace(mut world: DeferredWorld, entity: Entity, _: ComponentId) { - let parent = world.entity(entity).get::().unwrap().get(); - if let Ok(mut parent_entity) = world.get_entity_mut(parent) { - if let Some(mut relationship_sources) = - parent_entity.get_mut::() - { - relationship_sources.collection_mut().remove(entity); - } - } - } } // The "deprecated" state is used to prevent users from mutating the internal RelationshipSource collection. @@ -99,9 +89,10 @@ pub trait RelationshipSources: Component + Sized { #[deprecated = "Creating a relationship source manually should only be done by internals as it can invalidate relationships."] fn from_collection(collection: Self::Collection) -> Self; - // TODO: this should probably be an on_despawn hook to avoid accidentally despawning on removal - // on_despawn could also take a &mut EntityWorldMut, which would allow doing this without commands 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 RelationshipSources collection + // SAFETY: This only reads the Self component and queues Remove commands unsafe { let world = world.as_unsafe_world_cell(); let sources = world.get_entity(entity).unwrap().get::().unwrap(); @@ -121,6 +112,9 @@ pub trait RelationshipSources: Component + Sized { } 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 RelationshipSources collection + // SAFETY: This only reads the Self component and queues despawn commands unsafe { let world = world.as_unsafe_world_cell(); let sources = world.get_entity(entity).unwrap().get::().unwrap(); @@ -154,6 +148,11 @@ pub trait RelationshipSources: Component + Sized { fn len(&self) -> usize { self.collection().len() } + + #[inline] + fn is_empty(&self) -> bool { + self.collection().is_empty() + } } pub trait RelationshipSourceCollection { @@ -163,6 +162,10 @@ pub trait RelationshipSourceCollection { fn iter(&self) -> impl DoubleEndedIterator; fn take(&mut self) -> Vec; fn len(&self) -> usize; + #[inline] + fn is_empty(&self) -> bool { + self.len() == 0 + } } impl RelationshipSourceCollection for Vec { @@ -175,21 +178,21 @@ impl RelationshipSourceCollection for Vec { } fn remove(&mut self, entity: Entity) { - if let Some(index) = self.into_iter().position(|e| *e == entity) { + if let Some(index) = <[Entity]>::iter(self).position(|e| *e == entity) { Vec::remove(self, index); } } fn iter(&self) -> impl DoubleEndedIterator { - self.into_iter().copied() + <[Entity]>::iter(self).copied() } fn take(&mut self) -> Vec { - std::mem::take(self) + core::mem::take(self) } fn len(&self) -> usize { - Vec::len(&self) + Vec::len(self) } } @@ -210,7 +213,7 @@ impl<'w, 's, D: QueryData, F: QueryFilter> Query<'w, 's, D, F> { { self.get(entity) .into_iter() - .flat_map(|children| children.iter()) + .flat_map(RelationshipSources::iter) } pub fn root_ancestor(&'w self, entity: Entity) -> Entity @@ -309,7 +312,7 @@ where vecdeque: children_query .get(entity) .into_iter() - .flat_map(|s| s.iter()) + .flat_map(RelationshipSources::iter) .collect(), } } diff --git a/crates/bevy_input_focus/src/tab_navigation.rs b/crates/bevy_input_focus/src/tab_navigation.rs index f484f8eec1afc..8039938649adf 100644 --- a/crates/bevy_input_focus/src/tab_navigation.rs +++ b/crates/bevy_input_focus/src/tab_navigation.rs @@ -370,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_ui/src/stack.rs b/crates/bevy_ui/src/stack.rs index 3f014c4235fc4..fdfc2fd9c6c7f 100644 --- a/crates/bevy_ui/src/stack.rs +++ b/crates/bevy_ui/src/stack.rs @@ -132,7 +132,7 @@ mod tests { system::Commands, world::{CommandQueue, World}, }; - + use crate::{GlobalZIndex, Node, UiStack, ZIndex}; use super::ui_stack_system; From 1492844a228e45adf96990d96a63c8e2f791a987 Mon Sep 17 00:00:00 2001 From: Carter Anderson Date: Mon, 13 Jan 2025 18:45:29 -0800 Subject: [PATCH 04/38] Support deriving on_despawn and fix archetype flags --- crates/bevy_ecs/macros/src/component.rs | 11 ++++++++++- crates/bevy_ecs/src/component.rs | 3 +++ 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/crates/bevy_ecs/macros/src/component.rs b/crates/bevy_ecs/macros/src/component.rs index cd7cd52bf6570..a63406b6cb3f5 100644 --- a/crates/bevy_ecs/macros/src/component.rs +++ b/crates/bevy_ecs/macros/src/component.rs @@ -69,7 +69,9 @@ pub fn derive_component(input: TokenStream) -> TokenStream { 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 on_remove: Option = + hook_register_function_call(quote! {on_remove}, attrs.on_remove); + let on_despawn = hook_register_function_call(quote! {on_despawn}, attrs.on_despawn); ast.generics .make_where_clause() @@ -165,6 +167,7 @@ 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 { @@ -212,6 +215,7 @@ 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"; @@ -222,6 +226,7 @@ struct Attrs { on_insert: Option, on_replace: Option, on_remove: Option, + on_despawn: Option, immutable: bool, } @@ -252,6 +257,7 @@ fn parse_component_attr(ast: &DeriveInput) -> Result { on_insert: None, on_replace: None, on_remove: None, + on_despawn: None, requires: None, immutable: false, }; @@ -283,6 +289,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(()) diff --git a/crates/bevy_ecs/src/component.rs b/crates/bevy_ecs/src/component.rs index 2ceec518d0d10..6cab46f8e25a4 100644 --- a/crates/bevy_ecs/src/component.rs +++ b/crates/bevy_ecs/src/component.rs @@ -799,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`] From b4798f27198f4fdf5af7eb07c40336ba5a6b829a Mon Sep 17 00:00:00 2001 From: Carter Anderson Date: Mon, 13 Jan 2025 20:07:19 -0800 Subject: [PATCH 05/38] Derive relationship --- crates/bevy_ecs/macros/src/lib.rs | 6 ++ crates/bevy_ecs/macros/src/relationship.rs | 64 ++++++++++++++++++++++ crates/bevy_ecs/src/hierarchy.rs | 25 +-------- crates/bevy_ecs/src/relationship.rs | 2 + 4 files changed, 74 insertions(+), 23 deletions(-) create mode 100644 crates/bevy_ecs/macros/src/relationship.rs diff --git a/crates/bevy_ecs/macros/src/lib.rs b/crates/bevy_ecs/macros/src/lib.rs index 0893e721aef8e..b90875ee1d3a4 100644 --- a/crates/bevy_ecs/macros/src/lib.rs +++ b/crates/bevy_ecs/macros/src/lib.rs @@ -6,6 +6,7 @@ extern crate proc_macro; mod component; mod query_data; mod query_filter; +mod relationship; mod states; mod world_query; @@ -611,3 +612,8 @@ pub fn derive_states(input: TokenStream) -> TokenStream { pub fn derive_substates(input: TokenStream) -> TokenStream { states::derive_substates(input) } + +#[proc_macro_derive(Relationship, attributes(relationship_sources))] +pub fn derive_relationship(input: TokenStream) -> TokenStream { + relationship::derive_relationship(input) +} diff --git a/crates/bevy_ecs/macros/src/relationship.rs b/crates/bevy_ecs/macros/src/relationship.rs new file mode 100644 index 0000000000000..fd7cab543c3fa --- /dev/null +++ b/crates/bevy_ecs/macros/src/relationship.rs @@ -0,0 +1,64 @@ +use proc_macro::TokenStream; +use quote::quote; +use syn::{parse_macro_input, parse_quote, spanned::Spanned, DeriveInput, Ident, Path}; + +pub fn derive_relationship(input: TokenStream) -> TokenStream { + let mut ast = parse_macro_input!(input as DeriveInput); + let bevy_ecs_path: Path = crate::bevy_ecs_path(); + + let Some(relationship_sources) = ast + .attrs + .iter() + .find(|a| a.path().is_ident("relationship_sources")) + .and_then(|a| a.parse_args::().ok()) + else { + return syn::Error::new( + ast.span(), + "Relationships must define a relationship_sources(SOURCES_COMPONENT) attribute.", + ) + .into_compile_error() + .into(); + }; + + ast.generics + .make_where_clause() + .predicates + .push(parse_quote! { Self: Send + Sync + 'static }); + + let struct_name = &ast.ident; + let (impl_generics, type_generics, where_clause) = &ast.generics.split_for_impl(); + + TokenStream::from(quote! { + impl #impl_generics #bevy_ecs_path::relationship::Relationship for #struct_name #type_generics #where_clause { + type RelationshipSources = #relationship_sources; + + #[inline(always)] + fn get(&self) -> #bevy_ecs_path::entity::Entity { + self.0 + } + + fn set(&mut self, entity: #bevy_ecs_path::entity::Entity) { + self.0 = entity; + } + + fn from(entity: #bevy_ecs_path::entity::Entity) -> Self { + Self(entity) + } + } + + impl #impl_generics #bevy_ecs_path::component::Component for #struct_name #type_generics #where_clause { + const STORAGE_TYPE: #bevy_ecs_path::component::StorageType = #bevy_ecs_path::component::StorageType::Table; + type Mutability = #bevy_ecs_path::component::Immutable; + + fn register_component_hooks(hooks: &mut #bevy_ecs_path::component::ComponentHooks) { + hooks.on_insert(::on_insert); + hooks.on_replace(::on_replace); + } + fn get_component_clone_handler() -> #bevy_ecs_path::component::ComponentCloneHandler { + use #bevy_ecs_path::component::{ComponentCloneBase, ComponentCloneViaClone}; + (&&&#bevy_ecs_path::component::ComponentCloneSpecializationWrapper::::default()) + .get_component_clone_handler() + } + } + }) +} diff --git a/crates/bevy_ecs/src/hierarchy.rs b/crates/bevy_ecs/src/hierarchy.rs index 39d978b507f28..fd04ef6ba179f 100644 --- a/crates/bevy_ecs/src/hierarchy.rs +++ b/crates/bevy_ecs/src/hierarchy.rs @@ -26,7 +26,7 @@ use core::slice; use disqualified::ShortName; use log::warn; -#[derive(Component, Clone, Reflect, VisitEntities, VisitEntitiesMut, PartialEq, Eq, Debug)] +#[derive(Relationship, Clone, Reflect, VisitEntities, VisitEntitiesMut, PartialEq, Eq, Debug)] #[reflect( Component, MapEntities, @@ -36,11 +36,7 @@ use log::warn; Debug, FromWorld )] -#[component( - immutable, - on_insert = Self::on_insert, - on_replace = Self::on_replace, -)] +#[relationship_sources(Children)] pub struct Parent(pub Entity); impl Parent { @@ -49,23 +45,6 @@ impl Parent { } } -impl Relationship for Parent { - type RelationshipSources = Children; - - #[inline(always)] - fn get(&self) -> Entity { - self.0 - } - - fn set(&mut self, entity: Entity) { - self.0 = entity; - } - - fn from(entity: Entity) -> Self { - Self(entity) - } -} - // 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 diff --git a/crates/bevy_ecs/src/relationship.rs b/crates/bevy_ecs/src/relationship.rs index 6e2655eb5e9ac..a358d5df2fa50 100644 --- a/crates/bevy_ecs/src/relationship.rs +++ b/crates/bevy_ecs/src/relationship.rs @@ -1,6 +1,8 @@ // TODO: remove this #![allow(missing_docs)] +pub use bevy_ecs_macros::Relationship; + use crate::{ bundle::Bundle, component::{Component, ComponentId, Mutable}, From 44eb8b954a980df4bac7eeed74dcc66a7400f9cf Mon Sep 17 00:00:00 2001 From: Carter Anderson Date: Tue, 14 Jan 2025 13:15:13 -0800 Subject: [PATCH 06/38] RelationshipSources derive --- crates/bevy_ecs/macros/src/lib.rs | 5 ++ crates/bevy_ecs/macros/src/relationship.rs | 90 +++++++++++++++++++++- crates/bevy_ecs/src/hierarchy.rs | 36 +-------- crates/bevy_ecs/src/relationship.rs | 30 +++++++- 4 files changed, 125 insertions(+), 36 deletions(-) diff --git a/crates/bevy_ecs/macros/src/lib.rs b/crates/bevy_ecs/macros/src/lib.rs index b90875ee1d3a4..907aa7b5d4c8c 100644 --- a/crates/bevy_ecs/macros/src/lib.rs +++ b/crates/bevy_ecs/macros/src/lib.rs @@ -617,3 +617,8 @@ pub fn derive_substates(input: TokenStream) -> TokenStream { pub fn derive_relationship(input: TokenStream) -> TokenStream { relationship::derive_relationship(input) } + +#[proc_macro_derive(RelationshipSources, attributes(relationship))] +pub fn derive_relationship_sources(input: TokenStream) -> TokenStream { + relationship::derive_relationship_sources(input) +} diff --git a/crates/bevy_ecs/macros/src/relationship.rs b/crates/bevy_ecs/macros/src/relationship.rs index fd7cab543c3fa..7d0d022bd5311 100644 --- a/crates/bevy_ecs/macros/src/relationship.rs +++ b/crates/bevy_ecs/macros/src/relationship.rs @@ -1,6 +1,9 @@ use proc_macro::TokenStream; use quote::quote; -use syn::{parse_macro_input, parse_quote, spanned::Spanned, DeriveInput, Ident, Path}; +use syn::{ + parse_macro_input, parse_quote, spanned::Spanned, Data, DataStruct, DeriveInput, Fields, Ident, + Path, Visibility, +}; pub fn derive_relationship(input: TokenStream) -> TokenStream { let mut ast = parse_macro_input!(input as DeriveInput); @@ -14,7 +17,7 @@ pub fn derive_relationship(input: TokenStream) -> TokenStream { else { return syn::Error::new( ast.span(), - "Relationships must define a relationship_sources(SOURCES_COMPONENT) attribute.", + "Relationship derives must define a relationship_sources(SOURCES_COMPONENT) attribute.", ) .into_compile_error() .into(); @@ -62,3 +65,86 @@ pub fn derive_relationship(input: TokenStream) -> TokenStream { } }) } + +pub fn derive_relationship_sources(input: TokenStream) -> TokenStream { + let mut ast = parse_macro_input!(input as DeriveInput); + let bevy_ecs_path: Path = crate::bevy_ecs_path(); + + let Some(relationship) = ast + .attrs + .iter() + .find(|a| a.path().is_ident("relationship")) + .and_then(|a| a.parse_args::().ok()) + else { + return syn::Error::new( + ast.span(), + "RelationshipSources derives must define a relationship(RELATIONSHIP) attribute.", + ) + .into_compile_error() + .into(); + }; + + const RELATIONSHIP_SOURCES_FORMAT_MESSAGE: &str = "RelationshipSources derives must be a tuple struct with the first element being a private RelationshipSourceCollection (ex: Children(Vec))"; + 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 syn::Error::new(first.span(), RELATIONSHIP_SOURCES_FORMAT_MESSAGE) + .into_compile_error() + .into(); + } + } else { + return syn::Error::new(struct_token.span(), RELATIONSHIP_SOURCES_FORMAT_MESSAGE) + .into_compile_error() + .into(); + } + } else { + return syn::Error::new(ast.span(), RELATIONSHIP_SOURCES_FORMAT_MESSAGE) + .into_compile_error() + .into(); + } + + ast.generics + .make_where_clause() + .predicates + .push(parse_quote! { Self: Send + Sync + 'static }); + + let struct_name = &ast.ident; + let (impl_generics, type_generics, where_clause) = &ast.generics.split_for_impl(); + + TokenStream::from(quote! { + impl #impl_generics #bevy_ecs_path::relationship::RelationshipSources for #struct_name #type_generics #where_clause { + type Relationship = #relationship; + type Collection = Vec; + + fn collection(&self) -> &Self::Collection { + &self.0 + } + + fn collection_mut(&mut self) -> &mut Self::Collection { + &mut self.0 + } + + fn from_collection(collection: Self::Collection) -> Self { + Self(collection) + } + } + + impl #impl_generics #bevy_ecs_path::component::Component for #struct_name #type_generics #where_clause { + const STORAGE_TYPE: #bevy_ecs_path::component::StorageType = #bevy_ecs_path::component::StorageType::Table; + type Mutability = #bevy_ecs_path::component::Mutable; + + fn register_component_hooks(hooks: &mut #bevy_ecs_path::component::ComponentHooks) { + hooks.on_replace(::on_replace); + hooks.on_despawn(::on_despawn); + } + fn get_component_clone_handler() -> #bevy_ecs_path::component::ComponentCloneHandler { + #bevy_ecs_path::component::ComponentCloneHandler::ignore() + } + } + }) +} diff --git a/crates/bevy_ecs/src/hierarchy.rs b/crates/bevy_ecs/src/hierarchy.rs index fd04ef6ba179f..b8f19ef8c0ba7 100644 --- a/crates/bevy_ecs/src/hierarchy.rs +++ b/crates/bevy_ecs/src/hierarchy.rs @@ -10,7 +10,7 @@ use crate::relationship::{RelatedSpawner, RelatedSpawnerCommands}; use crate::system::EntityCommands; use crate::world::{DeferredWorld, EntityWorldMut}; use crate::{ - component::{Component, ComponentCloneHandler, Mutable, StorageType}, + component::Component, entity::{Entity, VisitEntities}, reflect::{ ReflectComponent, ReflectFromWorld, ReflectMapEntities, ReflectVisitEntities, @@ -56,24 +56,11 @@ impl FromWorld for Parent { } } -#[derive(Default, Reflect, VisitEntitiesMut)] +#[derive(RelationshipSources, Default, Reflect, VisitEntitiesMut)] +#[relationship(Parent)] #[reflect(Component, MapEntities, VisitEntities, VisitEntitiesMut)] pub struct Children(Vec); -impl Component for Children { - const STORAGE_TYPE: StorageType = StorageType::Table; - type Mutability = Mutable; - - fn register_component_hooks(hooks: &mut crate::component::ComponentHooks) { - hooks.on_replace(Self::on_replace); - hooks.on_despawn(Self::on_despawn); - } - - fn get_component_clone_handler() -> ComponentCloneHandler { - ComponentCloneHandler::ignore() - } -} - impl<'a> IntoIterator for &'a Children { type Item = ::Item; @@ -93,23 +80,6 @@ impl core::ops::Deref for Children { } } -impl RelationshipSources for Children { - type Relationship = Parent; - type Collection = Vec; - - fn collection(&self) -> &Self::Collection { - &self.0 - } - - fn collection_mut(&mut self) -> &mut Self::Collection { - &mut self.0 - } - - fn from_collection(collection: Self::Collection) -> Self { - Self(collection) - } -} - pub type ChildSpawner<'w> = RelatedSpawner<'w, Parent>; pub type ChildSpawnerCommands<'w> = RelatedSpawnerCommands<'w, Parent>; diff --git a/crates/bevy_ecs/src/relationship.rs b/crates/bevy_ecs/src/relationship.rs index a358d5df2fa50..4dcb5f2ecc12f 100644 --- a/crates/bevy_ecs/src/relationship.rs +++ b/crates/bevy_ecs/src/relationship.rs @@ -1,7 +1,7 @@ // TODO: remove this #![allow(missing_docs)] -pub use bevy_ecs_macros::Relationship; +pub use bevy_ecs_macros::{Relationship, RelationshipSources}; use crate::{ bundle::Bundle, @@ -521,3 +521,31 @@ impl<'a> EntityCommands<'a> { self } } + +#[cfg(test)] +mod tests { + use crate as bevy_ecs; + use crate::world::World; + use crate::{ + entity::Entity, + relationship::{Relationship, RelationshipSources}, + }; + use std::vec::Vec; + + #[test] + fn custom_relationship() { + #[derive(Relationship)] + #[relationship_sources(LikedBy)] + struct Likes(pub Entity); + + #[derive(RelationshipSources)] + #[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]); + } +} From 0bd10cd575223c12656ac00ad253973db73d95fe Mon Sep 17 00:00:00 2001 From: Carter Anderson Date: Tue, 14 Jan 2025 13:23:32 -0800 Subject: [PATCH 07/38] Support arbitrary collections --- crates/bevy_ecs/macros/src/relationship.rs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/crates/bevy_ecs/macros/src/relationship.rs b/crates/bevy_ecs/macros/src/relationship.rs index 7d0d022bd5311..27e2757bcf267 100644 --- a/crates/bevy_ecs/macros/src/relationship.rs +++ b/crates/bevy_ecs/macros/src/relationship.rs @@ -85,7 +85,7 @@ pub fn derive_relationship_sources(input: TokenStream) -> TokenStream { }; const RELATIONSHIP_SOURCES_FORMAT_MESSAGE: &str = "RelationshipSources derives must be a tuple struct with the first element being a private RelationshipSourceCollection (ex: Children(Vec))"; - if let Data::Struct(DataStruct { + let collection = if let Data::Struct(DataStruct { fields: Fields::Unnamed(unnamed_fields), struct_token, .. @@ -97,6 +97,7 @@ pub fn derive_relationship_sources(input: TokenStream) -> TokenStream { .into_compile_error() .into(); } + first.ty.clone() } else { return syn::Error::new(struct_token.span(), RELATIONSHIP_SOURCES_FORMAT_MESSAGE) .into_compile_error() @@ -106,7 +107,7 @@ pub fn derive_relationship_sources(input: TokenStream) -> TokenStream { return syn::Error::new(ast.span(), RELATIONSHIP_SOURCES_FORMAT_MESSAGE) .into_compile_error() .into(); - } + }; ast.generics .make_where_clause() @@ -119,7 +120,7 @@ pub fn derive_relationship_sources(input: TokenStream) -> TokenStream { TokenStream::from(quote! { impl #impl_generics #bevy_ecs_path::relationship::RelationshipSources for #struct_name #type_generics #where_clause { type Relationship = #relationship; - type Collection = Vec; + type Collection = #collection; fn collection(&self) -> &Self::Collection { &self.0 From c38bc9c10e7317a921f8ac9a19f2e58d0a4d5fa8 Mon Sep 17 00:00:00 2001 From: Carter Anderson Date: Tue, 14 Jan 2025 13:39:03 -0800 Subject: [PATCH 08/38] Add type constraint --- crates/bevy_ecs/src/relationship.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/bevy_ecs/src/relationship.rs b/crates/bevy_ecs/src/relationship.rs index 4dcb5f2ecc12f..c399a73183b5f 100644 --- a/crates/bevy_ecs/src/relationship.rs +++ b/crates/bevy_ecs/src/relationship.rs @@ -82,7 +82,7 @@ pub trait Relationship: Component + Sized { // These internals are allowed to modify the internal RelationshipSource collection. #[allow(deprecated)] pub trait RelationshipSources: Component + Sized { - type Relationship: Relationship; + type Relationship: Relationship; type Collection: RelationshipSourceCollection; fn collection(&self) -> &Self::Collection; From 005208078172b55b38b8b5148093c697e1ffaf7b Mon Sep 17 00:00:00 2001 From: Carter Anderson Date: Wed, 15 Jan 2025 12:11:00 -0800 Subject: [PATCH 09/38] Opt-in despawn_descendants. Apply commands on spawn --- crates/bevy_ecs/macros/src/lib.rs | 2 +- crates/bevy_ecs/macros/src/relationship.rs | 44 ++++++++++++++++++---- crates/bevy_ecs/src/hierarchy.rs | 1 + crates/bevy_ecs/src/observer/mod.rs | 3 +- crates/bevy_ecs/src/relationship.rs | 2 +- crates/bevy_ecs/src/world/entity_ref.rs | 9 +++-- crates/bevy_ecs/src/world/mod.rs | 28 ++++++++------ 7 files changed, 63 insertions(+), 26 deletions(-) diff --git a/crates/bevy_ecs/macros/src/lib.rs b/crates/bevy_ecs/macros/src/lib.rs index 907aa7b5d4c8c..9b1c1383e9797 100644 --- a/crates/bevy_ecs/macros/src/lib.rs +++ b/crates/bevy_ecs/macros/src/lib.rs @@ -618,7 +618,7 @@ pub fn derive_relationship(input: TokenStream) -> TokenStream { relationship::derive_relationship(input) } -#[proc_macro_derive(RelationshipSources, attributes(relationship))] +#[proc_macro_derive(RelationshipSources, attributes(relationship, despawn_descendants))] pub fn derive_relationship_sources(input: TokenStream) -> TokenStream { relationship::derive_relationship_sources(input) } diff --git a/crates/bevy_ecs/macros/src/relationship.rs b/crates/bevy_ecs/macros/src/relationship.rs index 27e2757bcf267..340912c22ed8c 100644 --- a/crates/bevy_ecs/macros/src/relationship.rs +++ b/crates/bevy_ecs/macros/src/relationship.rs @@ -23,6 +23,29 @@ pub fn derive_relationship(input: TokenStream) -> TokenStream { .into(); }; + 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 syn::Error::new(ast.span(), RELATIONSHIP_FORMAT_MESSAGE) + .into_compile_error() + .into(); + } + if unnamed_fields.unnamed.first().is_none() { + return syn::Error::new(struct_token.span(), RELATIONSHIP_FORMAT_MESSAGE) + .into_compile_error() + .into(); + } + } else { + return syn::Error::new(ast.span(), RELATIONSHIP_FORMAT_MESSAGE) + .into_compile_error() + .into(); + }; + ast.generics .make_where_clause() .predicates @@ -70,12 +93,19 @@ pub fn derive_relationship_sources(input: TokenStream) -> TokenStream { let mut ast = parse_macro_input!(input as DeriveInput); let bevy_ecs_path: Path = crate::bevy_ecs_path(); - let Some(relationship) = ast - .attrs - .iter() - .find(|a| a.path().is_ident("relationship")) - .and_then(|a| a.parse_args::().ok()) - else { + let mut relationship = None; + let mut despawn_descendants = None; + for attr in ast.attrs.iter() { + if attr.path().is_ident("relationship") { + relationship = attr.parse_args::().ok(); + } + if attr.path().is_ident("despawn_descendants") { + despawn_descendants = + Some(quote! {hooks.on_despawn(::on_despawn);}); + } + } + + let Some(relationship) = relationship else { return syn::Error::new( ast.span(), "RelationshipSources derives must define a relationship(RELATIONSHIP) attribute.", @@ -141,7 +171,7 @@ pub fn derive_relationship_sources(input: TokenStream) -> TokenStream { fn register_component_hooks(hooks: &mut #bevy_ecs_path::component::ComponentHooks) { hooks.on_replace(::on_replace); - hooks.on_despawn(::on_despawn); + #despawn_descendants } fn get_component_clone_handler() -> #bevy_ecs_path::component::ComponentCloneHandler { #bevy_ecs_path::component::ComponentCloneHandler::ignore() diff --git a/crates/bevy_ecs/src/hierarchy.rs b/crates/bevy_ecs/src/hierarchy.rs index b8f19ef8c0ba7..ef2b48098fabe 100644 --- a/crates/bevy_ecs/src/hierarchy.rs +++ b/crates/bevy_ecs/src/hierarchy.rs @@ -58,6 +58,7 @@ impl FromWorld for Parent { #[derive(RelationshipSources, Default, Reflect, VisitEntitiesMut)] #[relationship(Parent)] +#[despawn_descendants] #[reflect(Component, MapEntities, VisitEntities, VisitEntitiesMut)] pub struct Children(Vec); diff --git a/crates/bevy_ecs/src/observer/mod.rs b/crates/bevy_ecs/src/observer/mod.rs index 8c04843e98e0a..986911c3a57ae 100644 --- a/crates/bevy_ecs/src/observer/mod.rs +++ b/crates/bevy_ecs/src/observer/mod.rs @@ -1476,6 +1476,7 @@ mod tests { } #[test] + #[should_panic] fn observer_invalid_params() { #[derive(Resource)] struct ResA; @@ -1489,8 +1490,6 @@ mod tests { commands.insert_resource(ResB); }); world.trigger(EventA); - - assert!(world.get_resource::().is_none()); } #[test] diff --git a/crates/bevy_ecs/src/relationship.rs b/crates/bevy_ecs/src/relationship.rs index c399a73183b5f..f3205bc56be65 100644 --- a/crates/bevy_ecs/src/relationship.rs +++ b/crates/bevy_ecs/src/relationship.rs @@ -530,7 +530,7 @@ mod tests { entity::Entity, relationship::{Relationship, RelationshipSources}, }; - use std::vec::Vec; + use alloc::vec::Vec; #[test] fn custom_relationship() { diff --git a/crates/bevy_ecs/src/world/entity_ref.rs b/crates/bevy_ecs/src/world/entity_ref.rs index 43d7dbf25b503..53b0ef6b95ac6 100644 --- a/crates/bevy_ecs/src/world/entity_ref.rs +++ b/crates/bevy_ecs/src/world/entity_ref.rs @@ -5280,7 +5280,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) { @@ -5388,12 +5389,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 1037969dedca9..9cda5f0c4768d 100644 --- a/crates/bevy_ecs/src/world/mod.rs +++ b/crates/bevy_ecs/src/world/mod.rs @@ -1074,19 +1074,25 @@ 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(), + ) }; + 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()); From cf86a92d73cd21cb0c1b12421de4843bb9d861e6 Mon Sep 17 00:00:00 2001 From: Carter Anderson Date: Wed, 15 Jan 2025 12:13:49 -0800 Subject: [PATCH 10/38] Better "private" error message --- crates/bevy_ecs/macros/src/relationship.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/bevy_ecs/macros/src/relationship.rs b/crates/bevy_ecs/macros/src/relationship.rs index 340912c22ed8c..1571075e34aed 100644 --- a/crates/bevy_ecs/macros/src/relationship.rs +++ b/crates/bevy_ecs/macros/src/relationship.rs @@ -123,7 +123,7 @@ pub fn derive_relationship_sources(input: TokenStream) -> TokenStream { { if let Some(first) = unnamed_fields.unnamed.first() { if first.vis != Visibility::Inherited { - return syn::Error::new(first.span(), RELATIONSHIP_SOURCES_FORMAT_MESSAGE) + return syn::Error::new(first.span(), "The collection in RelationshipSources must be private to prevent users from directly mutating it, which could invalidate the correctness of relationships.") .into_compile_error() .into(); } From bdacea772a5fbdf1023812f3805820683adeb162 Mon Sep 17 00:00:00 2001 From: Carter Anderson Date: Wed, 15 Jan 2025 12:39:12 -0800 Subject: [PATCH 11/38] despawn_related --- crates/bevy_ecs/src/relationship.rs | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/crates/bevy_ecs/src/relationship.rs b/crates/bevy_ecs/src/relationship.rs index f3205bc56be65..228fdc366851a 100644 --- a/crates/bevy_ecs/src/relationship.rs +++ b/crates/bevy_ecs/src/relationship.rs @@ -496,6 +496,21 @@ impl<'w> EntityWorldMut<'w> { }); self } + + /// Despawns entities that relate to this one via the given [`RelationshipSources`]. + /// 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> { @@ -520,6 +535,16 @@ impl<'a> EntityCommands<'a> { }); self } + + /// Despawns entities that relate to this one via the given [`RelationshipSources`]. + /// 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 + } } #[cfg(test)] From 060a3fbde7f75ae352d201ab759e0c46eea607d8 Mon Sep 17 00:00:00 2001 From: Carter Anderson Date: Wed, 15 Jan 2025 12:47:42 -0800 Subject: [PATCH 12/38] Reorganize relationship module --- crates/bevy_ecs/src/relationship.rs | 576 ------------------ crates/bevy_ecs/src/relationship/mod.rs | 186 ++++++ .../src/relationship/related_methods.rs | 137 +++++ .../src/relationship/relationship_query.rs | 228 +++++++ .../relationship_source_collection.rs | 43 ++ 5 files changed, 594 insertions(+), 576 deletions(-) delete mode 100644 crates/bevy_ecs/src/relationship.rs create mode 100644 crates/bevy_ecs/src/relationship/mod.rs create mode 100644 crates/bevy_ecs/src/relationship/related_methods.rs create mode 100644 crates/bevy_ecs/src/relationship/relationship_query.rs create mode 100644 crates/bevy_ecs/src/relationship/relationship_source_collection.rs diff --git a/crates/bevy_ecs/src/relationship.rs b/crates/bevy_ecs/src/relationship.rs deleted file mode 100644 index 228fdc366851a..0000000000000 --- a/crates/bevy_ecs/src/relationship.rs +++ /dev/null @@ -1,576 +0,0 @@ -// TODO: remove this -#![allow(missing_docs)] - -pub use bevy_ecs_macros::{Relationship, RelationshipSources}; - -use crate::{ - bundle::Bundle, - component::{Component, ComponentId, Mutable}, - entity::Entity, - query::{QueryData, QueryFilter, WorldQuery}, - system::{ - command::HandleError, - entity_command::{self, CommandWithEntity}, - error_handler, Commands, EntityCommands, Query, - }, - world::{DeferredWorld, EntityWorldMut, World}, -}; -use alloc::{collections::VecDeque, vec::Vec}; -use core::marker::PhantomData; -use log::warn; -use smallvec::SmallVec; - -// The "deprecated" state is used to prevent users from mutating the internal RelationshipSource collection. -// These internals are allowed to modify the internal RelationshipSource collection. -#[allow(deprecated)] -pub trait Relationship: Component + Sized { - type RelationshipSources: RelationshipSources; - /// Gets the [`Entity`] ID of the related entity. - fn get(&self) -> Entity; - fn set(&mut self, entity: Entity); - fn from(entity: Entity) -> Self; - fn on_insert(mut world: DeferredWorld, entity: Entity, _: ComponentId) { - let parent = world.entity(entity).get::().unwrap().get(); - if parent == entity { - warn!( - "The {}({parent:?}) 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 parent_entity) = world.get_entity_mut(parent) { - if let Some(mut relationship_sources) = - parent_entity.get_mut::() - { - relationship_sources.collection_mut().add(entity); - } else { - let mut sources = - ::with_capacity(1); - sources.collection_mut().add(entity); - world.commands().entity(parent).insert(sources); - } - } else { - warn!( - "The {}({parent:?}) 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::(); - } - } - - // note: think of this as "on_drop" - fn on_replace(mut world: DeferredWorld, entity: Entity, _: ComponentId) { - let parent = world.entity(entity).get::().unwrap().get(); - if let Ok(mut parent_entity) = world.get_entity_mut(parent) { - if let Some(mut relationship_sources) = - parent_entity.get_mut::() - { - relationship_sources.collection_mut().remove(entity); - if relationship_sources.len() == 0 { - if let Some(mut entity) = world.commands().get_entity(parent) { - entity.remove::(); - } - } - } - } - } -} - -// The "deprecated" state is used to prevent users from mutating the internal RelationshipSource collection. -// These internals are allowed to modify the internal RelationshipSource collection. -#[allow(deprecated)] -pub trait RelationshipSources: Component + Sized { - type Relationship: Relationship; - type Collection: RelationshipSourceCollection; - - fn collection(&self) -> &Self::Collection; - #[deprecated = "Modifying the internal RelationshipSource collection should only be done by internals as it can invalidate relationships."] - fn collection_mut(&mut self) -> &mut Self::Collection; - #[deprecated = "Creating a relationship source manually should only be done by internals as it can invalidate relationships."] - fn from_collection(collection: Self::Collection) -> Self; - - 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 RelationshipSources collection - // SAFETY: This only reads the Self component and queues Remove commands - unsafe { - let world = world.as_unsafe_world_cell(); - let sources = world.get_entity(entity).unwrap().get::().unwrap(); - let mut commands = world.get_raw_command_queue(); - for source_entity in sources.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); - } - } - } - } - - 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 RelationshipSources collection - // SAFETY: This only reads the Self component and queues despawn commands - unsafe { - let world = world.as_unsafe_world_cell(); - let sources = world.get_entity(entity).unwrap().get::().unwrap(); - let mut commands = world.get_raw_command_queue(); - for source_entity in sources.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); - } - } - } - } - - fn with_capacity(capacity: usize) -> Self { - let collection = - ::with_capacity(capacity); - Self::from_collection(collection) - } - - #[inline] - fn iter(&self) -> impl DoubleEndedIterator { - self.collection().iter() - } - - #[inline] - fn len(&self) -> usize { - self.collection().len() - } - - #[inline] - fn is_empty(&self) -> bool { - self.collection().is_empty() - } -} - -pub trait RelationshipSourceCollection { - fn with_capacity(capacity: usize) -> Self; - fn add(&mut self, entity: Entity); - fn remove(&mut self, entity: Entity); - fn iter(&self) -> impl DoubleEndedIterator; - fn take(&mut self) -> Vec; - fn len(&self) -> usize; - #[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 take(&mut self) -> Vec { - core::mem::take(self) - } - - fn len(&self) -> usize { - Vec::len(self) - } -} - -impl<'w, 's, D: QueryData, F: QueryFilter> Query<'w, 's, D, F> { - pub fn related(&'w self, entity: Entity) -> Option - where - ::ReadOnly: WorldQuery = &'w R>, - { - self.get(entity).map(R::get).ok() - } - - pub fn relationship_sources( - &'w self, - entity: Entity, - ) -> impl Iterator + 'w - where - ::ReadOnly: WorldQuery = &'w S>, - { - self.get(entity) - .into_iter() - .flat_map(RelationshipSources::iter) - } - - 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, - } - } - - 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) - }) - } - - pub fn iter_siblings( - &'w self, - entity: Entity, - ) -> impl Iterator + 'w - where - D::ReadOnly: WorldQuery = (Option<&'w R>, Option<&'w R::RelationshipSources>)>, - { - 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)) - } - - pub fn iter_descendants( - &'w self, - entity: Entity, - ) -> DescendantIter<'w, 's, D, F, S> - where - D::ReadOnly: WorldQuery = &'w S>, - { - DescendantIter::new(self, entity) - } - - 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) - } - - 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: RelationshipSources> -where - D::ReadOnly: WorldQuery = &'w S>, -{ - children_query: &'w Query<'w, 's, D, F>, - vecdeque: VecDeque, -} - -impl<'w, 's, D: QueryData, F: QueryFilter, S: RelationshipSources> 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(RelationshipSources::iter) - .collect(), - } - } -} - -impl<'w, 's, D: QueryData, F: QueryFilter, S: RelationshipSources> 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: RelationshipSources> -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: RelationshipSources> - 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: RelationshipSources> 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 - } -} - -pub struct RelatedSpawner<'w, R: Relationship> { - target: Entity, - world: &'w mut World, - _marker: PhantomData, -} - -impl<'w, R: Relationship> RelatedSpawner<'w, R> { - pub fn new(world: &'w mut World, target: Entity) -> Self { - Self { - world, - target, - _marker: PhantomData, - } - } - - pub fn spawn(&mut self, bundle: impl Bundle) -> EntityWorldMut<'_> { - self.world.spawn((R::from(self.target), bundle)) - } - - pub fn spawn_empty(&mut self) -> EntityWorldMut<'_> { - self.world.spawn(R::from(self.target)) - } - - pub fn target_entity(&self) -> Entity { - self.target - } -} - -pub struct RelatedSpawnerCommands<'w, R: Relationship> { - target: Entity, - commands: Commands<'w, 'w>, - _marker: PhantomData, -} - -impl<'w, R: Relationship> RelatedSpawnerCommands<'w, R> { - pub fn new(commands: Commands<'w, 'w>, target: Entity) -> Self { - Self { - commands, - target, - _marker: PhantomData, - } - } - pub fn spawn(&mut self, bundle: impl Bundle) -> EntityCommands<'_> { - self.commands.spawn((R::from(self.target), bundle)) - } - - pub fn spawn_empty(&mut self) -> EntityCommands<'_> { - self.commands.spawn(R::from(self.target)) - } - - pub fn target_entity(&self) -> Entity { - self.target - } -} - -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 [`RelationshipSources`]. - /// 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 [`RelationshipSources`]. - /// 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 - } -} - -#[cfg(test)] -mod tests { - use crate as bevy_ecs; - use crate::world::World; - use crate::{ - entity::Entity, - relationship::{Relationship, RelationshipSources}, - }; - use alloc::vec::Vec; - - #[test] - fn custom_relationship() { - #[derive(Relationship)] - #[relationship_sources(LikedBy)] - struct Likes(pub Entity); - - #[derive(RelationshipSources)] - #[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/mod.rs b/crates/bevy_ecs/src/relationship/mod.rs new file mode 100644 index 0000000000000..80fcdfed8a133 --- /dev/null +++ b/crates/bevy_ecs/src/relationship/mod.rs @@ -0,0 +1,186 @@ +mod related_methods; +mod relationship_query; +mod relationship_source_collection; + +pub use related_methods::*; +pub use relationship_query::*; +pub use relationship_source_collection::*; + +pub use bevy_ecs_macros::{Relationship, RelationshipSources}; + +use crate::{ + component::{Component, ComponentId, Mutable}, + entity::Entity, + system::{ + command::HandleError, + entity_command::{self, CommandWithEntity}, + error_handler, + }, + world::DeferredWorld, +}; +use log::warn; + +// The "deprecated" state is used to prevent users from mutating the internal RelationshipSource collection. +// These internals are allowed to modify the internal RelationshipSource collection. +#[allow(deprecated)] +pub trait Relationship: Component + Sized { + type RelationshipSources: RelationshipSources; + /// Gets the [`Entity`] ID of the related entity. + fn get(&self) -> Entity; + fn set(&mut self, entity: Entity); + fn from(entity: Entity) -> Self; + fn on_insert(mut world: DeferredWorld, entity: Entity, _: ComponentId) { + let parent = world.entity(entity).get::().unwrap().get(); + if parent == entity { + warn!( + "The {}({parent:?}) 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 parent_entity) = world.get_entity_mut(parent) { + if let Some(mut relationship_sources) = + parent_entity.get_mut::() + { + relationship_sources.collection_mut().add(entity); + } else { + let mut sources = + ::with_capacity(1); + sources.collection_mut().add(entity); + world.commands().entity(parent).insert(sources); + } + } else { + warn!( + "The {}({parent:?}) 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::(); + } + } + + // note: think of this as "on_drop" + fn on_replace(mut world: DeferredWorld, entity: Entity, _: ComponentId) { + let parent = world.entity(entity).get::().unwrap().get(); + if let Ok(mut parent_entity) = world.get_entity_mut(parent) { + if let Some(mut relationship_sources) = + parent_entity.get_mut::() + { + relationship_sources.collection_mut().remove(entity); + if relationship_sources.len() == 0 { + if let Some(mut entity) = world.commands().get_entity(parent) { + entity.remove::(); + } + } + } + } + } +} + +// The "deprecated" state is used to prevent users from mutating the internal RelationshipSource collection. +// These internals are allowed to modify the internal RelationshipSource collection. +#[allow(deprecated)] +pub trait RelationshipSources: Component + Sized { + type Relationship: Relationship; + type Collection: RelationshipSourceCollection; + + fn collection(&self) -> &Self::Collection; + #[deprecated = "Modifying the internal RelationshipSource collection should only be done by internals as it can invalidate relationships."] + fn collection_mut(&mut self) -> &mut Self::Collection; + #[deprecated = "Creating a relationship source manually should only be done by internals as it can invalidate relationships."] + fn from_collection(collection: Self::Collection) -> Self; + + 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 RelationshipSources collection + // SAFETY: This only reads the Self component and queues Remove commands + unsafe { + let world = world.as_unsafe_world_cell(); + let sources = world.get_entity(entity).unwrap().get::().unwrap(); + let mut commands = world.get_raw_command_queue(); + for source_entity in sources.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); + } + } + } + } + + 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 RelationshipSources collection + // SAFETY: This only reads the Self component and queues despawn commands + unsafe { + let world = world.as_unsafe_world_cell(); + let sources = world.get_entity(entity).unwrap().get::().unwrap(); + let mut commands = world.get_raw_command_queue(); + for source_entity in sources.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); + } + } + } + } + + fn with_capacity(capacity: usize) -> Self { + let collection = + ::with_capacity(capacity); + Self::from_collection(collection) + } + + #[inline] + fn iter(&self) -> impl DoubleEndedIterator { + self.collection().iter() + } + + #[inline] + fn len(&self) -> usize { + self.collection().len() + } + + #[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::{ + entity::Entity, + relationship::{Relationship, RelationshipSources}, + }; + use alloc::vec::Vec; + + #[test] + fn custom_relationship() { + #[derive(Relationship)] + #[relationship_sources(LikedBy)] + struct Likes(pub Entity); + + #[derive(RelationshipSources)] + #[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..8ed94aa6bab91 --- /dev/null +++ b/crates/bevy_ecs/src/relationship/related_methods.rs @@ -0,0 +1,137 @@ +use crate::{ + bundle::Bundle, + entity::Entity, + relationship::{Relationship, RelationshipSources}, + 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 [`RelationshipSources`]. + /// 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 [`RelationshipSources`]. + /// 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 + } +} + +pub struct RelatedSpawner<'w, R: Relationship> { + target: Entity, + world: &'w mut World, + _marker: PhantomData, +} + +impl<'w, R: Relationship> RelatedSpawner<'w, R> { + pub fn new(world: &'w mut World, target: Entity) -> Self { + Self { + world, + target, + _marker: PhantomData, + } + } + + pub fn spawn(&mut self, bundle: impl Bundle) -> EntityWorldMut<'_> { + self.world.spawn((R::from(self.target), bundle)) + } + + pub fn spawn_empty(&mut self) -> EntityWorldMut<'_> { + self.world.spawn(R::from(self.target)) + } + + pub fn target_entity(&self) -> Entity { + self.target + } +} + +pub struct RelatedSpawnerCommands<'w, R: Relationship> { + target: Entity, + commands: Commands<'w, 'w>, + _marker: PhantomData, +} + +impl<'w, R: Relationship> RelatedSpawnerCommands<'w, R> { + pub fn new(commands: Commands<'w, 'w>, target: Entity) -> Self { + Self { + commands, + target, + _marker: PhantomData, + } + } + pub fn spawn(&mut self, bundle: impl Bundle) -> EntityCommands<'_> { + self.commands.spawn((R::from(self.target), bundle)) + } + + pub fn spawn_empty(&mut self) -> EntityCommands<'_> { + self.commands.spawn(R::from(self.target)) + } + + pub fn target_entity(&self) -> Entity { + self.target + } +} 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..3ab90df54090e --- /dev/null +++ b/crates/bevy_ecs/src/relationship/relationship_query.rs @@ -0,0 +1,228 @@ +use crate::{ + entity::Entity, + query::{QueryData, QueryFilter, WorldQuery}, + relationship::{Relationship, RelationshipSources}, + system::Query, +}; +use smallvec::SmallVec; +use std::collections::VecDeque; + +impl<'w, 's, D: QueryData, F: QueryFilter> Query<'w, 's, D, F> { + pub fn related(&'w self, entity: Entity) -> Option + where + ::ReadOnly: WorldQuery = &'w R>, + { + self.get(entity).map(R::get).ok() + } + + pub fn relationship_sources( + &'w self, + entity: Entity, + ) -> impl Iterator + 'w + where + ::ReadOnly: WorldQuery = &'w S>, + { + self.get(entity) + .into_iter() + .flat_map(RelationshipSources::iter) + } + + 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, + } + } + + 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) + }) + } + + pub fn iter_siblings( + &'w self, + entity: Entity, + ) -> impl Iterator + 'w + where + D::ReadOnly: WorldQuery = (Option<&'w R>, Option<&'w R::RelationshipSources>)>, + { + 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)) + } + + pub fn iter_descendants( + &'w self, + entity: Entity, + ) -> DescendantIter<'w, 's, D, F, S> + where + D::ReadOnly: WorldQuery = &'w S>, + { + DescendantIter::new(self, entity) + } + + 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) + } + + 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: RelationshipSources> +where + D::ReadOnly: WorldQuery = &'w S>, +{ + children_query: &'w Query<'w, 's, D, F>, + vecdeque: VecDeque, +} + +impl<'w, 's, D: QueryData, F: QueryFilter, S: RelationshipSources> 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(RelationshipSources::iter) + .collect(), + } + } +} + +impl<'w, 's, D: QueryData, F: QueryFilter, S: RelationshipSources> 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: RelationshipSources> +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: RelationshipSources> + 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: RelationshipSources> 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..0fee0b125b3b5 --- /dev/null +++ b/crates/bevy_ecs/src/relationship/relationship_source_collection.rs @@ -0,0 +1,43 @@ +use crate::entity::Entity; +use alloc::vec::Vec; + +pub trait RelationshipSourceCollection { + fn with_capacity(capacity: usize) -> Self; + fn add(&mut self, entity: Entity); + fn remove(&mut self, entity: Entity); + fn iter(&self) -> impl DoubleEndedIterator; + fn take(&mut self) -> Vec; + fn len(&self) -> usize; + #[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 take(&mut self) -> Vec { + core::mem::take(self) + } + + fn len(&self) -> usize { + Vec::len(self) + } +} From 55864c0f8bf0861c1fd353274470468d9c295ade Mon Sep 17 00:00:00 2001 From: Carter Anderson Date: Wed, 15 Jan 2025 16:43:58 -0800 Subject: [PATCH 13/38] Documentation and ci --- crates/bevy_ecs/macros/src/lib.rs | 4 +- crates/bevy_ecs/macros/src/relationship.rs | 106 +++++++++++++----- crates/bevy_ecs/src/hierarchy.rs | 7 +- crates/bevy_ecs/src/reflect/component.rs | 18 +-- crates/bevy_ecs/src/relationship/mod.rs | 84 +++++++++++++- .../src/relationship/related_methods.rs | 17 +++ .../src/relationship/relationship_query.rs | 15 ++- .../relationship_source_collection.rs | 15 +++ .../src/system/commands/entity_command.rs | 4 +- crates/bevy_ecs/src/system/commands/mod.rs | 9 +- crates/bevy_ecs/src/world/mod.rs | 5 +- crates/bevy_render/src/view/visibility/mod.rs | 2 +- crates/bevy_scene/src/dynamic_scene.rs | 2 +- crates/bevy_state/src/state_scoped.rs | 4 - crates/bevy_transform/src/commands.rs | 2 +- .../src/components/global_transform.rs | 2 +- .../src/components/transform.rs | 2 +- .../src/experimental/ghost_hierarchy.rs | 2 - examples/3d/mixed_lighting.rs | 2 +- tools/ci/src/commands/compile_check_no_std.rs | 10 +- tools/publish.sh | 1 - 21 files changed, 240 insertions(+), 73 deletions(-) diff --git a/crates/bevy_ecs/macros/src/lib.rs b/crates/bevy_ecs/macros/src/lib.rs index 9b1c1383e9797..fa9135e82e214 100644 --- a/crates/bevy_ecs/macros/src/lib.rs +++ b/crates/bevy_ecs/macros/src/lib.rs @@ -613,12 +613,12 @@ pub fn derive_substates(input: TokenStream) -> TokenStream { states::derive_substates(input) } -#[proc_macro_derive(Relationship, attributes(relationship_sources))] +#[proc_macro_derive(Relationship, attributes(relationship))] pub fn derive_relationship(input: TokenStream) -> TokenStream { relationship::derive_relationship(input) } -#[proc_macro_derive(RelationshipSources, attributes(relationship, despawn_descendants))] +#[proc_macro_derive(RelationshipSources, attributes(relationship_sources))] pub fn derive_relationship_sources(input: TokenStream) -> TokenStream { relationship::derive_relationship_sources(input) } diff --git a/crates/bevy_ecs/macros/src/relationship.rs b/crates/bevy_ecs/macros/src/relationship.rs index 1571075e34aed..0a3a8b2760cf7 100644 --- a/crates/bevy_ecs/macros/src/relationship.rs +++ b/crates/bevy_ecs/macros/src/relationship.rs @@ -1,27 +1,29 @@ use proc_macro::TokenStream; use quote::quote; use syn::{ - parse_macro_input, parse_quote, spanned::Spanned, Data, DataStruct, DeriveInput, Fields, Ident, - Path, Visibility, + parse::Parse, parse_macro_input, parse_quote, spanned::Spanned, Data, DataStruct, DeriveInput, + Fields, Ident, Path, Token, Visibility, }; pub fn derive_relationship(input: TokenStream) -> TokenStream { let mut ast = parse_macro_input!(input as DeriveInput); let bevy_ecs_path: Path = crate::bevy_ecs_path(); - let Some(relationship_sources) = ast - .attrs - .iter() - .find(|a| a.path().is_ident("relationship_sources")) - .and_then(|a| a.parse_args::().ok()) + let Some(relationship_attribute) = ast.attrs.iter().find(|a| a.path().is_ident("relationship")) else { return syn::Error::new( ast.span(), - "Relationship derives must define a relationship_sources(SOURCES_COMPONENT) attribute.", + "Relationship derives must define a #[relationship(relationship_sources = X)] attribute.", ) .into_compile_error() .into(); }; + let relationship_args = match relationship_attribute.parse_args::() { + Ok(result) => result, + Err(err) => return err.into_compile_error().into(), + }; + + let relationship_sources = relationship_args.relationship_sources; 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 { @@ -63,10 +65,6 @@ pub fn derive_relationship(input: TokenStream) -> TokenStream { self.0 } - fn set(&mut self, entity: #bevy_ecs_path::entity::Entity) { - self.0 = entity; - } - fn from(entity: #bevy_ecs_path::entity::Entity) -> Self { Self(entity) } @@ -89,30 +87,42 @@ pub fn derive_relationship(input: TokenStream) -> TokenStream { }) } +pub struct RelationshipArgs { + relationship_sources: Ident, +} + +impl Parse for RelationshipArgs { + fn parse(input: syn::parse::ParseStream) -> syn::Result { + syn::custom_keyword!(relationship_sources); + input.parse::()?; + input.parse::()?; + Ok(RelationshipArgs { + relationship_sources: input.parse::()?, + }) + } +} + pub fn derive_relationship_sources(input: TokenStream) -> TokenStream { let mut ast = parse_macro_input!(input as DeriveInput); let bevy_ecs_path: Path = crate::bevy_ecs_path(); - let mut relationship = None; - let mut despawn_descendants = None; - for attr in ast.attrs.iter() { - if attr.path().is_ident("relationship") { - relationship = attr.parse_args::().ok(); - } - if attr.path().is_ident("despawn_descendants") { - despawn_descendants = - Some(quote! {hooks.on_despawn(::on_despawn);}); - } - } - - let Some(relationship) = relationship else { + let Some(relationship_sources_attribute) = ast + .attrs + .iter() + .find(|a| a.path().is_ident("relationship_sources")) + else { return syn::Error::new( ast.span(), - "RelationshipSources derives must define a relationship(RELATIONSHIP) attribute.", + "RelationshipSources derives must define a #[relationship_sources(relationship = X)] attribute.", ) .into_compile_error() .into(); }; + let relationship_sources_args = + match relationship_sources_attribute.parse_args::() { + Ok(result) => result, + Err(err) => return err.into_compile_error().into(), + }; const RELATIONSHIP_SOURCES_FORMAT_MESSAGE: &str = "RelationshipSources derives must be a tuple struct with the first element being a private RelationshipSourceCollection (ex: Children(Vec))"; let collection = if let Data::Struct(DataStruct { @@ -147,6 +157,11 @@ pub fn derive_relationship_sources(input: TokenStream) -> TokenStream { let struct_name = &ast.ident; let (impl_generics, type_generics, where_clause) = &ast.generics.split_for_impl(); + let relationship = relationship_sources_args.relationship; + let despawn_descendants = relationship_sources_args + .despawn_descendants + .then(|| quote! {hooks.on_despawn(::on_despawn);}); + TokenStream::from(quote! { impl #impl_generics #bevy_ecs_path::relationship::RelationshipSources for #struct_name #type_generics #where_clause { type Relationship = #relationship; @@ -179,3 +194,42 @@ pub fn derive_relationship_sources(input: TokenStream) -> TokenStream { } }) } + +pub struct RelationshipSourcesArgs { + relationship: Ident, + despawn_descendants: bool, +} + +impl Parse for RelationshipSourcesArgs { + fn parse(input: syn::parse::ParseStream) -> syn::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(), "RelationshipSources derive must specify a relationship via #[relationship_sources(relationship = X)"))?; + Ok(RelationshipSourcesArgs { + relationship, + despawn_descendants: despawn_descendants_exists, + }) + } +} diff --git a/crates/bevy_ecs/src/hierarchy.rs b/crates/bevy_ecs/src/hierarchy.rs index ef2b48098fabe..6a72db0462385 100644 --- a/crates/bevy_ecs/src/hierarchy.rs +++ b/crates/bevy_ecs/src/hierarchy.rs @@ -36,7 +36,7 @@ use log::warn; Debug, FromWorld )] -#[relationship_sources(Children)] +#[relationship(relationship_sources = Children)] pub struct Parent(pub Entity); impl Parent { @@ -57,8 +57,7 @@ impl FromWorld for Parent { } #[derive(RelationshipSources, Default, Reflect, VisitEntitiesMut)] -#[relationship(Parent)] -#[despawn_descendants] +#[relationship_sources(relationship = Parent, despawn_descendants)] #[reflect(Component, MapEntities, VisitEntities, VisitEntitiesMut)] pub struct Children(Vec); @@ -182,7 +181,7 @@ pub fn validate_parent_has_component( }; if !world .get_entity(child_of.get()) - .map_or(false, |e| e.contains::()) + .is_ok_and(|e| e.contains::()) { // TODO: print name here once Name lives in bevy_ecs let name: Option = None; 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 index 80fcdfed8a133..bc2b707b98cb6 100644 --- a/crates/bevy_ecs/src/relationship/mod.rs +++ b/crates/bevy_ecs/src/relationship/mod.rs @@ -1,3 +1,5 @@ +//! This module provides [`Relationship`] functionality. See the [`Relationship`] trait for more info. + mod related_methods; mod relationship_query; mod relationship_source_collection; @@ -20,15 +22,62 @@ use crate::{ }; use log::warn; -// The "deprecated" state is used to prevent users from mutating the internal RelationshipSource collection. +/// A [`Component`] on a "source" [`Entity`] that references another target [`Entity`], creating a "relationship" between them. Every [`Relationship`] +/// has a corresponding [`RelationshipSources`] 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 [`RelationshipSources`] component reflects that source of truth. When a [`Relationship`] +/// component is inserted on an [`Entity`], the corresponding [`RelationshipSources`] component is immediately inserted on the target component if it does +/// not already exist, and the "source" entity is automatically added to the [`RelationshipSources`] 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) [`RelationshipSources`]. +/// +/// [`Relationship`] and [`RelationshipSources`] should always be derived to ensure the hooks are set up properly. They will both automatically +/// implement [`Component`] with the necessary configuration to drive the [`Relationship`]. +/// +/// ``` +/// # use bevy_ecs::relationship::{Relationship, RelationshipSources}; +/// # use bevy_ecs::entity::Entity; +/// #[derive(Relationship)] +/// #[relationship(relationship_sources = Children)] +/// pub struct Parent(pub Entity); +/// +/// #[derive(RelationshipSources)] +/// #[relationship_sources(relationship = Parent)] +/// pub struct Children(Vec); +/// ``` +/// +/// When deriving [`RelationshipSources`] you can specify the `#[relationship_sources(despawn_descendants)]` attribute to +/// automatically despawn entities stored in an entity's [`RelationshipSources`] when that entity is despawned: +/// +/// ``` +/// # use bevy_ecs::relationship::{Relationship, RelationshipSources}; +/// # use bevy_ecs::entity::Entity; +/// #[derive(Relationship)] +/// #[relationship(relationship_sources = Children)] +/// pub struct Parent(pub Entity); +/// +/// #[derive(RelationshipSources)] +/// #[relationship_sources(relationship = Parent, despawn_descendants)] +/// pub struct Children(Vec); +/// ``` +/// +// NOTE: The "deprecated" state is used to prevent users from mutating the internal RelationshipSource collection. // These internals are allowed to modify the internal RelationshipSource collection. #[allow(deprecated)] 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 RelationshipSources: RelationshipSources; + /// Gets the [`Entity`] ID of the related entity. fn get(&self) -> Entity; - fn set(&mut self, entity: Entity); + + /// Creates this [`Relationship`] from the given `entity`. fn from(entity: Entity) -> Self; + + /// The `on_insert` component hook that maintains the [`Relationship`] / [`RelationshipSources`] connection. fn on_insert(mut world: DeferredWorld, entity: Entity, _: ComponentId) { let parent = world.entity(entity).get::().unwrap().get(); if parent == entity { @@ -60,6 +109,7 @@ pub trait Relationship: Component + Sized { } } + /// The `on_replace` component hook that maintains the [`Relationship`] / [`RelationshipSources`] connection. // note: think of this as "on_drop" fn on_replace(mut world: DeferredWorld, entity: Entity, _: ComponentId) { let parent = world.entity(entity).get::().unwrap().get(); @@ -78,19 +128,38 @@ pub trait Relationship: Component + Sized { } } +/// A [`Component`] containing the collection of entities that relate to this [`Entity`] via the associated `Relationship` type. +/// See the [`Relationship`] documentation for more information. +/// // The "deprecated" state is used to prevent users from mutating the internal RelationshipSource collection. // These internals are allowed to modify the internal RelationshipSource collection. #[allow(deprecated)] pub trait RelationshipSources: Component + Sized { + /// The [`Relationship`] that populates this [`RelationshipSources`] collection. type Relationship: Relationship; + /// The collection type that stores the "source" entities for this [`RelationshipSources`] component. type Collection: RelationshipSourceCollection; + /// Returns a reference to the stored [`RelationshipSources::Collection`]. fn collection(&self) -> &Self::Collection; + /// Returns a mutable reference to the stored [`RelationshipSources::Collection`]. + /// + /// # Warning + /// This should generally not be called by user code, as modifying the internal collection could invalidate the relationship. + /// This uses the "deprecated" state to warn users about this. #[deprecated = "Modifying the internal RelationshipSource collection should only be done by internals as it can invalidate relationships."] fn collection_mut(&mut self) -> &mut Self::Collection; + + /// Creates a new [`RelationshipSources`] from the given [`RelationshipSources::Collection`]. + /// + /// # Warning + /// This should generally not be called by user code, as constructing the internal collection could invalidate the relationship. + /// This uses the "deprecated" state to warn users about this. #[deprecated = "Creating a relationship source manually should only be done by internals as it can invalidate relationships."] fn from_collection(collection: Self::Collection) -> Self; + /// The `on_replace` component hook that maintains the [`Relationship`] / [`RelationshipSources`] 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 RelationshipSources collection @@ -113,6 +182,9 @@ pub trait RelationshipSources: Component + Sized { } } + /// The `on_despawn` component hook that despawns entities stored in an entity's [`RelationshipSources`] 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 RelationshipSources collection @@ -135,22 +207,26 @@ pub trait RelationshipSources: Component + Sized { } } + /// Creates this [`RelationshipSources`] with the given pre-allocated entity capacity. fn with_capacity(capacity: usize) -> Self { let collection = ::with_capacity(capacity); Self::from_collection(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() @@ -170,11 +246,11 @@ mod tests { #[test] fn custom_relationship() { #[derive(Relationship)] - #[relationship_sources(LikedBy)] + #[relationship(relationship_sources = LikedBy)] struct Likes(pub Entity); #[derive(RelationshipSources)] - #[relationship(Likes)] + #[relationship_sources(relationship = Likes)] struct LikedBy(Vec); let mut world = World::new(); diff --git a/crates/bevy_ecs/src/relationship/related_methods.rs b/crates/bevy_ecs/src/relationship/related_methods.rs index 8ed94aa6bab91..2713b1a74dafa 100644 --- a/crates/bevy_ecs/src/relationship/related_methods.rs +++ b/crates/bevy_ecs/src/relationship/related_methods.rs @@ -81,6 +81,8 @@ impl<'a> EntityCommands<'a> { } } +/// 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, @@ -88,6 +90,7 @@ pub struct RelatedSpawner<'w, R: Relationship> { } 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, @@ -96,19 +99,26 @@ impl<'w, R: Relationship> RelatedSpawner<'w, R> { } } + /// 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>, @@ -116,6 +126,7 @@ pub struct RelatedSpawnerCommands<'w, R: Relationship> { } 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, @@ -123,14 +134,20 @@ impl<'w, R: Relationship> RelatedSpawnerCommands<'w, R> { _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 } diff --git a/crates/bevy_ecs/src/relationship/relationship_query.rs b/crates/bevy_ecs/src/relationship/relationship_query.rs index 3ab90df54090e..0a5565bb2ee6d 100644 --- a/crates/bevy_ecs/src/relationship/relationship_query.rs +++ b/crates/bevy_ecs/src/relationship/relationship_query.rs @@ -4,10 +4,12 @@ use crate::{ relationship::{Relationship, RelationshipSources}, system::Query, }; +use alloc::collections::VecDeque; use smallvec::SmallVec; -use std::collections::VecDeque; 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>, @@ -15,6 +17,8 @@ impl<'w, 's, D: QueryData, F: QueryFilter> Query<'w, 's, D, F> { self.get(entity).map(R::get).ok() } + /// If the given `entity` contains the `S` [`RelationshipSources`] component, returns the + /// source entities stored on that component. pub fn relationship_sources( &'w self, entity: Entity, @@ -27,6 +31,8 @@ impl<'w, 's, D: QueryData, F: QueryFilter> Query<'w, 's, D, F> { .flat_map(RelationshipSources::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. pub fn root_ancestor(&'w self, entity: Entity) -> Entity where ::ReadOnly: WorldQuery = &'w R>, @@ -38,6 +44,7 @@ impl<'w, 's, D: QueryData, F: QueryFilter> Query<'w, 's, D, F> { } } + /// Iterates all "leaf entities" as defined by the [`RelationshipSources`] hierarchy. pub fn iter_leaves( &'w self, entity: Entity, @@ -54,6 +61,7 @@ impl<'w, 's, D: QueryData, F: QueryFilter> Query<'w, 's, D, F> { }) } + /// Iterates all sibling entities that also have the `R` [`Relationship`] with the same target entity. pub fn iter_siblings( &'w self, entity: Entity, @@ -70,6 +78,8 @@ impl<'w, 's, D: QueryData, F: QueryFilter> Query<'w, 's, D, F> { .flat_map(move |children| children.iter().filter(move |child| *child != entity)) } + /// Iterates all descendant entities as defined by the given `entity`'s [`RelationshipSources`] and their recursive + /// [`RelationshipSources`]. pub fn iter_descendants( &'w self, entity: Entity, @@ -80,6 +90,8 @@ impl<'w, 's, D: QueryData, F: QueryFilter> Query<'w, 's, D, F> { DescendantIter::new(self, entity) } + /// Iterates all descendant entities as defined by the given `entity`'s [`RelationshipSources`] and their recursive + /// [`RelationshipSources`] in depth-first order. pub fn iter_descendants_depth_first( &'w self, entity: Entity, @@ -90,6 +102,7 @@ impl<'w, 's, D: QueryData, F: QueryFilter> Query<'w, 's, D, F> { DescendantDepthFirstIter::new(self, entity) } + /// Iterates all ancestors of the given `entity` as defined by the `R` [`Relationship`]. pub fn iter_ancestors( &'w self, entity: Entity, diff --git a/crates/bevy_ecs/src/relationship/relationship_source_collection.rs b/crates/bevy_ecs/src/relationship/relationship_source_collection.rs index 0fee0b125b3b5..19af4ee79943d 100644 --- a/crates/bevy_ecs/src/relationship/relationship_source_collection.rs +++ b/crates/bevy_ecs/src/relationship/relationship_source_collection.rs @@ -1,13 +1,28 @@ use crate::entity::Entity; use alloc::vec::Vec; +/// The internal [`Entity`] collection used by a [`RelationshipSources`](crate::relationship::RelationshipSources) 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; + + /// Takes all entities in the given collection. fn take(&mut self) -> Vec; + + /// 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 diff --git a/crates/bevy_ecs/src/system/commands/entity_command.rs b/crates/bevy_ecs/src/system/commands/entity_command.rs index c81f657084ec0..8b2126b8c5b21 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 the entities in any [`RelationshipSources`](crate::relationship::RelationshipSources) that are configured +/// to despawn descendants. For example, this will recursively despawn [`Children`](crate::hierarchy::Children). 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 ffff2cb0944c2..d1f9de2a43fe3 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 [`RelationshipSources`](crate::relationship::RelationshipSources) that are configured + /// to despawn descendants. For example, this will recursively despawn [`Children`](crate::hierarchy::Children). /// /// # Example /// @@ -1728,6 +1728,11 @@ impl<'a> EntityCommands<'a> { /// /// 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 [`RelationshipSources`](crate::relationship::RelationshipSources) 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/world/mod.rs b/crates/bevy_ecs/src/world/mod.rs index 9cda5f0c4768d..b14d4419d2bd6 100644 --- a/crates/bevy_ecs/src/world/mod.rs +++ b/crates/bevy_ecs/src/world/mod.rs @@ -1085,6 +1085,7 @@ impl World { ) }; + // SAFETY: command_queue is not referenced anywhere else if !unsafe { self.command_queue.is_empty() } { self.flush_commands(); entity_location = self @@ -1267,8 +1268,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 [`RelationshipSources`](crate::relationship::RelationshipSources) that are 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_render/src/view/visibility/mod.rs b/crates/bevy_render/src/view/visibility/mod.rs index a0e138eec61dd..d0c84bd70ad19 100644 --- a/crates/bevy_render/src/view/visibility/mod.rs +++ b/crates/bevy_render/src/view/visibility/mod.rs @@ -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.\ diff --git a/crates/bevy_scene/src/dynamic_scene.rs b/crates/bevy_scene/src/dynamic_scene.rs index c3f45c36eae5b..f8adb6d0704f1 100644 --- a/crates/bevy_scene/src/dynamic_scene.rs +++ b/crates/bevy_scene/src/dynamic_scene.rs @@ -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_state/src/state_scoped.rs b/crates/bevy_state/src/state_scoped.rs index 083f5ed959838..3344ccc32652a 100644 --- a/crates/bevy_state/src/state_scoped.rs +++ b/crates/bevy_state/src/state_scoped.rs @@ -17,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::*; @@ -60,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>, diff --git a/crates/bevy_transform/src/commands.rs b/crates/bevy_transform/src/commands.rs index 646c7df903781..f5d8dc06bcfac 100644 --- a/crates/bevy_transform/src/commands.rs +++ b/crates/bevy_transform/src/commands.rs @@ -1,4 +1,4 @@ -//! 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}; diff --git a/crates/bevy_transform/src/components/global_transform.rs b/crates/bevy_transform/src/components/global_transform.rs index e53f253895a7c..86858a20fbefa 100644 --- a/crates/bevy_transform/src/components/global_transform.rs +++ b/crates/bevy_transform/src/components/global_transform.rs @@ -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 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_ui/src/experimental/ghost_hierarchy.rs b/crates/bevy_ui/src/experimental/ghost_hierarchy.rs index 728403cf5eaf0..28b07c49039ce 100644 --- a/crates/bevy_ui/src/experimental/ghost_hierarchy.rs +++ b/crates/bevy_ui/src/experimental/ghost_hierarchy.rs @@ -7,8 +7,6 @@ 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; diff --git a/examples/3d/mixed_lighting.rs b/examples/3d/mixed_lighting.rs index 88b2459f8e61c..b5d4474b751f3 100644 --- a/examples/3d/mixed_lighting.rs +++ b/examples/3d/mixed_lighting.rs @@ -458,7 +458,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/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 From a0519832dc8fedd96903bee71e6f3b8efbc5fd6d Mon Sep 17 00:00:00 2001 From: Carter Anderson Date: Wed, 15 Jan 2025 19:24:46 -0800 Subject: [PATCH 14/38] Register on_despawn in World::bootstrap --- crates/bevy_ecs/src/world/mod.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/crates/bevy_ecs/src/world/mod.rs b/crates/bevy_ecs/src/world/mod.rs index 9d4eb724de738..26cba3d168eac 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`]. /// From e823d1c25e97293b886ddd4bf00d70846449a56a Mon Sep 17 00:00:00 2001 From: Carter Anderson Date: Thu, 16 Jan 2025 13:05:21 -0800 Subject: [PATCH 15/38] Add commands access to RelatedSpawnerCommands --- crates/bevy_ecs/src/relationship/related_methods.rs | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/crates/bevy_ecs/src/relationship/related_methods.rs b/crates/bevy_ecs/src/relationship/related_methods.rs index 2713b1a74dafa..961aea9bbf367 100644 --- a/crates/bevy_ecs/src/relationship/related_methods.rs +++ b/crates/bevy_ecs/src/relationship/related_methods.rs @@ -151,4 +151,14 @@ impl<'w, R: Relationship> RelatedSpawnerCommands<'w, R> { 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 + } } From c69070da16d05ea91a3b9968cab598aac180288b Mon Sep 17 00:00:00 2001 From: Carter Anderson Date: Thu, 16 Jan 2025 14:45:01 -0800 Subject: [PATCH 16/38] Add comment about mutability to RelationshipSources --- crates/bevy_ecs/macros/src/relationship.rs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/crates/bevy_ecs/macros/src/relationship.rs b/crates/bevy_ecs/macros/src/relationship.rs index 0a3a8b2760cf7..8620fd6218d7b 100644 --- a/crates/bevy_ecs/macros/src/relationship.rs +++ b/crates/bevy_ecs/macros/src/relationship.rs @@ -161,7 +161,12 @@ pub fn derive_relationship_sources(input: TokenStream) -> TokenStream { let despawn_descendants = relationship_sources_args .despawn_descendants .then(|| quote! {hooks.on_despawn(::on_despawn);}); - + // NOTE: The Component impl is mutable for RelationshipSources for the following reasons: + // 1. RelationshipSources like Children will want user-facing APIs to reorder children, as order may be semantically relevant in some cases + // (or may just be organizational ... ex: dragging to reorder children in the editor). This does not violate the relationship correctness, + // so it can / should be allowed. + // 2. The immutable model doesn't really makes sense, given that we're appending to / removing from a list regularly as new children are added / removed. + // We could hack around this, but that would break the user-facing immutable data model. TokenStream::from(quote! { impl #impl_generics #bevy_ecs_path::relationship::RelationshipSources for #struct_name #type_generics #where_clause { type Relationship = #relationship; From cae502077ad9aa797124c9544c37fe93573d3520 Mon Sep 17 00:00:00 2001 From: Carter Anderson Date: Thu, 16 Jan 2025 14:51:14 -0800 Subject: [PATCH 17/38] More EntityWorldMut tests --- crates/bevy_ecs/src/hierarchy.rs | 78 ++++++++++++++++++++++++-------- 1 file changed, 59 insertions(+), 19 deletions(-) diff --git a/crates/bevy_ecs/src/hierarchy.rs b/crates/bevy_ecs/src/hierarchy.rs index 6a72db0462385..34f86f6785262 100644 --- a/crates/bevy_ecs/src/hierarchy.rs +++ b/crates/bevy_ecs/src/hierarchy.rs @@ -241,43 +241,83 @@ mod tests { #[test] fn hierarchy() { let mut world = World::new(); - let id = world.spawn_empty().id(); - let id1 = world.spawn(Parent(id)).id(); - let id3 = world.spawn(Parent(id1)).id(); - let id2 = world.spawn(Parent(id)).id(); + 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, id); + let hierarchy = get_hierarchy(&world, root); assert_eq!( hierarchy, Node::new_with( - id, - vec![Node::new_with(id1, vec![Node::new(id3)]), Node::new(id2)] + root, + vec![ + Node::new_with(child1, vec![Node::new(grandchild)]), + Node::new(child2) + ] ) ); // Removal - world.entity_mut(id1).remove::(); - let hierarchy = get_hierarchy(&world, id); - assert_eq!(hierarchy, Node::new_with(id, vec![Node::new(id2)])); + 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(id1).insert(Parent(id)); - let hierarchy = get_hierarchy(&world, id); + world.entity_mut(child1).insert(Parent(root)); + let hierarchy = get_hierarchy(&world, root); assert_eq!( hierarchy, Node::new_with( - id, - vec![Node::new(id2), Node::new_with(id1, vec![Node::new(id3)])] + root, + vec![ + Node::new(child2), + Node::new_with(child1, vec![Node::new(grandchild)]) + ] ) ); // Recursive Despawn - world.entity_mut(id).despawn(); - assert!(world.get_entity(id).is_err()); - assert!(world.get_entity(id1).is_err()); - assert!(world.get_entity(id2).is_err()); - assert!(world.get_entity(id3).is_err()); + 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] From 7aab4af6437553f6154388b3eae8b3b5939e0542 Mon Sep 17 00:00:00 2001 From: Carter Anderson Date: Thu, 16 Jan 2025 15:01:06 -0800 Subject: [PATCH 18/38] Remove take() --- .../src/relationship/relationship_source_collection.rs | 7 ------- 1 file changed, 7 deletions(-) diff --git a/crates/bevy_ecs/src/relationship/relationship_source_collection.rs b/crates/bevy_ecs/src/relationship/relationship_source_collection.rs index 19af4ee79943d..02a7fe7db166f 100644 --- a/crates/bevy_ecs/src/relationship/relationship_source_collection.rs +++ b/crates/bevy_ecs/src/relationship/relationship_source_collection.rs @@ -16,9 +16,6 @@ pub trait RelationshipSourceCollection { /// Iterates all entities in the collection. fn iter(&self) -> impl DoubleEndedIterator; - /// Takes all entities in the given collection. - fn take(&mut self) -> Vec; - /// Returns the current length of the collection. fn len(&self) -> usize; @@ -48,10 +45,6 @@ impl RelationshipSourceCollection for Vec { <[Entity]>::iter(self).copied() } - fn take(&mut self) -> Vec { - core::mem::take(self) - } - fn len(&self) -> usize { Vec::len(self) } From 0d6cffb74d66fa1ce10d8b9ca847060d6b3ed5fd Mon Sep 17 00:00:00 2001 From: Carter Anderson Date: Thu, 16 Jan 2025 15:01:20 -0800 Subject: [PATCH 19/38] Add deprecated version of despawn_recursive --- crates/bevy_ecs/src/system/commands/mod.rs | 8 ++++++++ crates/bevy_ecs/src/world/entity_ref.rs | 9 +++++++++ 2 files changed, 17 insertions(+) diff --git a/crates/bevy_ecs/src/system/commands/mod.rs b/crates/bevy_ecs/src/system/commands/mod.rs index 1b4c9b6f7ffa4..cac6c7f276c37 100644 --- a/crates/bevy_ecs/src/system/commands/mod.rs +++ b/crates/bevy_ecs/src/system/commands/mod.rs @@ -1723,6 +1723,14 @@ 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. /// diff --git a/crates/bevy_ecs/src/world/entity_ref.rs b/crates/bevy_ecs/src/world/entity_ref.rs index d28eaf1bd0584..26693db79d874 100644 --- a/crates/bevy_ecs/src/world/entity_ref.rs +++ b/crates/bevy_ecs/src/world/entity_ref.rs @@ -2107,6 +2107,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, From a6ebc650ce12c509a71232692f91d227d58e240e Mon Sep 17 00:00:00 2001 From: Carter Anderson Date: Thu, 16 Jan 2025 15:03:22 -0800 Subject: [PATCH 20/38] Put the children first! Why won't anyone think of the children! --- crates/bevy_ecs/src/system/commands/entity_command.rs | 4 ++-- crates/bevy_ecs/src/world/entity_ref.rs | 5 +++++ 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/crates/bevy_ecs/src/system/commands/entity_command.rs b/crates/bevy_ecs/src/system/commands/entity_command.rs index 8b2126b8c5b21..f163e9d56ee9e 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 will also despawn the entities in any [`RelationshipSources`](crate::relationship::RelationshipSources) that are configured -/// to despawn descendants. For example, this will recursively despawn [`Children`](crate::hierarchy::Children). +/// This will also despawn any [`Children`](crate::hierarchy::Children) entities, and any other [`RelationshipSources`](crate::relationship::RelationshipSources) that are 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/world/entity_ref.rs b/crates/bevy_ecs/src/world/entity_ref.rs index 26693db79d874..8e9c01afb1627 100644 --- a/crates/bevy_ecs/src/world/entity_ref.rs +++ b/crates/bevy_ecs/src/world/entity_ref.rs @@ -2096,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 [`RelationshipSources`](crate::relationship::RelationshipSources) that are configured + /// to despawn descendants. This results in "recursive despawn" behavior. + /// # Panics /// /// If the entity has been despawned while this `EntityWorldMut` is still alive. From 3cee622933d409b7ec7246e93ea9553af8d889d2 Mon Sep 17 00:00:00 2001 From: Carter Anderson Date: Thu, 16 Jan 2025 15:08:08 -0800 Subject: [PATCH 21/38] Adding warning about infinite loops --- crates/bevy_ecs/src/traversal.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/crates/bevy_ecs/src/traversal.rs b/crates/bevy_ecs/src/traversal.rs index 598a0e373f3de..3156d2462efa2 100644 --- a/crates/bevy_ecs/src/traversal.rs +++ b/crates/bevy_ecs/src/traversal.rs @@ -33,7 +33,9 @@ impl Traversal for () { /// This provides generalized hierarchy traversal for use in [event propagation]. /// -/// `Parent::traverse` will never form loops in properly-constructed hierarchies. +/// # Warning +/// +/// Traversing in a loop could result in infinite loops for relationship graphs with loops. /// /// [event propagation]: bevy_ecs::observer::Trigger::propagate impl Traversal for &R { From b9efb1a087577d6344849ced11e8b1aef8fb7e93 Mon Sep 17 00:00:00 2001 From: Carter Anderson Date: Thu, 16 Jan 2025 15:14:00 -0800 Subject: [PATCH 22/38] Warn about infinite loops --- .../src/relationship/relationship_query.rs | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/crates/bevy_ecs/src/relationship/relationship_query.rs b/crates/bevy_ecs/src/relationship/relationship_query.rs index 0a5565bb2ee6d..5a7a33cd27f6f 100644 --- a/crates/bevy_ecs/src/relationship/relationship_query.rs +++ b/crates/bevy_ecs/src/relationship/relationship_query.rs @@ -33,6 +33,10 @@ impl<'w, 's, D: QueryData, F: QueryFilter> Query<'w, 's, D, F> { /// 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>, @@ -45,6 +49,10 @@ impl<'w, 's, D: QueryData, F: QueryFilter> Query<'w, 's, D, F> { } /// Iterates all "leaf entities" as defined by the [`RelationshipSources`] 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, @@ -80,6 +88,10 @@ impl<'w, 's, D: QueryData, F: QueryFilter> Query<'w, 's, D, F> { /// Iterates all descendant entities as defined by the given `entity`'s [`RelationshipSources`] and their recursive /// [`RelationshipSources`]. + /// + /// # 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, @@ -92,6 +104,10 @@ impl<'w, 's, D: QueryData, F: QueryFilter> Query<'w, 's, D, F> { /// Iterates all descendant entities as defined by the given `entity`'s [`RelationshipSources`] and their recursive /// [`RelationshipSources`] 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, @@ -103,6 +119,10 @@ impl<'w, 's, D: QueryData, F: QueryFilter> Query<'w, 's, D, F> { } /// 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, From 30723bc1adc8c3e358535e78482d61b7998cdcb7 Mon Sep 17 00:00:00 2001 From: Carter Anderson Date: Thu, 16 Jan 2025 18:33:06 -0800 Subject: [PATCH 23/38] Derive Relationship and RelationshipSources through Component --- crates/bevy_ecs/macros/src/component.rs | 268 ++++++++++++++++++++- crates/bevy_ecs/macros/src/lib.rs | 13 +- crates/bevy_ecs/macros/src/relationship.rs | 240 ------------------ crates/bevy_ecs/src/hierarchy.rs | 5 +- crates/bevy_ecs/src/relationship/mod.rs | 26 +- 5 files changed, 272 insertions(+), 280 deletions(-) delete mode 100644 crates/bevy_ecs/macros/src/relationship.rs diff --git a/crates/bevy_ecs/macros/src/component.rs b/crates/bevy_ecs/macros/src/component.rs index d4f8cc8f6dbe0..7f79a7e30db81 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,14 +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_sources = match derive_relationship_sources(&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 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 on_despawn = hook_register_function_call(quote! {on_despawn}, attrs.on_despawn); + 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_sources) = &attrs.relationship_sources { + if on_replace.is_some() { + return syn::Error::new( + ast.span(), + "Custom on_replace hooks are not supported as RelationshipSources already define an on_replace hook", + ) + .into_compile_error() + .into(); + } + + on_replace = Some( + quote!(hooks.on_replace(::on_replace);), + ); + + if relationship_sources.despawn_descendants { + if on_despawn.is_some() { + return syn::Error::new( + ast.span(), + "Custom on_despawn hooks are not supported as this RelationshipSources 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() @@ -129,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_sources.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! { @@ -166,11 +242,13 @@ pub fn derive_component(input: TokenStream) -> TokenStream { } 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_sources }) } @@ -205,6 +283,8 @@ 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_SOURCES: &str = "relationship_sources"; pub const ON_ADD: &str = "on_add"; pub const ON_INSERT: &str = "on_insert"; @@ -222,6 +302,8 @@ struct Attrs { on_replace: Option, on_remove: Option, on_despawn: Option, + relationship: Option, + relationship_sources: Option, immutable: bool, } @@ -241,6 +323,15 @@ enum RequireFunc { Closure(ExprClosure), } +struct Relationship { + relationship_sources: Ident, +} + +struct RelationshipSources { + relationship: Ident, + despawn_descendants: bool, +} + // values for `storage` attribute const TABLE: &str = "Table"; const SPARSE_SET: &str = "SparseSet"; @@ -254,6 +345,8 @@ fn parse_component_attr(ast: &DeriveInput) -> Result { on_remove: None, on_despawn: None, requires: None, + relationship: None, + relationship_sources: None, immutable: false, }; @@ -310,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_SOURCES) { + let relationship_sources = attr.parse_args::()?; + attrs.relationship_sources = Some(relationship_sources); } } @@ -350,3 +449,154 @@ 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_sources); + input.parse::()?; + input.parse::()?; + Ok(Relationship { + relationship_sources: input.parse::()?, + }) + } +} + +impl Parse for RelationshipSources { + 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(), "RelationshipSources derive must specify a relationship via #[relationship_sources(relationship = X)"))?; + Ok(RelationshipSources { + 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_sources = &relationship.relationship_sources; + + Ok(Some(quote! { + impl #impl_generics #bevy_ecs_path::relationship::Relationship for #struct_name #type_generics #where_clause { + type RelationshipSources = #relationship_sources; + + #[inline(always)] + fn get(&self) -> #bevy_ecs_path::entity::Entity { + self.0 + } + + fn from(entity: #bevy_ecs_path::entity::Entity) -> Self { + Self(entity) + } + } + })) +} + +fn derive_relationship_sources( + ast: &DeriveInput, + attrs: &Attrs, + bevy_ecs_path: &Path, +) -> Result> { + let Some(relationship_sources) = &attrs.relationship_sources else { + return Ok(None); + }; + + const RELATIONSHIP_SOURCES_FORMAT_MESSAGE: &str = "RelationshipSources 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 RelationshipSources 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_SOURCES_FORMAT_MESSAGE, + )); + } + } else { + return Err(syn::Error::new( + ast.span(), + RELATIONSHIP_SOURCES_FORMAT_MESSAGE, + )); + }; + + let relationship = &relationship_sources.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::RelationshipSources for #struct_name #type_generics #where_clause { + type Relationship = #relationship; + type Collection = #collection; + + fn collection(&self) -> &Self::Collection { + &self.0 + } + + fn collection_mut(&mut self) -> &mut Self::Collection { + &mut self.0 + } + + fn from_collection(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 fa9135e82e214..9e2b7e0fb16ba 100644 --- a/crates/bevy_ecs/macros/src/lib.rs +++ b/crates/bevy_ecs/macros/src/lib.rs @@ -6,7 +6,6 @@ extern crate proc_macro; mod component; mod query_data; mod query_filter; -mod relationship; mod states; mod world_query; @@ -590,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_sources))] pub fn derive_component(input: TokenStream) -> TokenStream { component::derive_component(input) } @@ -612,13 +611,3 @@ pub fn derive_states(input: TokenStream) -> TokenStream { pub fn derive_substates(input: TokenStream) -> TokenStream { states::derive_substates(input) } - -#[proc_macro_derive(Relationship, attributes(relationship))] -pub fn derive_relationship(input: TokenStream) -> TokenStream { - relationship::derive_relationship(input) -} - -#[proc_macro_derive(RelationshipSources, attributes(relationship_sources))] -pub fn derive_relationship_sources(input: TokenStream) -> TokenStream { - relationship::derive_relationship_sources(input) -} diff --git a/crates/bevy_ecs/macros/src/relationship.rs b/crates/bevy_ecs/macros/src/relationship.rs deleted file mode 100644 index 8620fd6218d7b..0000000000000 --- a/crates/bevy_ecs/macros/src/relationship.rs +++ /dev/null @@ -1,240 +0,0 @@ -use proc_macro::TokenStream; -use quote::quote; -use syn::{ - parse::Parse, parse_macro_input, parse_quote, spanned::Spanned, Data, DataStruct, DeriveInput, - Fields, Ident, Path, Token, Visibility, -}; - -pub fn derive_relationship(input: TokenStream) -> TokenStream { - let mut ast = parse_macro_input!(input as DeriveInput); - let bevy_ecs_path: Path = crate::bevy_ecs_path(); - - let Some(relationship_attribute) = ast.attrs.iter().find(|a| a.path().is_ident("relationship")) - else { - return syn::Error::new( - ast.span(), - "Relationship derives must define a #[relationship(relationship_sources = X)] attribute.", - ) - .into_compile_error() - .into(); - }; - let relationship_args = match relationship_attribute.parse_args::() { - Ok(result) => result, - Err(err) => return err.into_compile_error().into(), - }; - - let relationship_sources = relationship_args.relationship_sources; - - 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 syn::Error::new(ast.span(), RELATIONSHIP_FORMAT_MESSAGE) - .into_compile_error() - .into(); - } - if unnamed_fields.unnamed.first().is_none() { - return syn::Error::new(struct_token.span(), RELATIONSHIP_FORMAT_MESSAGE) - .into_compile_error() - .into(); - } - } else { - return syn::Error::new(ast.span(), RELATIONSHIP_FORMAT_MESSAGE) - .into_compile_error() - .into(); - }; - - ast.generics - .make_where_clause() - .predicates - .push(parse_quote! { Self: Send + Sync + 'static }); - - let struct_name = &ast.ident; - let (impl_generics, type_generics, where_clause) = &ast.generics.split_for_impl(); - - TokenStream::from(quote! { - impl #impl_generics #bevy_ecs_path::relationship::Relationship for #struct_name #type_generics #where_clause { - type RelationshipSources = #relationship_sources; - - #[inline(always)] - fn get(&self) -> #bevy_ecs_path::entity::Entity { - self.0 - } - - fn from(entity: #bevy_ecs_path::entity::Entity) -> Self { - Self(entity) - } - } - - impl #impl_generics #bevy_ecs_path::component::Component for #struct_name #type_generics #where_clause { - const STORAGE_TYPE: #bevy_ecs_path::component::StorageType = #bevy_ecs_path::component::StorageType::Table; - type Mutability = #bevy_ecs_path::component::Immutable; - - fn register_component_hooks(hooks: &mut #bevy_ecs_path::component::ComponentHooks) { - hooks.on_insert(::on_insert); - hooks.on_replace(::on_replace); - } - fn get_component_clone_handler() -> #bevy_ecs_path::component::ComponentCloneHandler { - use #bevy_ecs_path::component::{ComponentCloneBase, ComponentCloneViaClone}; - (&&&#bevy_ecs_path::component::ComponentCloneSpecializationWrapper::::default()) - .get_component_clone_handler() - } - } - }) -} - -pub struct RelationshipArgs { - relationship_sources: Ident, -} - -impl Parse for RelationshipArgs { - fn parse(input: syn::parse::ParseStream) -> syn::Result { - syn::custom_keyword!(relationship_sources); - input.parse::()?; - input.parse::()?; - Ok(RelationshipArgs { - relationship_sources: input.parse::()?, - }) - } -} - -pub fn derive_relationship_sources(input: TokenStream) -> TokenStream { - let mut ast = parse_macro_input!(input as DeriveInput); - let bevy_ecs_path: Path = crate::bevy_ecs_path(); - - let Some(relationship_sources_attribute) = ast - .attrs - .iter() - .find(|a| a.path().is_ident("relationship_sources")) - else { - return syn::Error::new( - ast.span(), - "RelationshipSources derives must define a #[relationship_sources(relationship = X)] attribute.", - ) - .into_compile_error() - .into(); - }; - let relationship_sources_args = - match relationship_sources_attribute.parse_args::() { - Ok(result) => result, - Err(err) => return err.into_compile_error().into(), - }; - - const RELATIONSHIP_SOURCES_FORMAT_MESSAGE: &str = "RelationshipSources 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 syn::Error::new(first.span(), "The collection in RelationshipSources must be private to prevent users from directly mutating it, which could invalidate the correctness of relationships.") - .into_compile_error() - .into(); - } - first.ty.clone() - } else { - return syn::Error::new(struct_token.span(), RELATIONSHIP_SOURCES_FORMAT_MESSAGE) - .into_compile_error() - .into(); - } - } else { - return syn::Error::new(ast.span(), RELATIONSHIP_SOURCES_FORMAT_MESSAGE) - .into_compile_error() - .into(); - }; - - ast.generics - .make_where_clause() - .predicates - .push(parse_quote! { Self: Send + Sync + 'static }); - - let struct_name = &ast.ident; - let (impl_generics, type_generics, where_clause) = &ast.generics.split_for_impl(); - - let relationship = relationship_sources_args.relationship; - let despawn_descendants = relationship_sources_args - .despawn_descendants - .then(|| quote! {hooks.on_despawn(::on_despawn);}); - // NOTE: The Component impl is mutable for RelationshipSources for the following reasons: - // 1. RelationshipSources like Children will want user-facing APIs to reorder children, as order may be semantically relevant in some cases - // (or may just be organizational ... ex: dragging to reorder children in the editor). This does not violate the relationship correctness, - // so it can / should be allowed. - // 2. The immutable model doesn't really makes sense, given that we're appending to / removing from a list regularly as new children are added / removed. - // We could hack around this, but that would break the user-facing immutable data model. - TokenStream::from(quote! { - impl #impl_generics #bevy_ecs_path::relationship::RelationshipSources for #struct_name #type_generics #where_clause { - type Relationship = #relationship; - type Collection = #collection; - - fn collection(&self) -> &Self::Collection { - &self.0 - } - - fn collection_mut(&mut self) -> &mut Self::Collection { - &mut self.0 - } - - fn from_collection(collection: Self::Collection) -> Self { - Self(collection) - } - } - - impl #impl_generics #bevy_ecs_path::component::Component for #struct_name #type_generics #where_clause { - const STORAGE_TYPE: #bevy_ecs_path::component::StorageType = #bevy_ecs_path::component::StorageType::Table; - type Mutability = #bevy_ecs_path::component::Mutable; - - fn register_component_hooks(hooks: &mut #bevy_ecs_path::component::ComponentHooks) { - hooks.on_replace(::on_replace); - #despawn_descendants - } - fn get_component_clone_handler() -> #bevy_ecs_path::component::ComponentCloneHandler { - #bevy_ecs_path::component::ComponentCloneHandler::ignore() - } - } - }) -} - -pub struct RelationshipSourcesArgs { - relationship: Ident, - despawn_descendants: bool, -} - -impl Parse for RelationshipSourcesArgs { - fn parse(input: syn::parse::ParseStream) -> syn::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(), "RelationshipSources derive must specify a relationship via #[relationship_sources(relationship = X)"))?; - Ok(RelationshipSourcesArgs { - relationship, - despawn_descendants: despawn_descendants_exists, - }) - } -} diff --git a/crates/bevy_ecs/src/hierarchy.rs b/crates/bevy_ecs/src/hierarchy.rs index 34f86f6785262..ab252b14fedad 100644 --- a/crates/bevy_ecs/src/hierarchy.rs +++ b/crates/bevy_ecs/src/hierarchy.rs @@ -16,7 +16,6 @@ use crate::{ ReflectComponent, ReflectFromWorld, ReflectMapEntities, ReflectVisitEntities, ReflectVisitEntitiesMut, }, - relationship::{Relationship, RelationshipSources}, world::{FromWorld, World}, }; use alloc::{format, string::String, vec::Vec}; @@ -26,7 +25,7 @@ use core::slice; use disqualified::ShortName; use log::warn; -#[derive(Relationship, Clone, Reflect, VisitEntities, VisitEntitiesMut, PartialEq, Eq, Debug)] +#[derive(Component, Clone, Reflect, VisitEntities, VisitEntitiesMut, PartialEq, Eq, Debug)] #[reflect( Component, MapEntities, @@ -56,7 +55,7 @@ impl FromWorld for Parent { } } -#[derive(RelationshipSources, Default, Reflect, VisitEntitiesMut)] +#[derive(Component, Default, Reflect, VisitEntitiesMut)] #[relationship_sources(relationship = Parent, despawn_descendants)] #[reflect(Component, MapEntities, VisitEntities, VisitEntitiesMut)] pub struct Children(Vec); diff --git a/crates/bevy_ecs/src/relationship/mod.rs b/crates/bevy_ecs/src/relationship/mod.rs index bc2b707b98cb6..8464210d46d21 100644 --- a/crates/bevy_ecs/src/relationship/mod.rs +++ b/crates/bevy_ecs/src/relationship/mod.rs @@ -8,8 +8,6 @@ pub use related_methods::*; pub use relationship_query::*; pub use relationship_source_collection::*; -pub use bevy_ecs_macros::{Relationship, RelationshipSources}; - use crate::{ component::{Component, ComponentId, Mutable}, entity::Entity, @@ -33,17 +31,16 @@ use log::warn; /// 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) [`RelationshipSources`]. /// -/// [`Relationship`] and [`RelationshipSources`] should always be derived to ensure the hooks are set up properly. They will both automatically -/// implement [`Component`] with the necessary configuration to drive the [`Relationship`]. +/// [`Relationship`] and [`RelationshipSources`] should always be derived via the [`Component`] trait to ensure the hooks are set up properly. /// /// ``` -/// # use bevy_ecs::relationship::{Relationship, RelationshipSources}; +/// # use bevy_ecs::component::Component; /// # use bevy_ecs::entity::Entity; -/// #[derive(Relationship)] +/// #[derive(Component)] /// #[relationship(relationship_sources = Children)] /// pub struct Parent(pub Entity); /// -/// #[derive(RelationshipSources)] +/// #[derive(Component)] /// #[relationship_sources(relationship = Parent)] /// pub struct Children(Vec); /// ``` @@ -52,13 +49,13 @@ use log::warn; /// automatically despawn entities stored in an entity's [`RelationshipSources`] when that entity is despawned: /// /// ``` -/// # use bevy_ecs::relationship::{Relationship, RelationshipSources}; +/// # use bevy_ecs::component::Component; /// # use bevy_ecs::entity::Entity; -/// #[derive(Relationship)] +/// #[derive(Component)] /// #[relationship(relationship_sources = Children)] /// pub struct Parent(pub Entity); /// -/// #[derive(RelationshipSources)] +/// #[derive(Component)] /// #[relationship_sources(relationship = Parent, despawn_descendants)] /// pub struct Children(Vec); /// ``` @@ -237,19 +234,16 @@ pub trait RelationshipSources: Component + Sized { mod tests { use crate as bevy_ecs; use crate::world::World; - use crate::{ - entity::Entity, - relationship::{Relationship, RelationshipSources}, - }; + use crate::{component::Component, entity::Entity}; use alloc::vec::Vec; #[test] fn custom_relationship() { - #[derive(Relationship)] + #[derive(Component)] #[relationship(relationship_sources = LikedBy)] struct Likes(pub Entity); - #[derive(RelationshipSources)] + #[derive(Component)] #[relationship_sources(relationship = Likes)] struct LikedBy(Vec); From ac94d66c4ad3c273a02bcf3bdb866843eb1467ef Mon Sep 17 00:00:00 2001 From: Carter Anderson Date: Fri, 17 Jan 2025 15:20:57 -0800 Subject: [PATCH 24/38] Add hierarchy docs and Deref impl --- crates/bevy_ecs/src/hierarchy.rs | 103 +++++++++++++++++++++++++++++-- 1 file changed, 97 insertions(+), 6 deletions(-) diff --git a/crates/bevy_ecs/src/hierarchy.rs b/crates/bevy_ecs/src/hierarchy.rs index ab252b14fedad..ff9f3596b9730 100644 --- a/crates/bevy_ecs/src/hierarchy.rs +++ b/crates/bevy_ecs/src/hierarchy.rs @@ -1,7 +1,10 @@ -// TODO: REMOVE THIS -#![allow(missing_docs)] - -//! Parent-Child relationships for entities. +//! The canonical "parent-child" [`Relationship`] for entities, driven by +//! the [`Parent`] [`Relationship`] and the [`Children`] [`RelationshipSources`]. +//! +//! See [`Parent`] for a full description of the relationship and how to use it. +//! +//! [`Relationship`]: crate::relationship::Relationship +//! [`RelationshipSources`]: crate::relationship::RelationshipSources use crate as bevy_ecs; use crate::bundle::Bundle; @@ -21,10 +24,74 @@ use crate::{ use alloc::{format, string::String, vec::Vec}; use bevy_ecs_macros::VisitEntitiesMut; use bevy_reflect::Reflect; +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`] [`RelationshipSources`](crate::relationship::RelationshipSources). +/// +/// 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, Reflect, VisitEntities, VisitEntitiesMut, PartialEq, Eq, Debug)] #[reflect( Component, @@ -39,11 +106,21 @@ use log::warn; 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 @@ -55,7 +132,12 @@ impl FromWorld for Parent { } } -#[derive(Component, Default, Reflect, VisitEntitiesMut)] +/// A [`RelationshipSources`](crate::relationship::RelationshipSources) 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, Reflect, VisitEntitiesMut, Debug, PartialEq, Eq)] #[relationship_sources(relationship = Parent, despawn_descendants)] #[reflect(Component, MapEntities, VisitEntities, VisitEntitiesMut)] pub struct Children(Vec); @@ -71,7 +153,7 @@ impl<'a> IntoIterator for &'a Children { } } -impl core::ops::Deref for Children { +impl Deref for Children { type Target = [Entity]; fn deref(&self) -> &Self::Target { @@ -79,7 +161,10 @@ impl core::ops::Deref for Children { } } +/// 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> { @@ -112,12 +197,14 @@ impl<'w> EntityWorldMut<'w> { 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)); @@ -156,12 +243,14 @@ impl<'a> EntityCommands<'a> { 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)); @@ -169,6 +258,8 @@ impl<'a> EntityCommands<'a> { } } +/// 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, From 74eaf3621b46405a6dde1289082e50b3f05b01a2 Mon Sep 17 00:00:00 2001 From: Carter Anderson Date: Fri, 17 Jan 2025 15:27:48 -0800 Subject: [PATCH 25/38] Update crates/bevy_ecs/src/relationship/mod.rs Co-authored-by: Alice Cecile --- crates/bevy_ecs/src/relationship/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/bevy_ecs/src/relationship/mod.rs b/crates/bevy_ecs/src/relationship/mod.rs index 8464210d46d21..3b706e8c859a7 100644 --- a/crates/bevy_ecs/src/relationship/mod.rs +++ b/crates/bevy_ecs/src/relationship/mod.rs @@ -1,4 +1,4 @@ -//! This module provides [`Relationship`] functionality. See the [`Relationship`] trait for more info. +//! 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; From 66344923c62916e6cd3e9605c01be5a586d07b6f Mon Sep 17 00:00:00 2001 From: Carter Anderson Date: Fri, 17 Jan 2025 15:33:11 -0800 Subject: [PATCH 26/38] Scary names not deprecation --- crates/bevy_ecs/macros/src/component.rs | 8 ++++++-- crates/bevy_ecs/src/relationship/mod.rs | 22 ++++++---------------- 2 files changed, 12 insertions(+), 18 deletions(-) diff --git a/crates/bevy_ecs/macros/src/component.rs b/crates/bevy_ecs/macros/src/component.rs index 7f79a7e30db81..57d695ab563df 100644 --- a/crates/bevy_ecs/macros/src/component.rs +++ b/crates/bevy_ecs/macros/src/component.rs @@ -537,6 +537,7 @@ fn derive_relationship( self.0 } + #[inline] fn from(entity: #bevy_ecs_path::entity::Entity) -> Self { Self(entity) } @@ -586,15 +587,18 @@ fn derive_relationship_sources( type Relationship = #relationship; type Collection = #collection; + #[inline] fn collection(&self) -> &Self::Collection { &self.0 } - fn collection_mut(&mut self) -> &mut Self::Collection { + #[inline] + fn collection_mut_risky(&mut self) -> &mut Self::Collection { &mut self.0 } - fn from_collection(collection: Self::Collection) -> Self { + #[inline] + fn from_collection_risky(collection: Self::Collection) -> Self { Self(collection) } } diff --git a/crates/bevy_ecs/src/relationship/mod.rs b/crates/bevy_ecs/src/relationship/mod.rs index 3b706e8c859a7..a52ab1cc87206 100644 --- a/crates/bevy_ecs/src/relationship/mod.rs +++ b/crates/bevy_ecs/src/relationship/mod.rs @@ -59,10 +59,6 @@ use log::warn; /// #[relationship_sources(relationship = Parent, despawn_descendants)] /// pub struct Children(Vec); /// ``` -/// -// NOTE: The "deprecated" state is used to prevent users from mutating the internal RelationshipSource collection. -// These internals are allowed to modify the internal RelationshipSource collection. -#[allow(deprecated)] 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". @@ -89,11 +85,11 @@ pub trait Relationship: Component + Sized { if let Some(mut relationship_sources) = parent_entity.get_mut::() { - relationship_sources.collection_mut().add(entity); + relationship_sources.collection_mut_risky().add(entity); } else { let mut sources = ::with_capacity(1); - sources.collection_mut().add(entity); + sources.collection_mut_risky().add(entity); world.commands().entity(parent).insert(sources); } } else { @@ -114,7 +110,7 @@ pub trait Relationship: Component + Sized { if let Some(mut relationship_sources) = parent_entity.get_mut::() { - relationship_sources.collection_mut().remove(entity); + relationship_sources.collection_mut_risky().remove(entity); if relationship_sources.len() == 0 { if let Some(mut entity) = world.commands().get_entity(parent) { entity.remove::(); @@ -127,10 +123,6 @@ pub trait Relationship: Component + Sized { /// A [`Component`] containing the collection of entities that relate to this [`Entity`] via the associated `Relationship` type. /// See the [`Relationship`] documentation for more information. -/// -// The "deprecated" state is used to prevent users from mutating the internal RelationshipSource collection. -// These internals are allowed to modify the internal RelationshipSource collection. -#[allow(deprecated)] pub trait RelationshipSources: Component + Sized { /// The [`Relationship`] that populates this [`RelationshipSources`] collection. type Relationship: Relationship; @@ -144,16 +136,14 @@ pub trait RelationshipSources: Component + Sized { /// # Warning /// This should generally not be called by user code, as modifying the internal collection could invalidate the relationship. /// This uses the "deprecated" state to warn users about this. - #[deprecated = "Modifying the internal RelationshipSource collection should only be done by internals as it can invalidate relationships."] - fn collection_mut(&mut self) -> &mut Self::Collection; + fn collection_mut_risky(&mut self) -> &mut Self::Collection; /// Creates a new [`RelationshipSources`] from the given [`RelationshipSources::Collection`]. /// /// # Warning /// This should generally not be called by user code, as constructing the internal collection could invalidate the relationship. /// This uses the "deprecated" state to warn users about this. - #[deprecated = "Creating a relationship source manually should only be done by internals as it can invalidate relationships."] - fn from_collection(collection: Self::Collection) -> Self; + fn from_collection_risky(collection: Self::Collection) -> Self; /// The `on_replace` component hook that maintains the [`Relationship`] / [`RelationshipSources`] connection. // note: think of this as "on_drop" @@ -208,7 +198,7 @@ pub trait RelationshipSources: Component + Sized { fn with_capacity(capacity: usize) -> Self { let collection = ::with_capacity(capacity); - Self::from_collection(collection) + Self::from_collection_risky(collection) } /// Iterates the entities stored in this collection. From ab00b6ddaae84a7fd33069236e71f4a2857bc263 Mon Sep 17 00:00:00 2001 From: Carter Anderson Date: Fri, 17 Jan 2025 15:33:49 -0800 Subject: [PATCH 27/38] Clippy --- crates/bevy_ecs/src/hierarchy.rs | 4 ++-- crates/bevy_ecs/src/world/entity_ref.rs | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/crates/bevy_ecs/src/hierarchy.rs b/crates/bevy_ecs/src/hierarchy.rs index ff9f3596b9730..0adc918f7a3f1 100644 --- a/crates/bevy_ecs/src/hierarchy.rs +++ b/crates/bevy_ecs/src/hierarchy.rs @@ -258,7 +258,7 @@ impl<'a> EntityCommands<'a> { } } -/// An "on_insert" component hook that when run, will validate that the parent of a given entity +/// 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, @@ -385,7 +385,7 @@ mod tests { .spawn_empty() .with_children(|p| { child1 = p.spawn_empty().id(); - child2 = p.spawn_empty().id() + child2 = p.spawn_empty().id(); }) .id(); diff --git a/crates/bevy_ecs/src/world/entity_ref.rs b/crates/bevy_ecs/src/world/entity_ref.rs index 8e9c01afb1627..09adcea53268b 100644 --- a/crates/bevy_ecs/src/world/entity_ref.rs +++ b/crates/bevy_ecs/src/world/entity_ref.rs @@ -2100,7 +2100,7 @@ impl<'w> EntityWorldMut<'w> { /// /// This will also despawn any [`Children`](crate::hierarchy::Children) entities, and any other [`RelationshipSources`](crate::relationship::RelationshipSources) that are configured /// to despawn descendants. This results in "recursive despawn" behavior. - + /// /// # Panics /// /// If the entity has been despawned while this `EntityWorldMut` is still alive. From 3e84a87546359c69bf5853a3fe34f6fd3fdc8151 Mon Sep 17 00:00:00 2001 From: Carter Anderson Date: Fri, 17 Jan 2025 15:53:22 -0800 Subject: [PATCH 28/38] RelationshipSources -> RelationshipTarget --- crates/bevy_ecs/macros/src/component.rs | 66 +++++------ crates/bevy_ecs/macros/src/lib.rs | 2 +- crates/bevy_ecs/src/hierarchy.rs | 14 +-- crates/bevy_ecs/src/relationship/mod.rs | 105 +++++++++--------- .../src/relationship/related_methods.rs | 10 +- .../src/relationship/relationship_query.rs | 40 +++---- .../relationship_source_collection.rs | 2 +- .../src/system/commands/entity_command.rs | 2 +- crates/bevy_ecs/src/system/commands/mod.rs | 4 +- crates/bevy_ecs/src/world/entity_ref.rs | 2 +- crates/bevy_ecs/src/world/mod.rs | 2 +- 11 files changed, 124 insertions(+), 125 deletions(-) diff --git a/crates/bevy_ecs/macros/src/component.rs b/crates/bevy_ecs/macros/src/component.rs index 57d695ab563df..44c25171c250a 100644 --- a/crates/bevy_ecs/macros/src/component.rs +++ b/crates/bevy_ecs/macros/src/component.rs @@ -64,7 +64,7 @@ pub fn derive_component(input: TokenStream) -> TokenStream { Ok(value) => value, Err(err) => err.into_compile_error().into(), }; - let relationship_sources = match derive_relationship_sources(&ast, &attrs, &bevy_ecs_path) { + let relationship_target = match derive_relationship_target(&ast, &attrs, &bevy_ecs_path) { Ok(value) => value, Err(err) => err.into_compile_error().into(), }; @@ -106,32 +106,32 @@ pub fn derive_component(input: TokenStream) -> TokenStream { ); } - if let Some(relationship_sources) = &attrs.relationship_sources { + 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 RelationshipSources already define an on_replace hook", + "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);), + quote!(hooks.on_replace(::on_replace);), ); - if relationship_sources.despawn_descendants { + 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 RelationshipSources already defines an on_despawn hook, via the despawn_descendants attribute", + "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);), + quote!(hooks.on_despawn(::on_despawn);), ); } } @@ -201,7 +201,7 @@ pub fn derive_component(input: TokenStream) -> TokenStream { .then_some(quote! { #bevy_ecs_path::component::Immutable }) .unwrap_or(quote! { #bevy_ecs_path::component::Mutable }); - let clone_handler = if relationship_sources.is_some() { + let clone_handler = if relationship_target.is_some() { quote!(#bevy_ecs_path::component::ComponentCloneHandler::ignore()) } else { quote!( @@ -248,7 +248,7 @@ pub fn derive_component(input: TokenStream) -> TokenStream { #relationship - #relationship_sources + #relationship_target }) } @@ -284,7 +284,7 @@ pub const COMPONENT: &str = "component"; pub const STORAGE: &str = "storage"; pub const REQUIRE: &str = "require"; pub const RELATIONSHIP: &str = "relationship"; -pub const RELATIONSHIP_SOURCES: &str = "relationship_sources"; +pub const RELATIONSHIP_TARGET: &str = "relationship_target"; pub const ON_ADD: &str = "on_add"; pub const ON_INSERT: &str = "on_insert"; @@ -303,7 +303,7 @@ struct Attrs { on_remove: Option, on_despawn: Option, relationship: Option, - relationship_sources: Option, + relationship_target: Option, immutable: bool, } @@ -324,10 +324,10 @@ enum RequireFunc { } struct Relationship { - relationship_sources: Ident, + relationship_target: Ident, } -struct RelationshipSources { +struct RelationshipTarget { relationship: Ident, despawn_descendants: bool, } @@ -346,7 +346,7 @@ fn parse_component_attr(ast: &DeriveInput) -> Result { on_despawn: None, requires: None, relationship: None, - relationship_sources: None, + relationship_target: None, immutable: false, }; @@ -406,9 +406,9 @@ fn parse_component_attr(ast: &DeriveInput) -> Result { } else if attr.path().is_ident(RELATIONSHIP) { let relationship = attr.parse_args::()?; attrs.relationship = Some(relationship); - } else if attr.path().is_ident(RELATIONSHIP_SOURCES) { - let relationship_sources = attr.parse_args::()?; - attrs.relationship_sources = Some(relationship_sources); + } else if attr.path().is_ident(RELATIONSHIP_TARGET) { + let relationship_target = attr.parse_args::()?; + attrs.relationship_target = Some(relationship_target); } } @@ -452,16 +452,16 @@ fn hook_register_function_call( impl Parse for Relationship { fn parse(input: syn::parse::ParseStream) -> Result { - syn::custom_keyword!(relationship_sources); - input.parse::()?; + syn::custom_keyword!(relationship_target); + input.parse::()?; input.parse::()?; Ok(Relationship { - relationship_sources: input.parse::()?, + relationship_target: input.parse::()?, }) } } -impl Parse for RelationshipSources { +impl Parse for RelationshipTarget { fn parse(input: syn::parse::ParseStream) -> Result { let mut relationship_ident = None; let mut despawn_descendants_exists = false; @@ -487,8 +487,8 @@ impl Parse for RelationshipSources { } } - let relationship = relationship_ident.ok_or_else(|| syn::Error::new(input.span(), "RelationshipSources derive must specify a relationship via #[relationship_sources(relationship = X)"))?; - Ok(RelationshipSources { + 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, }) @@ -526,11 +526,11 @@ fn derive_relationship( let struct_name = &ast.ident; let (impl_generics, type_generics, where_clause) = &ast.generics.split_for_impl(); - let relationship_sources = &relationship.relationship_sources; + 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 RelationshipSources = #relationship_sources; + type RelationshipTarget = #relationship_target; #[inline(always)] fn get(&self) -> #bevy_ecs_path::entity::Entity { @@ -545,16 +545,16 @@ fn derive_relationship( })) } -fn derive_relationship_sources( +fn derive_relationship_target( ast: &DeriveInput, attrs: &Attrs, bevy_ecs_path: &Path, ) -> Result> { - let Some(relationship_sources) = &attrs.relationship_sources else { + let Some(relationship_target) = &attrs.relationship_target else { return Ok(None); }; - const RELATIONSHIP_SOURCES_FORMAT_MESSAGE: &str = "RelationshipSources derives must be a tuple struct with the first element being a private RelationshipSourceCollection (ex: Children(Vec))"; + 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, @@ -563,27 +563,27 @@ fn derive_relationship_sources( { if let Some(first) = unnamed_fields.unnamed.first() { if first.vis != Visibility::Inherited { - return Err(syn::Error::new(first.span(), "The collection in RelationshipSources must be private to prevent users from directly mutating it, which could invalidate the correctness of relationships.")); + 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_SOURCES_FORMAT_MESSAGE, + RELATIONSHIP_TARGET_FORMAT_MESSAGE, )); } } else { return Err(syn::Error::new( ast.span(), - RELATIONSHIP_SOURCES_FORMAT_MESSAGE, + RELATIONSHIP_TARGET_FORMAT_MESSAGE, )); }; - let relationship = &relationship_sources.relationship; + 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::RelationshipSources for #struct_name #type_generics #where_clause { + impl #impl_generics #bevy_ecs_path::relationship::RelationshipTarget for #struct_name #type_generics #where_clause { type Relationship = #relationship; type Collection = #collection; diff --git a/crates/bevy_ecs/macros/src/lib.rs b/crates/bevy_ecs/macros/src/lib.rs index 9e2b7e0fb16ba..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, relationship, relationship_sources))] +#[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/hierarchy.rs b/crates/bevy_ecs/src/hierarchy.rs index 0adc918f7a3f1..e5a62d2f882a5 100644 --- a/crates/bevy_ecs/src/hierarchy.rs +++ b/crates/bevy_ecs/src/hierarchy.rs @@ -1,10 +1,10 @@ //! The canonical "parent-child" [`Relationship`] for entities, driven by -//! the [`Parent`] [`Relationship`] and the [`Children`] [`RelationshipSources`]. +//! 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 -//! [`RelationshipSources`]: crate::relationship::RelationshipSources +//! [`RelationshipTarget`]: crate::relationship::RelationshipTarget use crate as bevy_ecs; use crate::bundle::Bundle; @@ -31,7 +31,7 @@ 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`] [`RelationshipSources`](crate::relationship::RelationshipSources). +/// the [`Children`] [`RelationshipTarget`](crate::relationship::RelationshipTarget). /// /// This relationship should be used for things like: /// @@ -102,7 +102,7 @@ use log::warn; Debug, FromWorld )] -#[relationship(relationship_sources = Children)] +#[relationship(relationship_target = Children)] pub struct Parent(pub Entity); impl Parent { @@ -132,13 +132,13 @@ impl FromWorld for Parent { } } -/// A [`RelationshipSources`](crate::relationship::RelationshipSources) collection component that is populated +/// 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, Reflect, VisitEntitiesMut, Debug, PartialEq, Eq)] -#[relationship_sources(relationship = Parent, despawn_descendants)] +#[relationship_target(relationship = Parent, despawn_descendants)] #[reflect(Component, MapEntities, VisitEntities, VisitEntitiesMut)] pub struct Children(Vec); @@ -292,7 +292,7 @@ mod tests { use crate::{ entity::Entity, hierarchy::{Children, Parent}, - relationship::RelationshipSources, + relationship::RelationshipTarget, world::World, }; use alloc::{vec, vec::Vec}; diff --git a/crates/bevy_ecs/src/relationship/mod.rs b/crates/bevy_ecs/src/relationship/mod.rs index a52ab1cc87206..16ed8cab7a5d3 100644 --- a/crates/bevy_ecs/src/relationship/mod.rs +++ b/crates/bevy_ecs/src/relationship/mod.rs @@ -21,48 +21,48 @@ use crate::{ use log::warn; /// A [`Component`] on a "source" [`Entity`] that references another target [`Entity`], creating a "relationship" between them. Every [`Relationship`] -/// has a corresponding [`RelationshipSources`] type (and vice-versa), which exists on the "target" entity of a relationship and contains the list of all +/// 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 [`RelationshipSources`] component reflects that source of truth. When a [`Relationship`] -/// component is inserted on an [`Entity`], the corresponding [`RelationshipSources`] component is immediately inserted on the target component if it does -/// not already exist, and the "source" entity is automatically added to the [`RelationshipSources`] collection (this is done via "component hooks"). +/// 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) [`RelationshipSources`]. +/// [`Relationship`] and the [`Children`](crate::hierarchy::Children) [`RelationshipTarget`]. /// -/// [`Relationship`] and [`RelationshipSources`] should always be derived via the [`Component`] trait to ensure the hooks are set up properly. +/// [`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_sources = Children)] +/// #[relationship(relationship_target = Children)] /// pub struct Parent(pub Entity); /// /// #[derive(Component)] -/// #[relationship_sources(relationship = Parent)] +/// #[relationship_target(relationship = Parent)] /// pub struct Children(Vec); /// ``` /// -/// When deriving [`RelationshipSources`] you can specify the `#[relationship_sources(despawn_descendants)]` attribute to -/// automatically despawn entities stored in an entity's [`RelationshipSources`] when that entity is despawned: +/// 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_sources = Children)] +/// #[relationship(relationship_target = Children)] /// pub struct Parent(pub Entity); /// /// #[derive(Component)] -/// #[relationship_sources(relationship = Parent, despawn_descendants)] +/// #[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 RelationshipSources: RelationshipSources; + type RelationshipTarget: RelationshipTarget; /// Gets the [`Entity`] ID of the related entity. fn get(&self) -> Entity; @@ -70,31 +70,30 @@ pub trait Relationship: Component + Sized { /// Creates this [`Relationship`] from the given `entity`. fn from(entity: Entity) -> Self; - /// The `on_insert` component hook that maintains the [`Relationship`] / [`RelationshipSources`] connection. + /// The `on_insert` component hook that maintains the [`Relationship`] / [`RelationshipTarget`] connection. fn on_insert(mut world: DeferredWorld, entity: Entity, _: ComponentId) { - let parent = world.entity(entity).get::().unwrap().get(); - if parent == entity { + let target_entity = world.entity(entity).get::().unwrap().get(); + if target_entity == entity { warn!( - "The {}({parent:?}) relationship on entity {entity:?} points to itself. The invalid {} relationship has been removed.", + "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 parent_entity) = world.get_entity_mut(parent) { - if let Some(mut relationship_sources) = - parent_entity.get_mut::() + 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_sources.collection_mut_risky().add(entity); + relationship_target.collection_mut_risky().add(entity); } else { - let mut sources = - ::with_capacity(1); - sources.collection_mut_risky().add(entity); - world.commands().entity(parent).insert(sources); + let mut target = ::with_capacity(1); + target.collection_mut_risky().add(entity); + world.commands().entity(target_entity).insert(target); } } else { warn!( - "The {}({parent:?}) relationship on entity {entity:?} relates to an entity that does not exist. The invalid {} relationship has been removed.", + "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::() ); @@ -102,18 +101,18 @@ pub trait Relationship: Component + Sized { } } - /// The `on_replace` component hook that maintains the [`Relationship`] / [`RelationshipSources`] connection. + /// 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 parent = world.entity(entity).get::().unwrap().get(); - if let Ok(mut parent_entity) = world.get_entity_mut(parent) { - if let Some(mut relationship_sources) = - parent_entity.get_mut::() + 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_sources.collection_mut_risky().remove(entity); - if relationship_sources.len() == 0 { - if let Some(mut entity) = world.commands().get_entity(parent) { - entity.remove::(); + relationship_target.collection_mut_risky().remove(entity); + if relationship_target.len() == 0 { + if let Some(mut entity) = world.commands().get_entity(target_entity) { + entity.remove::(); } } } @@ -123,39 +122,39 @@ pub trait Relationship: Component + Sized { /// 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 RelationshipSources: Component + Sized { - /// The [`Relationship`] that populates this [`RelationshipSources`] collection. - type Relationship: Relationship; - /// The collection type that stores the "source" entities for this [`RelationshipSources`] component. +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 [`RelationshipSources::Collection`]. + /// Returns a reference to the stored [`RelationshipTarget::Collection`]. fn collection(&self) -> &Self::Collection; - /// Returns a mutable reference to the stored [`RelationshipSources::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. /// This uses the "deprecated" state to warn users about this. fn collection_mut_risky(&mut self) -> &mut Self::Collection; - /// Creates a new [`RelationshipSources`] from the given [`RelationshipSources::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. /// This uses the "deprecated" state to warn users about this. fn from_collection_risky(collection: Self::Collection) -> Self; - /// The `on_replace` component hook that maintains the [`Relationship`] / [`RelationshipSources`] connection. + /// 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 RelationshipSources collection + // copying the RelationshipTarget collection // SAFETY: This only reads the Self component and queues Remove commands unsafe { let world = world.as_unsafe_world_cell(); - let sources = world.get_entity(entity).unwrap().get::().unwrap(); + let relationship_target = world.get_entity(entity).unwrap().get::().unwrap(); let mut commands = world.get_raw_command_queue(); - for source_entity in sources.iter() { + for source_entity in relationship_target.iter() { if world.get_entity(source_entity).is_some() { commands.push( entity_command::remove::() @@ -169,18 +168,18 @@ pub trait RelationshipSources: Component + Sized { } } - /// The `on_despawn` component hook that despawns entities stored in an entity's [`RelationshipSources`] when + /// 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 RelationshipSources collection + // copying the RelationshipTarget collection // SAFETY: This only reads the Self component and queues despawn commands unsafe { let world = world.as_unsafe_world_cell(); - let sources = world.get_entity(entity).unwrap().get::().unwrap(); + let relationship_target = world.get_entity(entity).unwrap().get::().unwrap(); let mut commands = world.get_raw_command_queue(); - for source_entity in sources.iter() { + for source_entity in relationship_target.iter() { if world.get_entity(source_entity).is_some() { commands.push( entity_command::despawn() @@ -194,7 +193,7 @@ pub trait RelationshipSources: Component + Sized { } } - /// Creates this [`RelationshipSources`] with the given pre-allocated entity capacity. + /// Creates this [`RelationshipTarget`] with the given pre-allocated entity capacity. fn with_capacity(capacity: usize) -> Self { let collection = ::with_capacity(capacity); @@ -230,11 +229,11 @@ mod tests { #[test] fn custom_relationship() { #[derive(Component)] - #[relationship(relationship_sources = LikedBy)] + #[relationship(relationship_target = LikedBy)] struct Likes(pub Entity); #[derive(Component)] - #[relationship_sources(relationship = Likes)] + #[relationship_target(relationship = Likes)] struct LikedBy(Vec); let mut world = World::new(); diff --git a/crates/bevy_ecs/src/relationship/related_methods.rs b/crates/bevy_ecs/src/relationship/related_methods.rs index 961aea9bbf367..4b42709384d1d 100644 --- a/crates/bevy_ecs/src/relationship/related_methods.rs +++ b/crates/bevy_ecs/src/relationship/related_methods.rs @@ -1,7 +1,7 @@ use crate::{ bundle::Bundle, entity::Entity, - relationship::{Relationship, RelationshipSources}, + relationship::{Relationship, RelationshipTarget}, system::{Commands, EntityCommands}, world::{EntityWorldMut, World}, }; @@ -31,9 +31,9 @@ impl<'w> EntityWorldMut<'w> { self } - /// Despawns entities that relate to this one via the given [`RelationshipSources`]. + /// 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 { + pub fn despawn_related(&mut self) -> &mut Self { if let Some(sources) = self.take::() { self.world_scope(|world| { for entity in sources.iter() { @@ -70,9 +70,9 @@ impl<'a> EntityCommands<'a> { self } - /// Despawns entities that relate to this one via the given [`RelationshipSources`]. + /// 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 { + 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::(); diff --git a/crates/bevy_ecs/src/relationship/relationship_query.rs b/crates/bevy_ecs/src/relationship/relationship_query.rs index 5a7a33cd27f6f..f47b6c14caa14 100644 --- a/crates/bevy_ecs/src/relationship/relationship_query.rs +++ b/crates/bevy_ecs/src/relationship/relationship_query.rs @@ -1,7 +1,7 @@ use crate::{ entity::Entity, query::{QueryData, QueryFilter, WorldQuery}, - relationship::{Relationship, RelationshipSources}, + relationship::{Relationship, RelationshipTarget}, system::Query, }; use alloc::collections::VecDeque; @@ -17,9 +17,9 @@ impl<'w, 's, D: QueryData, F: QueryFilter> Query<'w, 's, D, F> { self.get(entity).map(R::get).ok() } - /// If the given `entity` contains the `S` [`RelationshipSources`] component, returns the + /// If the given `entity` contains the `S` [`RelationshipTarget`] component, returns the /// source entities stored on that component. - pub fn relationship_sources( + pub fn relationship_sources( &'w self, entity: Entity, ) -> impl Iterator + 'w @@ -28,7 +28,7 @@ impl<'w, 's, D: QueryData, F: QueryFilter> Query<'w, 's, D, F> { { self.get(entity) .into_iter() - .flat_map(RelationshipSources::iter) + .flat_map(RelationshipTarget::iter) } /// Recursively walks up the tree defined by the given `R` [`Relationship`] until @@ -48,12 +48,12 @@ impl<'w, 's, D: QueryData, F: QueryFilter> Query<'w, 's, D, F> { } } - /// Iterates all "leaf entities" as defined by the [`RelationshipSources`] hierarchy. + /// 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( + pub fn iter_leaves( &'w self, entity: Entity, ) -> impl Iterator + 'w @@ -75,7 +75,7 @@ impl<'w, 's, D: QueryData, F: QueryFilter> Query<'w, 's, D, F> { entity: Entity, ) -> impl Iterator + 'w where - D::ReadOnly: WorldQuery = (Option<&'w R>, Option<&'w R::RelationshipSources>)>, + D::ReadOnly: WorldQuery = (Option<&'w R>, Option<&'w R::RelationshipTarget>)>, { self.get(entity) .ok() @@ -86,13 +86,13 @@ impl<'w, 's, D: QueryData, F: QueryFilter> Query<'w, 's, D, F> { .flat_map(move |children| children.iter().filter(move |child| *child != entity)) } - /// Iterates all descendant entities as defined by the given `entity`'s [`RelationshipSources`] and their recursive - /// [`RelationshipSources`]. + /// 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( + pub fn iter_descendants( &'w self, entity: Entity, ) -> DescendantIter<'w, 's, D, F, S> @@ -102,13 +102,13 @@ impl<'w, 's, D: QueryData, F: QueryFilter> Query<'w, 's, D, F> { DescendantIter::new(self, entity) } - /// Iterates all descendant entities as defined by the given `entity`'s [`RelationshipSources`] and their recursive - /// [`RelationshipSources`] in depth-first order. + /// 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( + pub fn iter_descendants_depth_first( &'w self, entity: Entity, ) -> DescendantDepthFirstIter<'w, 's, D, F, S> @@ -137,7 +137,7 @@ impl<'w, 's, D: QueryData, F: QueryFilter> Query<'w, 's, D, F> { /// 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: RelationshipSources> +pub struct DescendantIter<'w, 's, D: QueryData, F: QueryFilter, S: RelationshipTarget> where D::ReadOnly: WorldQuery = &'w S>, { @@ -145,7 +145,7 @@ where vecdeque: VecDeque, } -impl<'w, 's, D: QueryData, F: QueryFilter, S: RelationshipSources> DescendantIter<'w, 's, D, F, S> +impl<'w, 's, D: QueryData, F: QueryFilter, S: RelationshipTarget> DescendantIter<'w, 's, D, F, S> where D::ReadOnly: WorldQuery = &'w S>, { @@ -156,13 +156,13 @@ where vecdeque: children_query .get(entity) .into_iter() - .flat_map(RelationshipSources::iter) + .flat_map(RelationshipTarget::iter) .collect(), } } } -impl<'w, 's, D: QueryData, F: QueryFilter, S: RelationshipSources> Iterator +impl<'w, 's, D: QueryData, F: QueryFilter, S: RelationshipTarget> Iterator for DescendantIter<'w, 's, D, F, S> where D::ReadOnly: WorldQuery = &'w S>, @@ -183,7 +183,7 @@ where /// 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: RelationshipSources> +pub struct DescendantDepthFirstIter<'w, 's, D: QueryData, F: QueryFilter, S: RelationshipTarget> where D::ReadOnly: WorldQuery = &'w S>, { @@ -191,7 +191,7 @@ where stack: SmallVec<[Entity; 8]>, } -impl<'w, 's, D: QueryData, F: QueryFilter, S: RelationshipSources> +impl<'w, 's, D: QueryData, F: QueryFilter, S: RelationshipTarget> DescendantDepthFirstIter<'w, 's, D, F, S> where D::ReadOnly: WorldQuery = &'w S>, @@ -207,7 +207,7 @@ where } } -impl<'w, 's, D: QueryData, F: QueryFilter, S: RelationshipSources> Iterator +impl<'w, 's, D: QueryData, F: QueryFilter, S: RelationshipTarget> Iterator for DescendantDepthFirstIter<'w, 's, D, F, S> where D::ReadOnly: WorldQuery = &'w S>, diff --git a/crates/bevy_ecs/src/relationship/relationship_source_collection.rs b/crates/bevy_ecs/src/relationship/relationship_source_collection.rs index 02a7fe7db166f..0158f05f4b457 100644 --- a/crates/bevy_ecs/src/relationship/relationship_source_collection.rs +++ b/crates/bevy_ecs/src/relationship/relationship_source_collection.rs @@ -1,7 +1,7 @@ use crate::entity::Entity; use alloc::vec::Vec; -/// The internal [`Entity`] collection used by a [`RelationshipSources`](crate::relationship::RelationshipSources) component. +/// 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`. diff --git a/crates/bevy_ecs/src/system/commands/entity_command.rs b/crates/bevy_ecs/src/system/commands/entity_command.rs index f163e9d56ee9e..eea85ba4e48c5 100644 --- a/crates/bevy_ecs/src/system/commands/entity_command.rs +++ b/crates/bevy_ecs/src/system/commands/entity_command.rs @@ -254,7 +254,7 @@ pub fn retain() -> impl EntityCommand { /// /// # Note /// -/// This will also despawn any [`Children`](crate::hierarchy::Children) entities, and any other [`RelationshipSources`](crate::relationship::RelationshipSources) that are configured +/// 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")] diff --git a/crates/bevy_ecs/src/system/commands/mod.rs b/crates/bevy_ecs/src/system/commands/mod.rs index cac6c7f276c37..d68c1f5a6769c 100644 --- a/crates/bevy_ecs/src/system/commands/mod.rs +++ b/crates/bevy_ecs/src/system/commands/mod.rs @@ -1699,7 +1699,7 @@ impl<'a> EntityCommands<'a> { /// /// # Note /// - /// This will also despawn the entities in any [`RelationshipSources`](crate::relationship::RelationshipSources) that are configured + /// 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 @@ -1739,7 +1739,7 @@ impl<'a> EntityCommands<'a> { /// /// # Note /// - /// This will also despawn the entities in any [`RelationshipSources`](crate::relationship::RelationshipSources) that are configured + /// 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/world/entity_ref.rs b/crates/bevy_ecs/src/world/entity_ref.rs index 09adcea53268b..66018f7a4e476 100644 --- a/crates/bevy_ecs/src/world/entity_ref.rs +++ b/crates/bevy_ecs/src/world/entity_ref.rs @@ -2098,7 +2098,7 @@ impl<'w> EntityWorldMut<'w> { /// /// # Note /// - /// This will also despawn any [`Children`](crate::hierarchy::Children) entities, and any other [`RelationshipSources`](crate::relationship::RelationshipSources) that are configured + /// 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 diff --git a/crates/bevy_ecs/src/world/mod.rs b/crates/bevy_ecs/src/world/mod.rs index 26cba3d168eac..1bac1df44dcd7 100644 --- a/crates/bevy_ecs/src/world/mod.rs +++ b/crates/bevy_ecs/src/world/mod.rs @@ -1276,7 +1276,7 @@ impl World { /// /// # Note /// - /// This will also despawn the entities in any [`RelationshipSources`](crate::relationship::RelationshipSources) that are configured + /// 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). /// /// ``` From 966ebca6e50a002690498459ead2fa77d963906a Mon Sep 17 00:00:00 2001 From: Carter Anderson Date: Fri, 17 Jan 2025 15:57:34 -0800 Subject: [PATCH 29/38] Fix doc link --- crates/bevy_ecs/src/traversal.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/bevy_ecs/src/traversal.rs b/crates/bevy_ecs/src/traversal.rs index 3156d2462efa2..342ad47849e06 100644 --- a/crates/bevy_ecs/src/traversal.rs +++ b/crates/bevy_ecs/src/traversal.rs @@ -37,7 +37,7 @@ impl Traversal for () { /// /// Traversing in a loop could result in infinite loops for relationship graphs with loops. /// -/// [event propagation]: bevy_ecs::observer::Trigger::propagate +/// [event propagation]: crate::observer::Trigger::propagate impl Traversal for &R { fn traverse(item: Self::Item<'_>, _data: &D) -> Option { Some(item.get()) From ec626d6dbf73a9fdc33b0482331f4e62e8f1d1ff Mon Sep 17 00:00:00 2001 From: Carter Anderson Date: Fri, 17 Jan 2025 19:10:07 -0800 Subject: [PATCH 30/38] Fix bug that occurs when inserting the same Parent on top of itself. --- crates/bevy_app/src/app.rs | 2 ++ crates/bevy_ecs/src/hierarchy.rs | 15 ++++++++++++++- crates/bevy_ecs/src/relationship/mod.rs | 14 ++++++++++++-- 3 files changed, 28 insertions(+), 3 deletions(-) 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_ecs/src/hierarchy.rs b/crates/bevy_ecs/src/hierarchy.rs index e5a62d2f882a5..dd82d4bed00f1 100644 --- a/crates/bevy_ecs/src/hierarchy.rs +++ b/crates/bevy_ecs/src/hierarchy.rs @@ -139,7 +139,7 @@ impl FromWorld for Parent { /// description of this relationship and instructions on how to use it. #[derive(Component, Default, Reflect, VisitEntitiesMut, Debug, PartialEq, Eq)] #[relationship_target(relationship = Parent, despawn_descendants)] -#[reflect(Component, MapEntities, VisitEntities, VisitEntitiesMut)] +#[reflect(Component, MapEntities, VisitEntities, VisitEntitiesMut, FromWorld)] pub struct Children(Vec); impl<'a> IntoIterator for &'a Children { @@ -432,4 +432,17 @@ mod tests { "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/relationship/mod.rs b/crates/bevy_ecs/src/relationship/mod.rs index 16ed8cab7a5d3..cd4e22b369fbd 100644 --- a/crates/bevy_ecs/src/relationship/mod.rs +++ b/crates/bevy_ecs/src/relationship/mod.rs @@ -16,7 +16,7 @@ use crate::{ entity_command::{self, CommandWithEntity}, error_handler, }, - world::DeferredWorld, + world::{DeferredWorld, EntityWorldMut}, }; use log::warn; @@ -112,7 +112,17 @@ pub trait Relationship: Component + Sized { relationship_target.collection_mut_risky().remove(entity); if relationship_target.len() == 0 { if let Some(mut entity) = world.commands().get_entity(target_entity) { - entity.remove::(); + // 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::() + .map_or(false, |t| t.is_empty()) + { + entity.remove::(); + } + }); } } } From 585199398961fb1d2847985f719c169f31b6252f Mon Sep 17 00:00:00 2001 From: Carter Anderson Date: Fri, 17 Jan 2025 19:50:11 -0800 Subject: [PATCH 31/38] Fix test and clippy --- crates/bevy_ecs/src/relationship/mod.rs | 2 +- crates/bevy_transform/src/systems.rs | 25 +++++++++++++++++-------- 2 files changed, 18 insertions(+), 9 deletions(-) diff --git a/crates/bevy_ecs/src/relationship/mod.rs b/crates/bevy_ecs/src/relationship/mod.rs index cd4e22b369fbd..93e8508710095 100644 --- a/crates/bevy_ecs/src/relationship/mod.rs +++ b/crates/bevy_ecs/src/relationship/mod.rs @@ -118,7 +118,7 @@ pub trait Relationship: Component + Sized { entity.queue(|mut entity: EntityWorldMut| { if entity .get::() - .map_or(false, |t| t.is_empty()) + .is_some_and(RelationshipTarget::is_empty) { entity.remove::(); } diff --git a/crates/bevy_transform/src/systems.rs b/crates/bevy_transform/src/systems.rs index 6257f2cfcb1ed..898befe2963a4 100644 --- a/crates/bevy_transform/src/systems.rs +++ b/crates/bevy_transform/src/systems.rs @@ -453,14 +453,23 @@ mod test { app.world_mut() .spawn(Transform::IDENTITY) .add_children(&[child]); - let grandchild_parent = app.world().entity(grandchild).get::().unwrap().0; - let child_parent = app.world().entity(child).get::().unwrap().0; - app.world_mut() - .entity_mut(child) - .insert(Parent(grandchild_parent)); - app.world_mut() - .entity_mut(grandchild) - .insert(Parent(child_parent)); + core::mem::swap( + #[allow(unsafe_code)] + unsafe { + &mut *app + .world_mut() + .entity_mut(child) + .get_mut_assume_mutable::() + .unwrap() + }, + #[allow(unsafe_code)] + unsafe { + &mut *temp + .entity_mut(grandchild) + .get_mut_assume_mutable::() + .unwrap() + }, + ); app.update(); } From 874a310a0616bbe1e816a61e5fe7ed70c97be4c4 Mon Sep 17 00:00:00 2001 From: Carter Anderson Date: Fri, 17 Jan 2025 20:03:29 -0800 Subject: [PATCH 32/38] taplo fmt --- crates/bevy_state/Cargo.toml | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/crates/bevy_state/Cargo.toml b/crates/bevy_state/Cargo.toml index c9b81617916e9..18f75afec8d0c 100644 --- a/crates/bevy_state/Cargo.toml +++ b/crates/bevy_state/Cargo.toml @@ -28,12 +28,7 @@ bevy_app = ["dep:bevy_app"] ## 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", -] +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`. From 504b1058ddbae7fdce17618d5e3626056317a6ea Mon Sep 17 00:00:00 2001 From: Carter Anderson Date: Fri, 17 Jan 2025 20:10:56 -0800 Subject: [PATCH 33/38] Clippy --- crates/bevy_transform/src/systems.rs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/crates/bevy_transform/src/systems.rs b/crates/bevy_transform/src/systems.rs index 898befe2963a4..457651dad6bba 100644 --- a/crates/bevy_transform/src/systems.rs +++ b/crates/bevy_transform/src/systems.rs @@ -454,7 +454,8 @@ mod test { .spawn(Transform::IDENTITY) .add_children(&[child]); core::mem::swap( - #[allow(unsafe_code)] + #[expect(unsafe_code)] + // SAFETY: Parent is not mutable but this is for a test to produce a scenario that cannot happen unsafe { &mut *app .world_mut() @@ -462,7 +463,8 @@ mod test { .get_mut_assume_mutable::() .unwrap() }, - #[allow(unsafe_code)] + // SAFETY: Parent is not mutable but this is for a test to produce a scenario that cannot happen + #[expect(unsafe_code)] unsafe { &mut *temp .entity_mut(grandchild) From 626d35a4965974be66081f8d6cdfc28d7cd29046 Mon Sep 17 00:00:00 2001 From: Carter Anderson Date: Fri, 17 Jan 2025 20:18:14 -0800 Subject: [PATCH 34/38] - ___ - --- crates/bevy_transform/src/systems.rs | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/crates/bevy_transform/src/systems.rs b/crates/bevy_transform/src/systems.rs index 457651dad6bba..1db3be25a1263 100644 --- a/crates/bevy_transform/src/systems.rs +++ b/crates/bevy_transform/src/systems.rs @@ -454,7 +454,10 @@ mod test { .spawn(Transform::IDENTITY) .add_children(&[child]); core::mem::swap( - #[expect(unsafe_code)] + #[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 @@ -464,7 +467,10 @@ mod test { .unwrap() }, // SAFETY: Parent is not mutable but this is for a test to produce a scenario that cannot happen - #[expect(unsafe_code)] + #[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) From 3799b5756c701ba60ccbe38739be3f6936c9f810 Mon Sep 17 00:00:00 2001 From: Carter Anderson Date: Fri, 17 Jan 2025 20:40:22 -0800 Subject: [PATCH 35/38] Thank you miri <3 --- crates/bevy_ecs/src/bundle.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) 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(); From 7865248178f1f3a23c7f6ae8ac83cc43ef428632 Mon Sep 17 00:00:00 2001 From: Carter Anderson Date: Fri, 17 Jan 2025 20:52:49 -0800 Subject: [PATCH 36/38] Feature gate bevy_reflect --- crates/bevy_ecs/src/hierarchy.rs | 54 ++++++++++++++++++-------------- 1 file changed, 30 insertions(+), 24 deletions(-) diff --git a/crates/bevy_ecs/src/hierarchy.rs b/crates/bevy_ecs/src/hierarchy.rs index dd82d4bed00f1..6e495a116b7f9 100644 --- a/crates/bevy_ecs/src/hierarchy.rs +++ b/crates/bevy_ecs/src/hierarchy.rs @@ -6,24 +6,22 @@ //! [`Relationship`]: crate::relationship::Relationship //! [`RelationshipTarget`]: crate::relationship::RelationshipTarget -use crate as bevy_ecs; -use crate::bundle::Bundle; -use crate::component::ComponentId; -use crate::relationship::{RelatedSpawner, RelatedSpawnerCommands}; -use crate::system::EntityCommands; -use crate::world::{DeferredWorld, EntityWorldMut}; +#[cfg(feature = "bevy_reflect")] +use crate::reflect::{ + ReflectComponent, ReflectFromWorld, ReflectMapEntities, ReflectVisitEntities, + ReflectVisitEntitiesMut, +}; use crate::{ - component::Component, + self as bevy_ecs, + bundle::Bundle, + component::{Component, ComponentId}, entity::{Entity, VisitEntities}, - reflect::{ - ReflectComponent, ReflectFromWorld, ReflectMapEntities, ReflectVisitEntities, - ReflectVisitEntitiesMut, - }, - world::{FromWorld, World}, + relationship::{RelatedSpawner, RelatedSpawnerCommands}, + system::EntityCommands, + world::{DeferredWorld, EntityWorldMut, FromWorld, World}, }; use alloc::{format, string::String, vec::Vec}; use bevy_ecs_macros::VisitEntitiesMut; -use bevy_reflect::Reflect; use core::ops::Deref; use core::slice; use disqualified::ShortName; @@ -92,15 +90,19 @@ use log::warn; /// assert_eq!(&**world.entity(root).get::().unwrap(), &[child1, child2]); /// assert_eq!(&**world.entity(child1).get::().unwrap(), &[grandchild]); /// ``` -#[derive(Component, Clone, Reflect, VisitEntities, VisitEntitiesMut, PartialEq, Eq, Debug)] -#[reflect( - Component, - MapEntities, - VisitEntities, - VisitEntitiesMut, - PartialEq, - Debug, - FromWorld +#[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); @@ -137,9 +139,13 @@ impl FromWorld for Parent { /// /// 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, Reflect, VisitEntitiesMut, Debug, PartialEq, Eq)] +#[derive(Component, Default, VisitEntitiesMut, Debug, PartialEq, Eq)] #[relationship_target(relationship = Parent, despawn_descendants)] -#[reflect(Component, MapEntities, VisitEntities, VisitEntitiesMut, FromWorld)] +#[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 { From da8874c4530d62dd922eca73e731f163021b2618 Mon Sep 17 00:00:00 2001 From: Carter Anderson Date: Fri, 17 Jan 2025 21:01:47 -0800 Subject: [PATCH 37/38] More optional reflection --- crates/bevy_transform/src/plugins.rs | 13 +++---------- 1 file changed, 3 insertions(+), 10 deletions(-) diff --git a/crates/bevy_transform/src/plugins.rs b/crates/bevy_transform/src/plugins.rs index a2f951fea164b..36ea08ef848a7 100644 --- a/crates/bevy_transform/src/plugins.rs +++ b/crates/bevy_transform/src/plugins.rs @@ -1,14 +1,7 @@ +use crate::systems::{propagate_transforms, sync_simple_transforms}; use bevy_app::{App, Plugin, PostStartup, PostUpdate}; use bevy_ecs::schedule::{IntoSystemConfigs, IntoSystemSetConfigs, SystemSet}; -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 { @@ -28,8 +21,8 @@ impl Plugin for TransformPlugin { struct PropagateTransformsSet; #[cfg(feature = "bevy_reflect")] - app.register_type::() - .register_type::(); + app.register_type::() + .register_type::(); app.configure_sets( PostStartup, From a2d0e57b0e4de49775569f24bec3a63fcb43a6bb Mon Sep 17 00:00:00 2001 From: Carter Anderson Date: Sat, 18 Jan 2025 13:30:26 -0800 Subject: [PATCH 38/38] Doc links --- crates/bevy_ecs/src/relationship/mod.rs | 2 -- crates/bevy_transform/src/commands.rs | 6 +++--- crates/bevy_transform/src/plugins.rs | 4 ++-- 3 files changed, 5 insertions(+), 7 deletions(-) diff --git a/crates/bevy_ecs/src/relationship/mod.rs b/crates/bevy_ecs/src/relationship/mod.rs index 93e8508710095..54105c0d7391d 100644 --- a/crates/bevy_ecs/src/relationship/mod.rs +++ b/crates/bevy_ecs/src/relationship/mod.rs @@ -144,14 +144,12 @@ pub trait RelationshipTarget: Component + Sized { /// /// # Warning /// This should generally not be called by user code, as modifying the internal collection could invalidate the relationship. - /// This uses the "deprecated" state to warn users about this. 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. - /// This uses the "deprecated" state to warn users about this. fn from_collection_risky(collection: Self::Collection) -> Self; /// The `on_replace` component hook that maintains the [`Relationship`] / [`RelationshipTarget`] connection. diff --git a/crates/bevy_transform/src/commands.rs b/crates/bevy_transform/src/commands.rs index f5d8dc06bcfac..a3bc817ae2f94 100644 --- a/crates/bevy_transform/src/commands.rs +++ b/crates/bevy_transform/src/commands.rs @@ -4,13 +4,13 @@ use crate::prelude::{GlobalTransform, Transform}; 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 @@ -20,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 diff --git a/crates/bevy_transform/src/plugins.rs b/crates/bevy_transform/src/plugins.rs index 36ea08ef848a7..1add9713a0be9 100644 --- a/crates/bevy_transform/src/plugins.rs +++ b/crates/bevy_transform/src/plugins.rs @@ -5,11 +5,11 @@ use bevy_ecs::schedule::{IntoSystemConfigs, IntoSystemSetConfigs, SystemSet}; /// 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;