From 21f1e3045c80230bc8c0055ba70dbf2e98d934a3 Mon Sep 17 00:00:00 2001 From: Carter Anderson Date: Sat, 18 Jan 2025 14:20:30 -0800 Subject: [PATCH] Relationships (non-fragmenting, one-to-many) (#17398) This adds support for one-to-many non-fragmenting relationships (with planned paths for fragmenting and non-fragmenting many-to-many relationships). "Non-fragmenting" means that entities with the same relationship type, but different relationship targets, are not forced into separate tables (which would cause "table fragmentation"). Functionally, this fills a similar niche as the current Parent/Children system. The biggest differences are: 1. Relationships have simpler internals and significantly improved performance and UX. Commands and specialized APIs are no longer necessary to keep everything in sync. Just spawn entities with the relationship components you want and everything "just works". 2. Relationships are generalized. Bevy can provide additional built in relationships, and users can define their own. **REQUEST TO REVIEWERS**: _please don't leave top level comments and instead comment on specific lines of code. That way we can take advantage of threaded discussions. Also dont leave comments simply pointing out CI failures as I can read those just fine._ ## Built on top of what we have Relationships are implemented on top of the Bevy ECS features we already have: components, immutability, and hooks. This makes them immediately compatible with all of our existing (and future) APIs for querying, spawning, removing, scenes, reflection, etc. The fewer specialized APIs we need to build, maintain, and teach, the better. ## Why focus on one-to-many non-fragmenting first? 1. This allows us to improve Parent/Children relationships immediately, in a way that is reasonably uncontroversial. Switching our hierarchy to fragmenting relationships would have significant performance implications. ~~Flecs is heavily considering a switch to non-fragmenting relations after careful considerations of the performance tradeoffs.~~ _(Correction from @SanderMertens: Flecs is implementing non-fragmenting storage specialized for asset hierarchies, where asset hierarchies are many instances of small trees that have a well defined structure)_ 2. Adding generalized one-to-many relationships is currently a priority for the [Next Generation Scene / UI effort](https://github.com/bevyengine/bevy/discussions/14437). Specifically, we're interested in building reactions and observers on top. ## The changes This PR does the following: 1. Adds a generic one-to-many Relationship system 3. Ports the existing Parent/Children system to Relationships, which now lives in `bevy_ecs::hierarchy`. The old `bevy_hierarchy` crate has been removed. 4. Adds on_despawn component hooks 5. Relationships can opt-in to "despawn descendants" behavior, meaning that the entire relationship hierarchy is despawned when `entity.despawn()` is called. The built in Parent/Children hierarchies enable this behavior, and `entity.despawn_recursive()` has been removed. 6. `world.spawn` now applies commands after spawning. This ensures that relationship bookkeeping happens immediately and removes the need to manually flush. This is in line with the equivalent behaviors recently added to the other APIs (ex: insert). 7. Removes the ValidParentCheckPlugin (system-driven / poll based) in favor of a `validate_parent_has_component` hook. ## Using Relationships The `Relationship` trait looks like this: ```rust pub trait Relationship: Component + Sized { type RelationshipSources: RelationshipSources; fn get(&self) -> Entity; fn from(entity: Entity) -> Self; } ``` A relationship is a component that: 1. Is a simple wrapper over a "target" Entity. 2. Has a corresponding `RelationshipSources` component, which is a simple wrapper over a collection of entities. Every "target entity" targeted by a "source entity" with a `Relationship` has a `RelationshipSources` component, which contains every "source entity" that targets it. For example, the `Parent` component (as it currently exists in Bevy) is the `Relationship` component and the entity containing the Parent is the "source entity". The entity _inside_ the `Parent(Entity)` component is the "target entity". And that target entity has a `Children` component (which implements `RelationshipSources`). In practice, the Parent/Children relationship looks like this: ```rust #[derive(Relationship)] #[relationship(relationship_sources = Children)] pub struct Parent(pub Entity); #[derive(RelationshipSources)] #[relationship_sources(relationship = Parent)] pub struct Children(Vec); ``` The Relationship and RelationshipSources derives automatically implement Component with the relevant configuration (namely, the hooks necessary to keep everything in sync). The most direct way to add relationships is to spawn entities with relationship components: ```rust let a = world.spawn_empty().id(); let b = world.spawn(Parent(a)).id(); assert_eq!(world.entity(a).get::().unwrap(), &[b]); ``` There are also convenience APIs for spawning more than one entity with the same relationship: ```rust world.spawn_empty().with_related::(|s| { s.spawn_empty(); s.spawn_empty(); }) ``` The existing `with_children` API is now a simpler wrapper over `with_related`. This makes this change largely non-breaking for existing spawn patterns. ```rust world.spawn_empty().with_children(|s| { s.spawn_empty(); s.spawn_empty(); }) ``` There are also other relationship APIs, such as `add_related` and `despawn_related`. ## Automatic recursive despawn via the new on_despawn hook `RelationshipSources` can opt-in to "despawn descendants" behavior, which will despawn all related entities in the relationship hierarchy: ```rust #[derive(RelationshipSources)] #[relationship_sources(relationship = Parent, despawn_descendants)] pub struct Children(Vec); ``` This means that `entity.despawn_recursive()` is no longer required. Instead, just use `entity.despawn()` and the relevant related entities will also be despawned. To despawn an entity _without_ despawning its parent/child descendants, you should remove the `Children` component first, which will also remove the related `Parent` components: ```rust entity .remove::() .despawn() ``` This builds on the on_despawn hook introduced in this PR, which is fired when an entity is despawned (before other hooks). ## Relationships are the source of truth `Relationship` is the _single_ source of truth component. `RelationshipSources` is merely a reflection of what all the `Relationship` components say. By embracing this, we are able to significantly improve the performance of the system as a whole. We can rely on component lifecycles to protect us against duplicates, rather than needing to scan at runtime to ensure entities don't already exist (which results in quadratic runtime). A single source of truth gives us constant-time inserts. This does mean that we cannot directly spawn populated `Children` components (or directly add or remove entities from those components). I personally think this is a worthwhile tradeoff, both because it makes the performance much better _and_ because it means theres exactly one way to do things (which is a philosophy we try to employ for Bevy APIs). As an aside: treating both sides of the relationship as "equivalent source of truth relations" does enable building simple and flexible many-to-many relationships. But this introduces an _inherent_ need to scan (or hash) to protect against duplicates. [`evergreen_relations`](https://github.com/EvergreenNest/evergreen_relations) has a very nice implementation of the "symmetrical many-to-many" approach. Unfortunately I think the performance issues inherent to that approach make it a poor choice for Bevy's default relationship system. ## Followup Work * Discuss renaming `Parent` to `ChildOf`. I refrained from doing that in this PR to keep the diff reasonable, but I'm personally biased toward this change (and using that naming pattern generally for relationships). * [Improved spawning ergonomics](https://github.com/bevyengine/bevy/discussions/16920) * Consider adding relationship observers/triggers for "relationship targets" whenever a source is added or removed. This would replace the current "hierarchy events" system, which is unused upstream but may have existing users downstream. I think triggers are the better fit for this than a buffered event queue, and would prefer not to add that back. * Fragmenting relations: My current idea hinges on the introduction of "value components" (aka: components whose type _and_ value determines their ComponentId, via something like Hashing / PartialEq). By labeling a Relationship component such as `ChildOf(Entity)` as a "value component", `ChildOf(e1)` and `ChildOf(e2)` would be considered "different components". This makes the transition between fragmenting and non-fragmenting a single flag, and everything else continues to work as expected. * Many-to-many support * Non-fragmenting: We can expand Relationship to be a list of entities instead of a single entity. I have largely already written the code for this. * Fragmenting: With the "value component" impl mentioned above, we get many-to-many support "for free", as it would allow inserting multiple copies of a Relationship component with different target entities. Fixes #3742 (If this PR is merged, I think we should open more targeted followup issues for the work above, with a fresh tracking issue free of the large amount of less-directed historical context) Fixes #17301 Fixes #12235 Fixes #15299 Fixes #15308 ## Migration Guide * Replace `ChildBuilder` with `ChildSpawnerCommands`. * Replace calls to `.set_parent(parent_id)` with `.insert(Parent(parent_id))`. * Replace calls to `.replace_children()` with `.remove::()` followed by `.add_children()`. Note that you'll need to manually despawn any children that are not carried over. * Replace calls to `.despawn_recursive()` with `.despawn()`. * Replace calls to `.despawn_descendants()` with `.despawn_related::()`. * If you have any calls to `.despawn()` which depend on the children being preserved, you'll need to remove the `Children` component first. --------- Co-authored-by: Alice Cecile --- 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_app/src/app.rs | 2 + 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/macros/src/component.rs | 281 +++- crates/bevy_ecs/macros/src/lib.rs | 2 +- crates/bevy_ecs/src/archetype.rs | 24 +- crates/bevy_ecs/src/bundle.rs | 3 +- crates/bevy_ecs/src/component.rs | 27 + crates/bevy_ecs/src/entity/clone_entities.rs | 54 +- crates/bevy_ecs/src/hierarchy.rs | 454 ++++++ crates/bevy_ecs/src/lib.rs | 3 + crates/bevy_ecs/src/observer/mod.rs | 15 +- crates/bevy_ecs/src/reflect/component.rs | 18 +- crates/bevy_ecs/src/relationship/mod.rs | 253 ++++ .../src/relationship/related_methods.rs | 164 +++ .../src/relationship/relationship_query.rs | 261 ++++ .../relationship_source_collection.rs | 51 + .../src/system/commands/entity_command.rs | 4 +- crates/bevy_ecs/src/system/commands/mod.rs | 17 +- crates/bevy_ecs/src/traversal.rs | 15 +- .../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 | 34 +- crates/bevy_ecs/src/world/mod.rs | 36 +- 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 | 1222 ----------------- .../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 | 505 ------- 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 | 10 +- 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 | 9 +- crates/bevy_render/src/view/visibility/mod.rs | 13 +- .../bevy_render/src/view/window/screenshot.rs | 5 +- crates/bevy_scene/Cargo.toml | 1 - crates/bevy_scene/src/dynamic_scene.rs | 4 +- crates/bevy_scene/src/scene_spawner.rs | 12 +- crates/bevy_state/Cargo.toml | 17 +- crates/bevy_state/src/state_scoped.rs | 9 - 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 | 13 +- .../src/components/global_transform.rs | 11 +- .../src/components/transform.rs | 2 +- crates/bevy_transform/src/helper.rs | 7 +- crates/bevy_transform/src/plugins.rs | 79 +- crates/bevy_transform/src/systems.rs | 45 +- crates/bevy_ui/Cargo.toml | 1 - .../src/experimental/ghost_hierarchy.rs | 9 +- crates/bevy_ui/src/layout/mod.rs | 26 +- crates/bevy_ui/src/stack.rs | 1 - crates/bevy_winit/Cargo.toml | 1 - crates/bevy_winit/src/accessibility.rs | 10 +- examples/3d/color_grading.rs | 8 +- examples/3d/mixed_lighting.rs | 2 +- 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 +- tools/ci/src/commands/compile_check_no_std.rs | 10 +- tools/publish.sh | 1 - 114 files changed, 1889 insertions(+), 3108 deletions(-) create mode 100644 crates/bevy_ecs/src/hierarchy.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 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 1e55f712a7..ce2467b022 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 80577b9a9d..5558b60069 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 1989c05fc6..b4560895c0 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 482086ab17..dd1ca4325b 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 8dfa704b8d..36091c8db3 100644 --- a/crates/bevy_animation/Cargo.toml +++ b/crates/bevy_animation/Cargo.toml @@ -25,7 +25,6 @@ bevy_time = { path = "../bevy_time", version = "0.16.0-dev" } bevy_utils = { path = "../bevy_utils", version = "0.16.0-dev" } bevy_ecs = { path = "../bevy_ecs", version = "0.16.0-dev" } bevy_transform = { path = "../bevy_transform", version = "0.16.0-dev" } -bevy_hierarchy = { path = "../bevy_hierarchy", version = "0.16.0-dev" } # other petgraph = { version = "0.6", features = ["serde-1"] } diff --git a/crates/bevy_app/src/app.rs b/crates/bevy_app/src/app.rs index 6ff5b9993d..e9889670bc 100644 --- a/crates/bevy_app/src/app.rs +++ b/crates/bevy_app/src/app.rs @@ -104,6 +104,8 @@ impl Default for App { { app.init_resource::(); app.register_type::(); + app.register_type::(); + app.register_type::(); } #[cfg(feature = "reflect_functions")] diff --git a/crates/bevy_audio/Cargo.toml b/crates/bevy_audio/Cargo.toml index 71c7efe65b..4d7967977e 100644 --- a/crates/bevy_audio/Cargo.toml +++ b/crates/bevy_audio/Cargo.toml @@ -13,7 +13,6 @@ keywords = ["bevy"] bevy_app = { path = "../bevy_app", version = "0.16.0-dev" } bevy_asset = { path = "../bevy_asset", version = "0.16.0-dev" } bevy_ecs = { path = "../bevy_ecs", version = "0.16.0-dev" } -bevy_hierarchy = { path = "../bevy_hierarchy", version = "0.16.0-dev" } bevy_math = { path = "../bevy_math", version = "0.16.0-dev" } bevy_reflect = { path = "../bevy_reflect", version = "0.16.0-dev", features = [ "bevy", diff --git a/crates/bevy_audio/src/audio_output.rs b/crates/bevy_audio/src/audio_output.rs index 90bdc38499..c098ac8382 100644 --- a/crates/bevy_audio/src/audio_output.rs +++ b/crates/bevy_audio/src/audio_output.rs @@ -4,7 +4,6 @@ use crate::{ }; use bevy_asset::{Asset, Assets}; use bevy_ecs::{prelude::*, system::SystemParam}; -use bevy_hierarchy::DespawnRecursiveExt; use bevy_math::Vec3; use bevy_transform::prelude::GlobalTransform; use rodio::{OutputStream, OutputStreamHandle, Sink, Source, SpatialSink}; @@ -253,12 +252,12 @@ pub(crate) fn cleanup_finished_audio( ) { for (entity, sink) in &query_nonspatial_despawn { if sink.sink.empty() { - commands.entity(entity).despawn_recursive(); + commands.entity(entity).despawn(); } } for (entity, sink) in &query_spatial_despawn { if sink.sink.empty() { - commands.entity(entity).despawn_recursive(); + commands.entity(entity).despawn(); } } for (entity, sink) in &query_nonspatial_remove { diff --git a/crates/bevy_dev_tools/Cargo.toml b/crates/bevy_dev_tools/Cargo.toml index 223474a0f0..0b9618d20d 100644 --- a/crates/bevy_dev_tools/Cargo.toml +++ b/crates/bevy_dev_tools/Cargo.toml @@ -18,7 +18,6 @@ bevy_asset = { path = "../bevy_asset", version = "0.16.0-dev" } bevy_color = { path = "../bevy_color", version = "0.16.0-dev" } bevy_diagnostic = { path = "../bevy_diagnostic", version = "0.16.0-dev" } bevy_ecs = { path = "../bevy_ecs", version = "0.16.0-dev" } -bevy_hierarchy = { path = "../bevy_hierarchy", version = "0.16.0-dev" } bevy_input = { path = "../bevy_input", version = "0.16.0-dev" } bevy_picking = { path = "../bevy_picking", version = "0.16.0-dev" } bevy_render = { path = "../bevy_render", version = "0.16.0-dev" } diff --git a/crates/bevy_dev_tools/src/fps_overlay.rs b/crates/bevy_dev_tools/src/fps_overlay.rs index a6368cf92d..a68324e235 100644 --- a/crates/bevy_dev_tools/src/fps_overlay.rs +++ b/crates/bevy_dev_tools/src/fps_overlay.rs @@ -12,7 +12,6 @@ use bevy_ecs::{ schedule::{common_conditions::resource_changed, IntoSystemConfigs}, system::{Commands, Query, Res, Resource}, }; -use bevy_hierarchy::{BuildChildren, ChildBuild}; use bevy_render::view::Visibility; use bevy_text::{Font, TextColor, TextFont, TextSpan}; use bevy_ui::{ diff --git a/crates/bevy_ecs/macros/src/component.rs b/crates/bevy_ecs/macros/src/component.rs index e89436e426..44c25171c2 100644 --- a/crates/bevy_ecs/macros/src/component.rs +++ b/crates/bevy_ecs/macros/src/component.rs @@ -9,7 +9,8 @@ use syn::{ punctuated::Punctuated, spanned::Spanned, token::{Comma, Paren}, - DeriveInput, ExprClosure, ExprPath, Ident, LitStr, Path, Result, + Data, DataStruct, DeriveInput, ExprClosure, ExprPath, Fields, Ident, LitStr, Path, Result, + Token, Visibility, }; pub fn derive_event(input: TokenStream) -> TokenStream { @@ -59,12 +60,81 @@ pub fn derive_component(input: TokenStream) -> TokenStream { Err(e) => return e.into_compile_error().into(), }; + let relationship = match derive_relationship(&ast, &attrs, &bevy_ecs_path) { + Ok(value) => value, + Err(err) => err.into_compile_error().into(), + }; + let relationship_target = match derive_relationship_target(&ast, &attrs, &bevy_ecs_path) { + Ok(value) => value, + Err(err) => err.into_compile_error().into(), + }; + let storage = storage_path(&bevy_ecs_path, attrs.storage); let on_add = hook_register_function_call(quote! {on_add}, attrs.on_add); - let on_insert = hook_register_function_call(quote! {on_insert}, attrs.on_insert); - let on_replace = hook_register_function_call(quote! {on_replace}, attrs.on_replace); - let on_remove = hook_register_function_call(quote! {on_remove}, attrs.on_remove); + let mut on_insert = hook_register_function_call(quote! {on_insert}, attrs.on_insert); + let mut on_replace = hook_register_function_call(quote! {on_replace}, attrs.on_replace); + let on_remove: Option = + hook_register_function_call(quote! {on_remove}, attrs.on_remove); + let mut on_despawn = hook_register_function_call(quote! {on_despawn}, attrs.on_despawn); + + if relationship.is_some() { + if on_insert.is_some() { + return syn::Error::new( + ast.span(), + "Custom on_insert hooks are not supported as relationships already define an on_insert hook", + ) + .into_compile_error() + .into(); + } + + on_insert = Some( + quote!(hooks.on_insert(::on_insert);), + ); + + if on_replace.is_some() { + return syn::Error::new( + ast.span(), + "Custom on_replace hooks are not supported as Relationships already define an on_replace hook", + ) + .into_compile_error() + .into(); + } + + on_replace = Some( + quote!(hooks.on_replace(::on_replace);), + ); + } + + if let Some(relationship_target) = &attrs.relationship_target { + if on_replace.is_some() { + return syn::Error::new( + ast.span(), + "Custom on_replace hooks are not supported as RelationshipTarget already defines an on_replace hook", + ) + .into_compile_error() + .into(); + } + + on_replace = Some( + quote!(hooks.on_replace(::on_replace);), + ); + + if relationship_target.despawn_descendants { + if on_despawn.is_some() { + return syn::Error::new( + ast.span(), + "Custom on_despawn hooks are not supported as this RelationshipTarget already defines an on_despawn hook, via the despawn_descendants attribute", + ) + .into_compile_error() + .into(); + } + + on_despawn = Some( + quote!(hooks.on_despawn(::on_despawn);), + ); + } + } ast.generics .make_where_clause() @@ -127,11 +197,19 @@ pub fn derive_component(input: TokenStream) -> TokenStream { let struct_name = &ast.ident; let (impl_generics, type_generics, where_clause) = &ast.generics.split_for_impl(); - let mutable_type = attrs - .immutable + let mutable_type = (attrs.immutable || relationship.is_some()) .then_some(quote! { #bevy_ecs_path::component::Immutable }) .unwrap_or(quote! { #bevy_ecs_path::component::Mutable }); + let clone_handler = if relationship_target.is_some() { + quote!(#bevy_ecs_path::component::ComponentCloneHandler::ignore()) + } else { + quote!( + use #bevy_ecs_path::component::{ComponentCloneViaClone, ComponentCloneBase}; + (&&&#bevy_ecs_path::component::ComponentCloneSpecializationWrapper::::default()).get_component_clone_handler() + ) + }; + // This puts `register_required` before `register_recursive_requires` to ensure that the constructors of _all_ top // level components are initialized first, giving them precedence over recursively defined constructors for the same component type TokenStream::from(quote! { @@ -160,14 +238,17 @@ pub fn derive_component(input: TokenStream) -> TokenStream { #on_insert #on_replace #on_remove + #on_despawn } fn get_component_clone_handler() -> #bevy_ecs_path::component::ComponentCloneHandler { - use #bevy_ecs_path::component::{ComponentCloneViaClone, ComponentCloneBase}; - (&&&#bevy_ecs_path::component::ComponentCloneSpecializationWrapper::::default()) - .get_component_clone_handler() + #clone_handler } } + + #relationship + + #relationship_target }) } @@ -202,11 +283,14 @@ pub fn document_required_components(attr: TokenStream, item: TokenStream) -> Tok pub const COMPONENT: &str = "component"; pub const STORAGE: &str = "storage"; pub const REQUIRE: &str = "require"; +pub const RELATIONSHIP: &str = "relationship"; +pub const RELATIONSHIP_TARGET: &str = "relationship_target"; pub const ON_ADD: &str = "on_add"; pub const ON_INSERT: &str = "on_insert"; pub const ON_REPLACE: &str = "on_replace"; pub const ON_REMOVE: &str = "on_remove"; +pub const ON_DESPAWN: &str = "on_despawn"; pub const IMMUTABLE: &str = "immutable"; @@ -217,6 +301,9 @@ struct Attrs { on_insert: Option, on_replace: Option, on_remove: Option, + on_despawn: Option, + relationship: Option, + relationship_target: Option, immutable: bool, } @@ -236,6 +323,15 @@ enum RequireFunc { Closure(ExprClosure), } +struct Relationship { + relationship_target: Ident, +} + +struct RelationshipTarget { + relationship: Ident, + despawn_descendants: bool, +} + // values for `storage` attribute const TABLE: &str = "Table"; const SPARSE_SET: &str = "SparseSet"; @@ -247,7 +343,10 @@ fn parse_component_attr(ast: &DeriveInput) -> Result { on_insert: None, on_replace: None, on_remove: None, + on_despawn: None, requires: None, + relationship: None, + relationship_target: None, immutable: false, }; @@ -278,6 +377,9 @@ fn parse_component_attr(ast: &DeriveInput) -> Result { } else if nested.path.is_ident(ON_REMOVE) { attrs.on_remove = Some(nested.value()?.parse::()?); Ok(()) + } else if nested.path.is_ident(ON_DESPAWN) { + attrs.on_despawn = Some(nested.value()?.parse::()?); + Ok(()) } else if nested.path.is_ident(IMMUTABLE) { attrs.immutable = true; Ok(()) @@ -301,6 +403,12 @@ fn parse_component_attr(ast: &DeriveInput) -> Result { } else { attrs.requires = Some(punctuated); } + } else if attr.path().is_ident(RELATIONSHIP) { + let relationship = attr.parse_args::()?; + attrs.relationship = Some(relationship); + } else if attr.path().is_ident(RELATIONSHIP_TARGET) { + let relationship_target = attr.parse_args::()?; + attrs.relationship_target = Some(relationship_target); } } @@ -341,3 +449,158 @@ fn hook_register_function_call( ) -> Option { function.map(|meta| quote! { hooks. #hook (#meta); }) } + +impl Parse for Relationship { + fn parse(input: syn::parse::ParseStream) -> Result { + syn::custom_keyword!(relationship_target); + input.parse::()?; + input.parse::()?; + Ok(Relationship { + relationship_target: input.parse::()?, + }) + } +} + +impl Parse for RelationshipTarget { + fn parse(input: syn::parse::ParseStream) -> Result { + let mut relationship_ident = None; + let mut despawn_descendants_exists = false; + syn::custom_keyword!(relationship); + syn::custom_keyword!(despawn_descendants); + let mut done = false; + loop { + if input.peek(relationship) { + input.parse::()?; + input.parse::()?; + relationship_ident = Some(input.parse::()?); + } else if input.peek(despawn_descendants) { + input.parse::()?; + despawn_descendants_exists = true; + } else { + done = true; + } + if input.peek(Token![,]) { + input.parse::()?; + } + if done { + break; + } + } + + let relationship = relationship_ident.ok_or_else(|| syn::Error::new(input.span(), "RelationshipTarget derive must specify a relationship via #[relationship_target(relationship = X)"))?; + Ok(RelationshipTarget { + relationship, + despawn_descendants: despawn_descendants_exists, + }) + } +} + +fn derive_relationship( + ast: &DeriveInput, + attrs: &Attrs, + bevy_ecs_path: &Path, +) -> Result> { + let Some(relationship) = &attrs.relationship else { + return Ok(None); + }; + const RELATIONSHIP_FORMAT_MESSAGE: &str = "Relationship derives must be a tuple struct with the only element being an EntityTargets type (ex: ChildOf(Entity))"; + if let Data::Struct(DataStruct { + fields: Fields::Unnamed(unnamed_fields), + struct_token, + .. + }) = &ast.data + { + if unnamed_fields.unnamed.len() != 1 { + return Err(syn::Error::new(ast.span(), RELATIONSHIP_FORMAT_MESSAGE)); + } + if unnamed_fields.unnamed.first().is_none() { + return Err(syn::Error::new( + struct_token.span(), + RELATIONSHIP_FORMAT_MESSAGE, + )); + } + } else { + return Err(syn::Error::new(ast.span(), RELATIONSHIP_FORMAT_MESSAGE)); + }; + + let struct_name = &ast.ident; + let (impl_generics, type_generics, where_clause) = &ast.generics.split_for_impl(); + + let relationship_target = &relationship.relationship_target; + + Ok(Some(quote! { + impl #impl_generics #bevy_ecs_path::relationship::Relationship for #struct_name #type_generics #where_clause { + type RelationshipTarget = #relationship_target; + + #[inline(always)] + fn get(&self) -> #bevy_ecs_path::entity::Entity { + self.0 + } + + #[inline] + fn from(entity: #bevy_ecs_path::entity::Entity) -> Self { + Self(entity) + } + } + })) +} + +fn derive_relationship_target( + ast: &DeriveInput, + attrs: &Attrs, + bevy_ecs_path: &Path, +) -> Result> { + let Some(relationship_target) = &attrs.relationship_target else { + return Ok(None); + }; + + const RELATIONSHIP_TARGET_FORMAT_MESSAGE: &str = "RelationshipTarget derives must be a tuple struct with the first element being a private RelationshipSourceCollection (ex: Children(Vec))"; + let collection = if let Data::Struct(DataStruct { + fields: Fields::Unnamed(unnamed_fields), + struct_token, + .. + }) = &ast.data + { + if let Some(first) = unnamed_fields.unnamed.first() { + if first.vis != Visibility::Inherited { + return Err(syn::Error::new(first.span(), "The collection in RelationshipTarget must be private to prevent users from directly mutating it, which could invalidate the correctness of relationships.")); + } + first.ty.clone() + } else { + return Err(syn::Error::new( + struct_token.span(), + RELATIONSHIP_TARGET_FORMAT_MESSAGE, + )); + } + } else { + return Err(syn::Error::new( + ast.span(), + RELATIONSHIP_TARGET_FORMAT_MESSAGE, + )); + }; + + let relationship = &relationship_target.relationship; + let struct_name = &ast.ident; + let (impl_generics, type_generics, where_clause) = &ast.generics.split_for_impl(); + Ok(Some(quote! { + impl #impl_generics #bevy_ecs_path::relationship::RelationshipTarget for #struct_name #type_generics #where_clause { + type Relationship = #relationship; + type Collection = #collection; + + #[inline] + fn collection(&self) -> &Self::Collection { + &self.0 + } + + #[inline] + fn collection_mut_risky(&mut self) -> &mut Self::Collection { + &mut self.0 + } + + #[inline] + fn from_collection_risky(collection: Self::Collection) -> Self { + Self(collection) + } + } + })) +} diff --git a/crates/bevy_ecs/macros/src/lib.rs b/crates/bevy_ecs/macros/src/lib.rs index 0893e721ae..8d8f19ca9a 100644 --- a/crates/bevy_ecs/macros/src/lib.rs +++ b/crates/bevy_ecs/macros/src/lib.rs @@ -589,7 +589,7 @@ pub fn derive_resource(input: TokenStream) -> TokenStream { component::derive_resource(input) } -#[proc_macro_derive(Component, attributes(component))] +#[proc_macro_derive(Component, attributes(component, relationship, relationship_target))] pub fn derive_component(input: TokenStream) -> TokenStream { component::derive_component(input) } diff --git a/crates/bevy_ecs/src/archetype.rs b/crates/bevy_ecs/src/archetype.rs index e3b8b8dac5..cb72db3067 100644 --- a/crates/bevy_ecs/src/archetype.rs +++ b/crates/bevy_ecs/src/archetype.rs @@ -354,10 +354,12 @@ bitflags::bitflags! { const ON_INSERT_HOOK = (1 << 1); const ON_REPLACE_HOOK = (1 << 2); const ON_REMOVE_HOOK = (1 << 3); - const ON_ADD_OBSERVER = (1 << 4); - const ON_INSERT_OBSERVER = (1 << 5); - const ON_REPLACE_OBSERVER = (1 << 6); - const ON_REMOVE_OBSERVER = (1 << 7); + const ON_DESPAWN_HOOK = (1 << 4); + const ON_ADD_OBSERVER = (1 << 5); + const ON_INSERT_OBSERVER = (1 << 6); + const ON_REPLACE_OBSERVER = (1 << 7); + const ON_REMOVE_OBSERVER = (1 << 8); + const ON_DESPAWN_OBSERVER = (1 << 9); } } @@ -672,6 +674,12 @@ impl Archetype { self.flags().contains(ArchetypeFlags::ON_REMOVE_HOOK) } + /// Returns true if any of the components in this archetype have `on_despawn` hooks + #[inline] + pub fn has_despawn_hook(&self) -> bool { + self.flags().contains(ArchetypeFlags::ON_DESPAWN_HOOK) + } + /// Returns true if any of the components in this archetype have at least one [`OnAdd`] observer /// /// [`OnAdd`]: crate::world::OnAdd @@ -703,6 +711,14 @@ impl Archetype { pub fn has_remove_observer(&self) -> bool { self.flags().contains(ArchetypeFlags::ON_REMOVE_OBSERVER) } + + /// Returns true if any of the components in this archetype have at least one [`OnDespawn`] observer + /// + /// [`OnDespawn`]: crate::world::OnDespawn + #[inline] + pub fn has_despawn_observer(&self) -> bool { + self.flags().contains(ArchetypeFlags::ON_DESPAWN_OBSERVER) + } } /// The next [`ArchetypeId`] in an [`Archetypes`] collection. diff --git a/crates/bevy_ecs/src/bundle.rs b/crates/bevy_ecs/src/bundle.rs index 6b4d9da281..d18d6bdd2a 100644 --- a/crates/bevy_ecs/src/bundle.rs +++ b/crates/bevy_ecs/src/bundle.rs @@ -1044,7 +1044,6 @@ impl<'w> BundleInserter<'w> { ) -> EntityLocation { let bundle_info = self.bundle_info.as_ref(); let archetype_after_insert = self.archetype_after_insert.as_ref(); - let table = self.table.as_mut(); let archetype = self.archetype.as_ref(); // SAFETY: All components in the bundle are guaranteed to exist in the World @@ -1069,6 +1068,8 @@ impl<'w> BundleInserter<'w> { } } + let table = self.table.as_mut(); + // SAFETY: Archetype gets borrowed when running the on_replace observers above, // so this reference can only be promoted from shared to &mut down here, after they have been ran let archetype = self.archetype.as_mut(); diff --git a/crates/bevy_ecs/src/component.rs b/crates/bevy_ecs/src/component.rs index 66d5db98f8..6cab46f8e2 100644 --- a/crates/bevy_ecs/src/component.rs +++ b/crates/bevy_ecs/src/component.rs @@ -563,6 +563,7 @@ pub struct ComponentHooks { pub(crate) on_insert: Option, pub(crate) on_replace: Option, pub(crate) on_remove: Option, + pub(crate) on_despawn: Option, } impl ComponentHooks { @@ -629,6 +630,16 @@ impl ComponentHooks { .expect("Component already has an on_remove hook") } + /// Register a [`ComponentHook`] that will be run for each component on an entity when it is despawned. + /// + /// # Panics + /// + /// Will panic if the component already has an `on_despawn` hook + pub fn on_despawn(&mut self, hook: ComponentHook) -> &mut Self { + self.try_on_despawn(hook) + .expect("Component already has an on_despawn hook") + } + /// Attempt to register a [`ComponentHook`] that will be run when this component is added to an entity. /// /// This is a fallible version of [`Self::on_add`]. @@ -680,6 +691,19 @@ impl ComponentHooks { self.on_remove = Some(hook); Some(self) } + + /// Attempt to register a [`ComponentHook`] that will be run for each component on an entity when it is despawned. + /// + /// This is a fallible version of [`Self::on_despawn`]. + /// + /// Returns `None` if the component already has an `on_despawn` hook. + pub fn try_on_despawn(&mut self, hook: ComponentHook) -> Option<&mut Self> { + if self.on_despawn.is_some() { + return None; + } + self.on_despawn = Some(hook); + Some(self) + } } /// Stores metadata for a type of component or resource stored in a specific [`World`]. @@ -775,6 +799,9 @@ impl ComponentInfo { if self.hooks().on_remove.is_some() { flags.insert(ArchetypeFlags::ON_REMOVE_HOOK); } + if self.hooks().on_despawn.is_some() { + flags.insert(ArchetypeFlags::ON_DESPAWN_HOOK); + } } /// Provides a reference to the collection of hooks associated with this [`Component`] diff --git a/crates/bevy_ecs/src/entity/clone_entities.rs b/crates/bevy_ecs/src/entity/clone_entities.rs index 2b51c1f937..e209df4ac3 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 0000000000..6e495a116b --- /dev/null +++ b/crates/bevy_ecs/src/hierarchy.rs @@ -0,0 +1,454 @@ +//! The canonical "parent-child" [`Relationship`] for entities, driven by +//! the [`Parent`] [`Relationship`] and the [`Children`] [`RelationshipTarget`]. +//! +//! See [`Parent`] for a full description of the relationship and how to use it. +//! +//! [`Relationship`]: crate::relationship::Relationship +//! [`RelationshipTarget`]: crate::relationship::RelationshipTarget + +#[cfg(feature = "bevy_reflect")] +use crate::reflect::{ + ReflectComponent, ReflectFromWorld, ReflectMapEntities, ReflectVisitEntities, + ReflectVisitEntitiesMut, +}; +use crate::{ + self as bevy_ecs, + bundle::Bundle, + component::{Component, ComponentId}, + entity::{Entity, VisitEntities}, + relationship::{RelatedSpawner, RelatedSpawnerCommands}, + system::EntityCommands, + world::{DeferredWorld, EntityWorldMut, FromWorld, World}, +}; +use alloc::{format, string::String, vec::Vec}; +use bevy_ecs_macros::VisitEntitiesMut; +use core::ops::Deref; +use core::slice; +use disqualified::ShortName; +use log::warn; + +/// A [`Relationship`](crate::relationship::Relationship) component that creates the canonical +/// "parent / child" hierarchy. This is the "source of truth" component, and it pairs with +/// the [`Children`] [`RelationshipTarget`](crate::relationship::RelationshipTarget). +/// +/// This relationship should be used for things like: +/// +/// 1. Organizing entities in a scene +/// 2. Propagating configuration or data inherited from a parent, such as "visibility" or "world-space global transforms". +/// 3. Ensuring a hierarchy is despawned when an entity is despawned. +/// 4. +/// +/// [`Parent`] contains a single "target" [`Entity`]. When [`Parent`] is inserted on a "source" entity, +/// the "target" entity will automatically (and immediately, via a component hook) have a [`Children`] +/// component inserted, and the "source" entity will be added to that [`Children`] instance. +/// +/// If the [`Parent`] component is replaced with a different "target" entity, the old target's [`Children`] +/// will be automatically (and immediately, via a component hook) be updated to reflect that change. +/// +/// Likewise, when the [`Parent`] component is removed, the "source" entity will be removed from the old +/// target's [`Children`]. If this results in [`Children`] being empty, [`Children`] will be automatically removed. +/// +/// When a parent is despawned, all children (and their descendants) will _also_ be despawned. +/// +/// You can create parent-child relationships in a variety of ways. The most direct way is to insert a [`Parent`] component: +/// +/// ``` +/// # use bevy_ecs::prelude::*; +/// # let mut world = World::new(); +/// let root = world.spawn_empty().id(); +/// let child1 = world.spawn(Parent(root)).id(); +/// let child2 = world.spawn(Parent(root)).id(); +/// let grandchild = world.spawn(Parent(child1)).id(); +/// +/// assert_eq!(&**world.entity(root).get::().unwrap(), &[child1, child2]); +/// assert_eq!(&**world.entity(child1).get::().unwrap(), &[grandchild]); +/// +/// world.entity_mut(child2).remove::(); +/// assert_eq!(&**world.entity(root).get::().unwrap(), &[child1]); +/// +/// world.entity_mut(root).despawn(); +/// assert!(world.get_entity(root).is_err()); +/// assert!(world.get_entity(child1).is_err()); +/// assert!(world.get_entity(grandchild).is_err()); +/// ``` +/// +/// However if you are spawning many children, you might want to use the [`EntityWorldMut::with_children`] helper instead: +/// +/// ``` +/// # use bevy_ecs::prelude::*; +/// # let mut world = World::new(); +/// let mut child1 = Entity::PLACEHOLDER; +/// let mut child2 = Entity::PLACEHOLDER; +/// let mut grandchild = Entity::PLACEHOLDER; +/// let root = world.spawn_empty().with_children(|p| { +/// child1 = p.spawn_empty().with_children(|p| { +/// grandchild = p.spawn_empty().id(); +/// }).id(); +/// child2 = p.spawn_empty().id(); +/// }).id(); +/// +/// assert_eq!(&**world.entity(root).get::().unwrap(), &[child1, child2]); +/// assert_eq!(&**world.entity(child1).get::().unwrap(), &[grandchild]); +/// ``` +#[derive(Component, Clone, VisitEntities, VisitEntitiesMut, PartialEq, Eq, Debug)] +#[cfg_attr(feature = "bevy_reflect", derive(bevy_reflect::Reflect))] +#[cfg_attr( + feature = "bevy_reflect", + reflect( + Component, + MapEntities, + VisitEntities, + VisitEntitiesMut, + PartialEq, + Debug, + FromWorld + ) +)] +#[relationship(relationship_target = Children)] +pub struct Parent(pub Entity); + +impl Parent { + /// Returns the "target" entity. + pub fn get(&self) -> Entity { + self.0 + } +} + +impl Deref for Parent { + type Target = Entity; + + #[inline] + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +// TODO: We need to impl either FromWorld or Default so Parent can be registered as Reflect. +// This is because Reflect deserialize by creating an instance and apply a patch on top. +// However Parent should only ever be set with a real user-defined entity. Its worth looking into +// better ways to handle cases like this. +impl FromWorld for Parent { + #[inline(always)] + fn from_world(_world: &mut World) -> Self { + Parent(Entity::PLACEHOLDER) + } +} + +/// A [`RelationshipTarget`](crate::relationship::RelationshipTarget) collection component that is populated +/// with entities that "target" this entity with the [`Parent`] [`Relationship`](crate::relationship::Relationship) component. +/// +/// Together, these components form the "canonical parent-child hierarchy". See the [`Parent`] component for all full +/// description of this relationship and instructions on how to use it. +#[derive(Component, Default, VisitEntitiesMut, Debug, PartialEq, Eq)] +#[relationship_target(relationship = Parent, despawn_descendants)] +#[cfg_attr(feature = "bevy_reflect", derive(bevy_reflect::Reflect))] +#[cfg_attr( + feature = "bevy_reflect", + reflect(Component, MapEntities, VisitEntities, VisitEntitiesMut, FromWorld) +)] +pub struct Children(Vec); + +impl<'a> IntoIterator for &'a Children { + type Item = ::Item; + + type IntoIter = slice::Iter<'a, Entity>; + + #[inline(always)] + fn into_iter(self) -> Self::IntoIter { + self.0.iter() + } +} + +impl Deref for Children { + type Target = [Entity]; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +/// A type alias over [`RelatedSpawner`] used to spawn child entities containing a [`Parent`] relationship. +pub type ChildSpawner<'w> = RelatedSpawner<'w, Parent>; + +/// A type alias over [`RelatedSpawnerCommands`] used to spawn child entities containing a [`Parent`] relationship. +pub type ChildSpawnerCommands<'w> = RelatedSpawnerCommands<'w, Parent>; + +impl<'w> EntityWorldMut<'w> { + /// Spawns children of this entity (with a [`Parent`] relationship) by taking a function that operates on a [`ChildSpawner`]. + pub fn with_children(&mut self, func: impl FnOnce(&mut ChildSpawner)) -> &mut Self { + self.with_related(func); + self + } + + /// Adds the given children to this entity + pub fn add_children(&mut self, children: &[Entity]) -> &mut Self { + self.add_related::(children) + } + + /// Adds the given child to this entity + pub fn add_child(&mut self, child: Entity) -> &mut Self { + self.add_related::(&[child]) + } + + /// Spawns the passed bundle and adds it to this entity as a child. + /// + /// For efficient spawning of multiple children, use [`with_children`]. + /// + /// [`with_children`]: EntityWorldMut::with_children + pub fn with_child(&mut self, bundle: impl Bundle) -> &mut Self { + let id = self.id(); + self.world_scope(|world| { + world.spawn((bundle, Parent(id))); + }); + self + } + + /// Removes the [`Parent`] component, if it exists. + #[deprecated(since = "0.16.0", note = "Use entity_mut.remove::()")] + pub fn remove_parent(&mut self) -> &mut Self { + self.remove::(); + self + } + + /// Inserts the [`Parent`] component with the given `parent` entity, if it exists. + #[deprecated(since = "0.16.0", note = "Use entity_mut.insert(Parent(entity))")] + pub fn set_parent(&mut self, parent: Entity) -> &mut Self { + self.insert(Parent(parent)); + self + } +} + +impl<'a> EntityCommands<'a> { + /// Spawns children of this entity (with a [`Parent`] relationship) by taking a function that operates on a [`ChildSpawner`]. + pub fn with_children( + &mut self, + func: impl FnOnce(&mut RelatedSpawnerCommands), + ) -> &mut Self { + self.with_related(func); + self + } + + /// Adds the given children to this entity + pub fn add_children(&mut self, children: &[Entity]) -> &mut Self { + self.add_related::(children) + } + + /// Adds the given child to this entity + pub fn add_child(&mut self, child: Entity) -> &mut Self { + self.add_related::(&[child]) + } + + /// Spawns the passed bundle and adds it to this entity as a child. + /// + /// For efficient spawning of multiple children, use [`with_children`]. + /// + /// [`with_children`]: EntityCommands::with_children + pub fn with_child(&mut self, bundle: impl Bundle) -> &mut Self { + let id = self.id(); + self.commands.spawn((bundle, Parent(id))); + self + } + + /// Removes the [`Parent`] component, if it exists. + #[deprecated(since = "0.16.0", note = "Use entity_commands.remove::()")] + pub fn remove_parent(&mut self) -> &mut Self { + self.remove::(); + self + } + + /// Inserts the [`Parent`] component with the given `parent` entity, if it exists. + #[deprecated(since = "0.16.0", note = "Use entity_commands.insert(Parent(entity))")] + pub fn set_parent(&mut self, parent: Entity) -> &mut Self { + self.insert(Parent(parent)); + self + } +} + +/// An `on_insert` component hook that when run, will validate that the parent of a given entity +/// contains component `C`. This will print a warning if the parent does not contain `C`. +pub fn validate_parent_has_component( + world: DeferredWorld, + entity: Entity, + _: ComponentId, +) { + let entity_ref = world.entity(entity); + let Some(child_of) = entity_ref.get::() else { + return; + }; + if !world + .get_entity(child_of.get()) + .is_ok_and(|e| e.contains::()) + { + // TODO: print name here once Name lives in bevy_ecs + let name: Option = None; + warn!( + "warning[B0004]: {name} with the {ty_name} component has a parent without {ty_name}.\n\ + This will cause inconsistent behaviors! See: https://bevyengine.org/learn/errors/b0004", + ty_name = ShortName::of::(), + name = name.map_or_else( + || format!("Entity {}", entity), + |s| format!("The {s} entity") + ), + ); + } +} + +#[cfg(test)] +mod tests { + use crate::{ + entity::Entity, + hierarchy::{Children, Parent}, + relationship::RelationshipTarget, + world::World, + }; + use alloc::{vec, vec::Vec}; + + #[derive(PartialEq, Eq, Debug)] + struct Node { + entity: Entity, + children: Vec, + } + + impl Node { + fn new(entity: Entity) -> Self { + Self { + entity, + children: Vec::new(), + } + } + + fn new_with(entity: Entity, children: Vec) -> Self { + Self { entity, children } + } + } + + fn get_hierarchy(world: &World, entity: Entity) -> Node { + Node { + entity, + children: world + .entity(entity) + .get::() + .map_or_else(Default::default, |c| { + c.iter().map(|e| get_hierarchy(world, e)).collect() + }), + } + } + + #[test] + fn hierarchy() { + let mut world = World::new(); + let root = world.spawn_empty().id(); + let child1 = world.spawn(Parent(root)).id(); + let grandchild = world.spawn(Parent(child1)).id(); + let child2 = world.spawn(Parent(root)).id(); + + // Spawn + let hierarchy = get_hierarchy(&world, root); + assert_eq!( + hierarchy, + Node::new_with( + root, + vec![ + Node::new_with(child1, vec![Node::new(grandchild)]), + Node::new(child2) + ] + ) + ); + + // Removal + world.entity_mut(child1).remove::(); + let hierarchy = get_hierarchy(&world, root); + assert_eq!(hierarchy, Node::new_with(root, vec![Node::new(child2)])); + + // Insert + world.entity_mut(child1).insert(Parent(root)); + let hierarchy = get_hierarchy(&world, root); + assert_eq!( + hierarchy, + Node::new_with( + root, + vec![ + Node::new(child2), + Node::new_with(child1, vec![Node::new(grandchild)]) + ] + ) + ); + + // Recursive Despawn + world.entity_mut(root).despawn(); + assert!(world.get_entity(root).is_err()); + assert!(world.get_entity(child1).is_err()); + assert!(world.get_entity(child2).is_err()); + assert!(world.get_entity(grandchild).is_err()); + } + + #[test] + fn with_children() { + let mut world = World::new(); + let mut child1 = Entity::PLACEHOLDER; + let mut child2 = Entity::PLACEHOLDER; + let root = world + .spawn_empty() + .with_children(|p| { + child1 = p.spawn_empty().id(); + child2 = p.spawn_empty().id(); + }) + .id(); + + let hierarchy = get_hierarchy(&world, root); + assert_eq!( + hierarchy, + Node::new_with(root, vec![Node::new(child1), Node::new(child2)]) + ); + } + + #[test] + fn add_children() { + let mut world = World::new(); + let child1 = world.spawn_empty().id(); + let child2 = world.spawn_empty().id(); + let root = world.spawn_empty().add_children(&[child1, child2]).id(); + + let hierarchy = get_hierarchy(&world, root); + assert_eq!( + hierarchy, + Node::new_with(root, vec![Node::new(child1), Node::new(child2)]) + ); + } + + #[test] + fn self_parenting_invalid() { + let mut world = World::new(); + let id = world.spawn_empty().id(); + world.entity_mut(id).insert(Parent(id)); + assert!( + world.entity(id).get::().is_none(), + "invalid Parent relationships should self-remove" + ); + } + + #[test] + fn missing_parent_invalid() { + let mut world = World::new(); + let parent = world.spawn_empty().id(); + world.entity_mut(parent).despawn(); + let id = world.spawn(Parent(parent)).id(); + assert!( + world.entity(id).get::().is_none(), + "invalid Parent relationships should self-remove" + ); + } + + #[test] + fn reinsert_same_parent() { + let mut world = World::new(); + let parent = world.spawn_empty().id(); + let id = world.spawn(Parent(parent)).id(); + world.entity_mut(id).insert(Parent(parent)); + assert_eq!( + Some(&Parent(parent)), + world.entity(id).get::(), + "Parent should still be there" + ); + } +} diff --git a/crates/bevy_ecs/src/lib.rs b/crates/bevy_ecs/src/lib.rs index 606d1b9e91..f6b9bba576 100644 --- a/crates/bevy_ecs/src/lib.rs +++ b/crates/bevy_ecs/src/lib.rs @@ -40,6 +40,7 @@ pub mod change_detection; pub mod component; pub mod entity; pub mod event; +pub mod hierarchy; pub mod identifier; pub mod intern; pub mod label; @@ -48,6 +49,7 @@ pub mod observer; pub mod query; #[cfg(feature = "bevy_reflect")] pub mod reflect; +pub mod relationship; pub mod removal_detection; pub mod result; pub mod schedule; @@ -73,6 +75,7 @@ pub mod prelude { component::{require, Component}, entity::{Entity, EntityBorrow, EntityMapper}, event::{Event, EventMutator, EventReader, EventWriter, Events}, + hierarchy::{ChildSpawner, ChildSpawnerCommands, Children, Parent}, name::{Name, NameOrEntity}, observer::{CloneEntityWithObserversExt, Observer, Trigger}, query::{Added, AnyOf, Changed, Has, Or, QueryBuilder, QueryState, With, Without}, diff --git a/crates/bevy_ecs/src/observer/mod.rs b/crates/bevy_ecs/src/observer/mod.rs index 36c74a3b98..9f147dd91b 100644 --- a/crates/bevy_ecs/src/observer/mod.rs +++ b/crates/bevy_ecs/src/observer/mod.rs @@ -351,6 +351,7 @@ pub struct Observers { on_insert: CachedObservers, on_replace: CachedObservers, on_remove: CachedObservers, + on_despawn: CachedObservers, // Map from trigger type to set of observers cache: HashMap, } @@ -362,6 +363,7 @@ impl Observers { ON_INSERT => &mut self.on_insert, ON_REPLACE => &mut self.on_replace, ON_REMOVE => &mut self.on_remove, + ON_DESPAWN => &mut self.on_despawn, _ => self.cache.entry(event_type).or_default(), } } @@ -372,6 +374,7 @@ impl Observers { ON_INSERT => Some(&self.on_insert), ON_REPLACE => Some(&self.on_replace), ON_REMOVE => Some(&self.on_remove), + ON_DESPAWN => Some(&self.on_despawn), _ => self.cache.get(&event_type), } } @@ -446,6 +449,7 @@ impl Observers { ON_INSERT => Some(ArchetypeFlags::ON_INSERT_OBSERVER), ON_REPLACE => Some(ArchetypeFlags::ON_REPLACE_OBSERVER), ON_REMOVE => Some(ArchetypeFlags::ON_REMOVE_OBSERVER), + ON_DESPAWN => Some(ArchetypeFlags::ON_DESPAWN_OBSERVER), _ => None, } } @@ -482,6 +486,14 @@ impl Observers { { flags.insert(ArchetypeFlags::ON_REMOVE_OBSERVER); } + + if self + .on_despawn + .component_observers + .contains_key(&component_id) + { + flags.insert(ArchetypeFlags::ON_DESPAWN_OBSERVER); + } } } @@ -1463,6 +1475,7 @@ mod tests { } #[test] + #[should_panic] fn observer_invalid_params() { #[derive(Resource)] struct ResA; @@ -1476,8 +1489,6 @@ mod tests { commands.insert_resource(ResB); }); world.trigger(EventA); - - assert!(world.get_resource::().is_none()); } #[test] diff --git a/crates/bevy_ecs/src/reflect/component.rs b/crates/bevy_ecs/src/reflect/component.rs index 7778f16451..6af8e34a31 100644 --- a/crates/bevy_ecs/src/reflect/component.rs +++ b/crates/bevy_ecs/src/reflect/component.rs @@ -301,14 +301,16 @@ impl FromType for ReflectComponent { component.apply(reflected_component); }, apply_or_insert: |entity, reflected_component, registry| { - if !C::Mutability::MUTABLE { - let name = ShortName::of::(); - panic!("Cannot call `ReflectComponent::apply_or_insert` on component {name}. It is immutable, and cannot modified through reflection"); - } - - // SAFETY: guard ensures `C` is a mutable component - if let Some(mut component) = unsafe { entity.get_mut_assume_mutable::() } { - component.apply(reflected_component.as_partial_reflect()); + if C::Mutability::MUTABLE { + // SAFETY: guard ensures `C` is a mutable component + if let Some(mut component) = unsafe { entity.get_mut_assume_mutable::() } { + component.apply(reflected_component.as_partial_reflect()); + } else { + let component = entity.world_scope(|world| { + from_reflect_with_fallback::(reflected_component, world, registry) + }); + entity.insert(component); + } } else { let component = entity.world_scope(|world| { from_reflect_with_fallback::(reflected_component, world, registry) diff --git a/crates/bevy_ecs/src/relationship/mod.rs b/crates/bevy_ecs/src/relationship/mod.rs new file mode 100644 index 0000000000..54105c0d73 --- /dev/null +++ b/crates/bevy_ecs/src/relationship/mod.rs @@ -0,0 +1,253 @@ +//! This module provides functionality to link entities to each other using specialized components called "relationships". See the [`Relationship`] trait for more info. + +mod related_methods; +mod relationship_query; +mod relationship_source_collection; + +pub use related_methods::*; +pub use relationship_query::*; +pub use relationship_source_collection::*; + +use crate::{ + component::{Component, ComponentId, Mutable}, + entity::Entity, + system::{ + command::HandleError, + entity_command::{self, CommandWithEntity}, + error_handler, + }, + world::{DeferredWorld, EntityWorldMut}, +}; +use log::warn; + +/// A [`Component`] on a "source" [`Entity`] that references another target [`Entity`], creating a "relationship" between them. Every [`Relationship`] +/// has a corresponding [`RelationshipTarget`] type (and vice-versa), which exists on the "target" entity of a relationship and contains the list of all +/// "source" entities that relate to the given "target" +/// +/// The [`Relationship`] component is the "source of truth" and the [`RelationshipTarget`] component reflects that source of truth. When a [`Relationship`] +/// component is inserted on an [`Entity`], the corresponding [`RelationshipTarget`] component is immediately inserted on the target component if it does +/// not already exist, and the "source" entity is automatically added to the [`RelationshipTarget`] collection (this is done via "component hooks"). +/// +/// A common example of a [`Relationship`] is the parent / child relationship. Bevy ECS includes a canonical form of this via the [`Parent`](crate::hierarchy::Parent) +/// [`Relationship`] and the [`Children`](crate::hierarchy::Children) [`RelationshipTarget`]. +/// +/// [`Relationship`] and [`RelationshipTarget`] should always be derived via the [`Component`] trait to ensure the hooks are set up properly. +/// +/// ``` +/// # use bevy_ecs::component::Component; +/// # use bevy_ecs::entity::Entity; +/// #[derive(Component)] +/// #[relationship(relationship_target = Children)] +/// pub struct Parent(pub Entity); +/// +/// #[derive(Component)] +/// #[relationship_target(relationship = Parent)] +/// pub struct Children(Vec); +/// ``` +/// +/// When deriving [`RelationshipTarget`] you can specify the `#[relationship_target(despawn_descendants)]` attribute to +/// automatically despawn entities stored in an entity's [`RelationshipTarget`] when that entity is despawned: +/// +/// ``` +/// # use bevy_ecs::component::Component; +/// # use bevy_ecs::entity::Entity; +/// #[derive(Component)] +/// #[relationship(relationship_target = Children)] +/// pub struct Parent(pub Entity); +/// +/// #[derive(Component)] +/// #[relationship_target(relationship = Parent, despawn_descendants)] +/// pub struct Children(Vec); +/// ``` +pub trait Relationship: Component + Sized { + /// The [`Component`] added to the "target" entities of this [`Relationship`], which contains the list of all "source" + /// entities that relate to the "target". + type RelationshipTarget: RelationshipTarget; + + /// Gets the [`Entity`] ID of the related entity. + fn get(&self) -> Entity; + + /// Creates this [`Relationship`] from the given `entity`. + fn from(entity: Entity) -> Self; + + /// The `on_insert` component hook that maintains the [`Relationship`] / [`RelationshipTarget`] connection. + fn on_insert(mut world: DeferredWorld, entity: Entity, _: ComponentId) { + let target_entity = world.entity(entity).get::().unwrap().get(); + if target_entity == entity { + warn!( + "The {}({target_entity:?}) relationship on entity {entity:?} points to itself. The invalid {} relationship has been removed.", + core::any::type_name::(), + core::any::type_name::() + ); + world.commands().entity(entity).remove::(); + } + if let Ok(mut target_entity_mut) = world.get_entity_mut(target_entity) { + if let Some(mut relationship_target) = + target_entity_mut.get_mut::() + { + relationship_target.collection_mut_risky().add(entity); + } else { + let mut target = ::with_capacity(1); + target.collection_mut_risky().add(entity); + world.commands().entity(target_entity).insert(target); + } + } else { + warn!( + "The {}({target_entity:?}) relationship on entity {entity:?} relates to an entity that does not exist. The invalid {} relationship has been removed.", + core::any::type_name::(), + core::any::type_name::() + ); + world.commands().entity(entity).remove::(); + } + } + + /// The `on_replace` component hook that maintains the [`Relationship`] / [`RelationshipTarget`] connection. + // note: think of this as "on_drop" + fn on_replace(mut world: DeferredWorld, entity: Entity, _: ComponentId) { + let target_entity = world.entity(entity).get::().unwrap().get(); + if let Ok(mut target_entity_mut) = world.get_entity_mut(target_entity) { + if let Some(mut relationship_target) = + target_entity_mut.get_mut::() + { + relationship_target.collection_mut_risky().remove(entity); + if relationship_target.len() == 0 { + if let Some(mut entity) = world.commands().get_entity(target_entity) { + // this "remove" operation must check emptiness because in the event that an identical + // relationship is inserted on top, this despawn would result in the removal of that identical + // relationship ... not what we want! + entity.queue(|mut entity: EntityWorldMut| { + if entity + .get::() + .is_some_and(RelationshipTarget::is_empty) + { + entity.remove::(); + } + }); + } + } + } + } + } +} + +/// A [`Component`] containing the collection of entities that relate to this [`Entity`] via the associated `Relationship` type. +/// See the [`Relationship`] documentation for more information. +pub trait RelationshipTarget: Component + Sized { + /// The [`Relationship`] that populates this [`RelationshipTarget`] collection. + type Relationship: Relationship; + /// The collection type that stores the "source" entities for this [`RelationshipTarget`] component. + type Collection: RelationshipSourceCollection; + + /// Returns a reference to the stored [`RelationshipTarget::Collection`]. + fn collection(&self) -> &Self::Collection; + /// Returns a mutable reference to the stored [`RelationshipTarget::Collection`]. + /// + /// # Warning + /// This should generally not be called by user code, as modifying the internal collection could invalidate the relationship. + fn collection_mut_risky(&mut self) -> &mut Self::Collection; + + /// Creates a new [`RelationshipTarget`] from the given [`RelationshipTarget::Collection`]. + /// + /// # Warning + /// This should generally not be called by user code, as constructing the internal collection could invalidate the relationship. + fn from_collection_risky(collection: Self::Collection) -> Self; + + /// The `on_replace` component hook that maintains the [`Relationship`] / [`RelationshipTarget`] connection. + // note: think of this as "on_drop" + fn on_replace(mut world: DeferredWorld, entity: Entity, _: ComponentId) { + // NOTE: this unsafe code is an optimization. We could make this safe, but it would require + // copying the RelationshipTarget collection + // SAFETY: This only reads the Self component and queues Remove commands + unsafe { + let world = world.as_unsafe_world_cell(); + let relationship_target = world.get_entity(entity).unwrap().get::().unwrap(); + let mut commands = world.get_raw_command_queue(); + for source_entity in relationship_target.iter() { + if world.get_entity(source_entity).is_some() { + commands.push( + entity_command::remove::() + .with_entity(source_entity) + .handle_error_with(error_handler::silent()), + ); + } else { + warn!("Tried to despawn non-existent entity {}", source_entity); + } + } + } + } + + /// The `on_despawn` component hook that despawns entities stored in an entity's [`RelationshipTarget`] when + /// that entity is despawned. + // note: think of this as "on_drop" + fn on_despawn(mut world: DeferredWorld, entity: Entity, _: ComponentId) { + // NOTE: this unsafe code is an optimization. We could make this safe, but it would require + // copying the RelationshipTarget collection + // SAFETY: This only reads the Self component and queues despawn commands + unsafe { + let world = world.as_unsafe_world_cell(); + let relationship_target = world.get_entity(entity).unwrap().get::().unwrap(); + let mut commands = world.get_raw_command_queue(); + for source_entity in relationship_target.iter() { + if world.get_entity(source_entity).is_some() { + commands.push( + entity_command::despawn() + .with_entity(source_entity) + .handle_error_with(error_handler::silent()), + ); + } else { + warn!("Tried to despawn non-existent entity {}", source_entity); + } + } + } + } + + /// Creates this [`RelationshipTarget`] with the given pre-allocated entity capacity. + fn with_capacity(capacity: usize) -> Self { + let collection = + ::with_capacity(capacity); + Self::from_collection_risky(collection) + } + + /// Iterates the entities stored in this collection. + #[inline] + fn iter(&self) -> impl DoubleEndedIterator { + self.collection().iter() + } + + /// Returns the number of entities in this collection. + #[inline] + fn len(&self) -> usize { + self.collection().len() + } + + /// Returns true if this entity collection is empty. + #[inline] + fn is_empty(&self) -> bool { + self.collection().is_empty() + } +} + +#[cfg(test)] +mod tests { + use crate as bevy_ecs; + use crate::world::World; + use crate::{component::Component, entity::Entity}; + use alloc::vec::Vec; + + #[test] + fn custom_relationship() { + #[derive(Component)] + #[relationship(relationship_target = LikedBy)] + struct Likes(pub Entity); + + #[derive(Component)] + #[relationship_target(relationship = Likes)] + struct LikedBy(Vec); + + let mut world = World::new(); + let a = world.spawn_empty().id(); + let b = world.spawn(Likes(a)).id(); + let c = world.spawn(Likes(a)).id(); + assert_eq!(world.entity(a).get::().unwrap().0, &[b, c]); + } +} diff --git a/crates/bevy_ecs/src/relationship/related_methods.rs b/crates/bevy_ecs/src/relationship/related_methods.rs new file mode 100644 index 0000000000..4b42709384 --- /dev/null +++ b/crates/bevy_ecs/src/relationship/related_methods.rs @@ -0,0 +1,164 @@ +use crate::{ + bundle::Bundle, + entity::Entity, + relationship::{Relationship, RelationshipTarget}, + system::{Commands, EntityCommands}, + world::{EntityWorldMut, World}, +}; +use core::marker::PhantomData; + +impl<'w> EntityWorldMut<'w> { + /// Spawns entities related to this entity (with the `R` relationship) by taking a function that operates on a [`RelatedSpawner`]. + pub fn with_related( + &mut self, + func: impl FnOnce(&mut RelatedSpawner), + ) -> &mut Self { + let parent = self.id(); + self.world_scope(|world| { + func(&mut RelatedSpawner::new(world, parent)); + }); + self + } + + /// Relates the given entities to this entity with the relation `R` + pub fn add_related(&mut self, related: &[Entity]) -> &mut Self { + let id = self.id(); + self.world_scope(|world| { + for related in related { + world.entity_mut(*related).insert(R::from(id)); + } + }); + self + } + + /// Despawns entities that relate to this one via the given [`RelationshipTarget`]. + /// This entity will not be despawned. + pub fn despawn_related(&mut self) -> &mut Self { + if let Some(sources) = self.take::() { + self.world_scope(|world| { + for entity in sources.iter() { + if let Ok(entity_mut) = world.get_entity_mut(entity) { + entity_mut.despawn(); + } + } + }); + } + self + } +} + +impl<'a> EntityCommands<'a> { + /// Spawns entities related to this entity (with the `R` relationship) by taking a function that operates on a [`RelatedSpawner`]. + pub fn with_related( + &mut self, + func: impl FnOnce(&mut RelatedSpawnerCommands), + ) -> &mut Self { + let id = self.id(); + func(&mut RelatedSpawnerCommands::new(self.commands(), id)); + self + } + + /// Relates the given entities to this entity with the relation `R` + pub fn add_related(&mut self, related: &[Entity]) -> &mut Self { + let id = self.id(); + let related = related.to_vec(); + self.commands().queue(move |world: &mut World| { + for related in related { + world.entity_mut(related).insert(R::from(id)); + } + }); + self + } + + /// Despawns entities that relate to this one via the given [`RelationshipTarget`]. + /// This entity will not be despawned. + pub fn despawn_related(&mut self) -> &mut Self { + let id = self.id(); + self.commands.queue(move |world: &mut World| { + world.entity_mut(id).despawn_related::(); + }); + self + } +} + +/// Directly spawns related "source" entities with the given [`Relationship`], targeting +/// a specific entity. +pub struct RelatedSpawner<'w, R: Relationship> { + target: Entity, + world: &'w mut World, + _marker: PhantomData, +} + +impl<'w, R: Relationship> RelatedSpawner<'w, R> { + /// Creates a new instance that will spawn entities targeting the `target` entity. + pub fn new(world: &'w mut World, target: Entity) -> Self { + Self { + world, + target, + _marker: PhantomData, + } + } + + /// Spawns an entity with the given `bundle` and an `R` relationship targeting the `target` + /// entity this spawner was initialized with. + pub fn spawn(&mut self, bundle: impl Bundle) -> EntityWorldMut<'_> { + self.world.spawn((R::from(self.target), bundle)) + } + + /// Spawns an entity with an `R` relationship targeting the `target` + /// entity this spawner was initialized with. + pub fn spawn_empty(&mut self) -> EntityWorldMut<'_> { + self.world.spawn(R::from(self.target)) + } + + /// Returns the "target entity" used when spawning entities with an `R` [`Relationship`]. + pub fn target_entity(&self) -> Entity { + self.target + } +} + +/// Uses commands to spawn related "source" entities with the given [`Relationship`], targeting +/// a specific entity. +pub struct RelatedSpawnerCommands<'w, R: Relationship> { + target: Entity, + commands: Commands<'w, 'w>, + _marker: PhantomData, +} + +impl<'w, R: Relationship> RelatedSpawnerCommands<'w, R> { + /// Creates a new instance that will spawn entities targeting the `target` entity. + pub fn new(commands: Commands<'w, 'w>, target: Entity) -> Self { + Self { + commands, + target, + _marker: PhantomData, + } + } + + /// Spawns an entity with the given `bundle` and an `R` relationship targeting the `target` + /// entity this spawner was initialized with. + pub fn spawn(&mut self, bundle: impl Bundle) -> EntityCommands<'_> { + self.commands.spawn((R::from(self.target), bundle)) + } + + /// Spawns an entity with an `R` relationship targeting the `target` + /// entity this spawner was initialized with. + pub fn spawn_empty(&mut self) -> EntityCommands<'_> { + self.commands.spawn(R::from(self.target)) + } + + /// Returns the "target entity" used when spawning entities with an `R` [`Relationship`]. + pub fn target_entity(&self) -> Entity { + self.target + } + + /// Returns the underlying [`Commands`]. + pub fn commands(&mut self) -> Commands { + self.commands.reborrow() + } + + /// Returns a mutable reference to the underlying [`Commands`]. + pub fn commands_mut(&mut self) -> &mut Commands<'w, 'w> { + &mut self.commands + } +} diff --git a/crates/bevy_ecs/src/relationship/relationship_query.rs b/crates/bevy_ecs/src/relationship/relationship_query.rs new file mode 100644 index 0000000000..f47b6c14ca --- /dev/null +++ b/crates/bevy_ecs/src/relationship/relationship_query.rs @@ -0,0 +1,261 @@ +use crate::{ + entity::Entity, + query::{QueryData, QueryFilter, WorldQuery}, + relationship::{Relationship, RelationshipTarget}, + system::Query, +}; +use alloc::collections::VecDeque; +use smallvec::SmallVec; + +impl<'w, 's, D: QueryData, F: QueryFilter> Query<'w, 's, D, F> { + /// If the given `entity` contains the `R` [`Relationship`] component, returns the + /// target entity of that relationship. + pub fn related(&'w self, entity: Entity) -> Option + where + ::ReadOnly: WorldQuery = &'w R>, + { + self.get(entity).map(R::get).ok() + } + + /// If the given `entity` contains the `S` [`RelationshipTarget`] component, returns the + /// source entities stored on that component. + pub fn relationship_sources( + &'w self, + entity: Entity, + ) -> impl Iterator + 'w + where + ::ReadOnly: WorldQuery = &'w S>, + { + self.get(entity) + .into_iter() + .flat_map(RelationshipTarget::iter) + } + + /// Recursively walks up the tree defined by the given `R` [`Relationship`] until + /// there are no more related entities, returning the "root entity" of the relationship hierarchy. + /// + /// # Warning + /// For relationship graphs that contain loops, this could loop infinitely. Only call this for "hierarchy-style" + /// relationships. + pub fn root_ancestor(&'w self, entity: Entity) -> Entity + where + ::ReadOnly: WorldQuery = &'w R>, + { + // Recursively search up the tree until we're out of parents + match self.get(entity) { + Ok(parent) => self.root_ancestor(parent.get()), + Err(_) => entity, + } + } + + /// Iterates all "leaf entities" as defined by the [`RelationshipTarget`] hierarchy. + /// + /// # Warning + /// For relationship graphs that contain loops, this could loop infinitely. Only call this for "hierarchy-style" + /// relationships. + pub fn iter_leaves( + &'w self, + entity: Entity, + ) -> impl Iterator + 'w + where + ::ReadOnly: WorldQuery = &'w S>, + { + self.iter_descendants_depth_first(entity).filter(|entity| { + self.get(*entity) + // These are leaf nodes if they have the `Children` component but it's empty + .map(|children| children.len() == 0) + // Or if they don't have the `Children` component at all + .unwrap_or(true) + }) + } + + /// Iterates all sibling entities that also have the `R` [`Relationship`] with the same target entity. + pub fn iter_siblings( + &'w self, + entity: Entity, + ) -> impl Iterator + 'w + where + D::ReadOnly: WorldQuery = (Option<&'w R>, Option<&'w R::RelationshipTarget>)>, + { + self.get(entity) + .ok() + .and_then(|(maybe_parent, _)| maybe_parent.map(R::get)) + .and_then(|parent| self.get(parent).ok()) + .and_then(|(_, maybe_children)| maybe_children) + .into_iter() + .flat_map(move |children| children.iter().filter(move |child| *child != entity)) + } + + /// Iterates all descendant entities as defined by the given `entity`'s [`RelationshipTarget`] and their recursive + /// [`RelationshipTarget`]. + /// + /// # Warning + /// For relationship graphs that contain loops, this could loop infinitely. Only call this for "hierarchy-style" + /// relationships. + pub fn iter_descendants( + &'w self, + entity: Entity, + ) -> DescendantIter<'w, 's, D, F, S> + where + D::ReadOnly: WorldQuery = &'w S>, + { + DescendantIter::new(self, entity) + } + + /// Iterates all descendant entities as defined by the given `entity`'s [`RelationshipTarget`] and their recursive + /// [`RelationshipTarget`] in depth-first order. + /// + /// # Warning + /// For relationship graphs that contain loops, this could loop infinitely. Only call this for "hierarchy-style" + /// relationships. + pub fn iter_descendants_depth_first( + &'w self, + entity: Entity, + ) -> DescendantDepthFirstIter<'w, 's, D, F, S> + where + D::ReadOnly: WorldQuery = &'w S>, + { + DescendantDepthFirstIter::new(self, entity) + } + + /// Iterates all ancestors of the given `entity` as defined by the `R` [`Relationship`]. + /// + /// # Warning + /// For relationship graphs that contain loops, this could loop infinitely. Only call this for "hierarchy-style" + /// relationships. + pub fn iter_ancestors( + &'w self, + entity: Entity, + ) -> AncestorIter<'w, 's, D, F, R> + where + D::ReadOnly: WorldQuery = &'w R>, + { + AncestorIter::new(self, entity) + } +} + +/// An [`Iterator`] of [`Entity`]s over the descendants of an [`Entity`]. +/// +/// Traverses the hierarchy breadth-first. +pub struct DescendantIter<'w, 's, D: QueryData, F: QueryFilter, S: RelationshipTarget> +where + D::ReadOnly: WorldQuery = &'w S>, +{ + children_query: &'w Query<'w, 's, D, F>, + vecdeque: VecDeque, +} + +impl<'w, 's, D: QueryData, F: QueryFilter, S: RelationshipTarget> DescendantIter<'w, 's, D, F, S> +where + D::ReadOnly: WorldQuery = &'w S>, +{ + /// Returns a new [`DescendantIter`]. + pub fn new(children_query: &'w Query<'w, 's, D, F>, entity: Entity) -> Self { + DescendantIter { + children_query, + vecdeque: children_query + .get(entity) + .into_iter() + .flat_map(RelationshipTarget::iter) + .collect(), + } + } +} + +impl<'w, 's, D: QueryData, F: QueryFilter, S: RelationshipTarget> Iterator + for DescendantIter<'w, 's, D, F, S> +where + D::ReadOnly: WorldQuery = &'w S>, +{ + type Item = Entity; + + fn next(&mut self) -> Option { + let entity = self.vecdeque.pop_front()?; + + if let Ok(children) = self.children_query.get(entity) { + self.vecdeque.extend(children.iter()); + } + + Some(entity) + } +} + +/// An [`Iterator`] of [`Entity`]s over the descendants of an [`Entity`]. +/// +/// Traverses the hierarchy depth-first. +pub struct DescendantDepthFirstIter<'w, 's, D: QueryData, F: QueryFilter, S: RelationshipTarget> +where + D::ReadOnly: WorldQuery = &'w S>, +{ + children_query: &'w Query<'w, 's, D, F>, + stack: SmallVec<[Entity; 8]>, +} + +impl<'w, 's, D: QueryData, F: QueryFilter, S: RelationshipTarget> + DescendantDepthFirstIter<'w, 's, D, F, S> +where + D::ReadOnly: WorldQuery = &'w S>, +{ + /// Returns a new [`DescendantDepthFirstIter`]. + pub fn new(children_query: &'w Query<'w, 's, D, F>, entity: Entity) -> Self { + DescendantDepthFirstIter { + children_query, + stack: children_query + .get(entity) + .map_or(SmallVec::new(), |children| children.iter().rev().collect()), + } + } +} + +impl<'w, 's, D: QueryData, F: QueryFilter, S: RelationshipTarget> Iterator + for DescendantDepthFirstIter<'w, 's, D, F, S> +where + D::ReadOnly: WorldQuery = &'w S>, +{ + type Item = Entity; + + fn next(&mut self) -> Option { + let entity = self.stack.pop()?; + + if let Ok(children) = self.children_query.get(entity) { + self.stack.extend(children.iter().rev()); + } + + Some(entity) + } +} + +/// An [`Iterator`] of [`Entity`]s over the ancestors of an [`Entity`]. +pub struct AncestorIter<'w, 's, D: QueryData, F: QueryFilter, R: Relationship> +where + D::ReadOnly: WorldQuery = &'w R>, +{ + parent_query: &'w Query<'w, 's, D, F>, + next: Option, +} + +impl<'w, 's, D: QueryData, F: QueryFilter, R: Relationship> AncestorIter<'w, 's, D, F, R> +where + D::ReadOnly: WorldQuery = &'w R>, +{ + /// Returns a new [`AncestorIter`]. + pub fn new(parent_query: &'w Query<'w, 's, D, F>, entity: Entity) -> Self { + AncestorIter { + parent_query, + next: Some(entity), + } + } +} + +impl<'w, 's, D: QueryData, F: QueryFilter, R: Relationship> Iterator + for AncestorIter<'w, 's, D, F, R> +where + D::ReadOnly: WorldQuery = &'w R>, +{ + type Item = Entity; + + fn next(&mut self) -> Option { + self.next = self.parent_query.get(self.next?).ok().map(R::get); + self.next + } +} diff --git a/crates/bevy_ecs/src/relationship/relationship_source_collection.rs b/crates/bevy_ecs/src/relationship/relationship_source_collection.rs new file mode 100644 index 0000000000..0158f05f4b --- /dev/null +++ b/crates/bevy_ecs/src/relationship/relationship_source_collection.rs @@ -0,0 +1,51 @@ +use crate::entity::Entity; +use alloc::vec::Vec; + +/// The internal [`Entity`] collection used by a [`RelationshipTarget`](crate::relationship::RelationshipTarget) component. +/// This is not intended to be modified directly by users, as it could invalidate the correctness of relationships. +pub trait RelationshipSourceCollection { + /// Returns an instance with the given pre-allocated entity `capacity`. + fn with_capacity(capacity: usize) -> Self; + + /// Adds the given `entity` to the collection. + fn add(&mut self, entity: Entity); + + /// Removes the given `entity` from the collection. + fn remove(&mut self, entity: Entity); + + /// Iterates all entities in the collection. + fn iter(&self) -> impl DoubleEndedIterator; + + /// Returns the current length of the collection. + fn len(&self) -> usize; + + /// Returns true if the collection contains no entities. + #[inline] + fn is_empty(&self) -> bool { + self.len() == 0 + } +} + +impl RelationshipSourceCollection for Vec { + fn with_capacity(capacity: usize) -> Self { + Vec::with_capacity(capacity) + } + + fn add(&mut self, entity: Entity) { + Vec::push(self, entity); + } + + fn remove(&mut self, entity: Entity) { + if let Some(index) = <[Entity]>::iter(self).position(|e| *e == entity) { + Vec::remove(self, index); + } + } + + fn iter(&self) -> impl DoubleEndedIterator { + <[Entity]>::iter(self).copied() + } + + fn len(&self) -> usize { + Vec::len(self) + } +} diff --git a/crates/bevy_ecs/src/system/commands/entity_command.rs b/crates/bevy_ecs/src/system/commands/entity_command.rs index c81f657084..eea85ba4e4 100644 --- a/crates/bevy_ecs/src/system/commands/entity_command.rs +++ b/crates/bevy_ecs/src/system/commands/entity_command.rs @@ -254,8 +254,8 @@ pub fn retain() -> impl EntityCommand { /// /// # Note /// -/// This won't clean up external references to the entity (such as parent-child relationships -/// if you're using `bevy_hierarchy`), which may leave the world in an invalid state. +/// This will also despawn any [`Children`](crate::hierarchy::Children) entities, and any other [`RelationshipTarget`](crate::relationship::RelationshipTarget) that is configured +/// to despawn descendants. This results in "recursive despawn" behavior. pub fn despawn() -> impl EntityCommand { #[cfg(feature = "track_location")] let caller = Location::caller(); diff --git a/crates/bevy_ecs/src/system/commands/mod.rs b/crates/bevy_ecs/src/system/commands/mod.rs index 52f0417ee2..d68c1f5a67 100644 --- a/crates/bevy_ecs/src/system/commands/mod.rs +++ b/crates/bevy_ecs/src/system/commands/mod.rs @@ -1699,8 +1699,8 @@ impl<'a> EntityCommands<'a> { /// /// # Note /// - /// This won't clean up external references to the entity (such as parent-child relationships - /// if you're using `bevy_hierarchy`), which may leave the world in an invalid state. + /// This will also despawn the entities in any [`RelationshipTarget`](crate::relationship::RelationshipTarget) that is configured + /// to despawn descendants. For example, this will recursively despawn [`Children`](crate::hierarchy::Children). /// /// # Example /// @@ -1723,11 +1723,24 @@ impl<'a> EntityCommands<'a> { pub fn despawn(&mut self) { self.queue_handled(entity_command::despawn(), error_handler::warn()); } + /// Despawns the provided entity and its descendants. + #[deprecated( + since = "0.16.0", + note = "Use entity.despawn(), which now automatically despawns recursively." + )] + pub fn despawn_recursive(&mut self) { + self.despawn(); + } /// Despawns the entity. /// /// This will not emit a warning if the entity does not exist, essentially performing /// the same function as [`Self::despawn`] without emitting warnings. + /// + /// # Note + /// + /// This will also despawn the entities in any [`RelationshipTarget`](crate::relationship::RelationshipTarget) that are configured + /// to despawn descendants. For example, this will recursively despawn [`Children`](crate::hierarchy::Children). pub fn try_despawn(&mut self) { self.queue_handled(entity_command::despawn(), error_handler::silent()); } diff --git a/crates/bevy_ecs/src/traversal.rs b/crates/bevy_ecs/src/traversal.rs index a8605e94ec..342ad47849 100644 --- a/crates/bevy_ecs/src/traversal.rs +++ b/crates/bevy_ecs/src/traversal.rs @@ -1,6 +1,6 @@ //! A trait for components that let you traverse the ECS. -use crate::{entity::Entity, query::ReadOnlyQueryData}; +use crate::{entity::Entity, query::ReadOnlyQueryData, relationship::Relationship}; /// A component that can point to another entity, and which can be used to define a path through the ECS. /// @@ -30,3 +30,16 @@ impl Traversal for () { None } } + +/// This provides generalized hierarchy traversal for use in [event propagation]. +/// +/// # Warning +/// +/// Traversing in a loop could result in infinite loops for relationship graphs with loops. +/// +/// [event propagation]: crate::observer::Trigger::propagate +impl Traversal for &R { + fn traverse(item: Self::Item<'_>, _data: &D) -> Option { + Some(item.get()) + } +} diff --git a/crates/bevy_ecs/src/world/component_constants.rs b/crates/bevy_ecs/src/world/component_constants.rs index 5eea8dc622..fa941edb4c 100644 --- a/crates/bevy_ecs/src/world/component_constants.rs +++ b/crates/bevy_ecs/src/world/component_constants.rs @@ -13,6 +13,8 @@ pub const ON_INSERT: ComponentId = ComponentId::new(1); pub const ON_REPLACE: ComponentId = ComponentId::new(2); /// [`ComponentId`] for [`OnRemove`] pub const ON_REMOVE: ComponentId = ComponentId::new(3); +/// [`ComponentId`] for [`OnDespawn`] +pub const ON_DESPAWN: ComponentId = ComponentId::new(4); /// Trigger emitted when a component is added to an entity. See [`crate::component::ComponentHooks::on_add`] /// for more information. @@ -41,3 +43,10 @@ pub struct OnReplace; #[cfg_attr(feature = "bevy_reflect", derive(Reflect))] #[cfg_attr(feature = "bevy_reflect", reflect(Debug))] pub struct OnRemove; + +/// Trigger emitted for each component on an entity when it is despawned. See [`crate::component::ComponentHooks::on_despawn`] +/// for more information. +#[derive(Event, Debug)] +#[cfg_attr(feature = "bevy_reflect", derive(Reflect))] +#[cfg_attr(feature = "bevy_reflect", reflect(Debug))] +pub struct OnDespawn; diff --git a/crates/bevy_ecs/src/world/deferred_world.rs b/crates/bevy_ecs/src/world/deferred_world.rs index 1d28051fc5..3564b8eae1 100644 --- a/crates/bevy_ecs/src/world/deferred_world.rs +++ b/crates/bevy_ecs/src/world/deferred_world.rs @@ -580,6 +580,28 @@ impl<'w> DeferredWorld<'w> { } } + /// Triggers all `on_despawn` hooks for [`ComponentId`] in target. + /// + /// # Safety + /// Caller must ensure [`ComponentId`] in target exist in self. + #[inline] + pub(crate) unsafe fn trigger_on_despawn( + &mut self, + archetype: &Archetype, + entity: Entity, + targets: impl Iterator, + ) { + if archetype.has_despawn_hook() { + for component_id in targets { + // SAFETY: Caller ensures that these components exist + let hooks = unsafe { self.components().get_info_unchecked(component_id) }.hooks(); + if let Some(hook) = hooks.on_despawn { + hook(DeferredWorld { world: self.world }, entity, component_id); + } + } + } + } + /// Triggers all event observers for [`ComponentId`] in target. /// /// # Safety diff --git a/crates/bevy_ecs/src/world/entity_ref.rs b/crates/bevy_ecs/src/world/entity_ref.rs index ddbfa81e62..66018f7a4e 100644 --- a/crates/bevy_ecs/src/world/entity_ref.rs +++ b/crates/bevy_ecs/src/world/entity_ref.rs @@ -12,7 +12,10 @@ use crate::{ removal_detection::RemovedComponentEvents, storage::Storages, system::{IntoObserverSystem, Resource}, - world::{error::EntityComponentError, DeferredWorld, Mut, World}, + world::{ + error::EntityComponentError, unsafe_world_cell::UnsafeEntityCell, DeferredWorld, Mut, Ref, + World, ON_DESPAWN, ON_REMOVE, ON_REPLACE, + }, }; use alloc::vec::Vec; use bevy_ptr::{OwningPtr, Ptr}; @@ -28,8 +31,6 @@ use core::{ }; use thiserror::Error; -use super::{unsafe_world_cell::UnsafeEntityCell, Ref, ON_REMOVE, ON_REPLACE}; - /// A read-only reference to a particular [`Entity`] and all of its components. /// /// # Examples @@ -2095,6 +2096,11 @@ impl<'w> EntityWorldMut<'w> { /// /// See [`World::despawn`] for more details. /// + /// # Note + /// + /// This will also despawn any [`Children`](crate::hierarchy::Children) entities, and any other [`RelationshipTarget`](crate::relationship::RelationshipTarget) that is configured + /// to despawn descendants. This results in "recursive despawn" behavior. + /// /// # Panics /// /// If the entity has been despawned while this `EntityWorldMut` is still alive. @@ -2106,6 +2112,15 @@ impl<'w> EntityWorldMut<'w> { ); } + /// Despawns the provided entity and its descendants. + #[deprecated( + since = "0.16.0", + note = "Use entity.despawn(), which now automatically despawns recursively." + )] + pub fn despawn_recursive(self) { + self.despawn(); + } + pub(crate) fn despawn_with_caller( self, #[cfg(feature = "track_location")] caller: &'static Location, @@ -2123,6 +2138,10 @@ impl<'w> EntityWorldMut<'w> { // SAFETY: All components in the archetype exist in world unsafe { + if archetype.has_despawn_observer() { + deferred_world.trigger_observers(ON_DESPAWN, self.entity, archetype.components()); + } + deferred_world.trigger_on_despawn(archetype, self.entity, archetype.components()); if archetype.has_replace_observer() { deferred_world.trigger_observers(ON_REPLACE, self.entity, archetype.components()); } @@ -5272,7 +5291,8 @@ mod tests { .resource_mut::() .0 .push("OrdA hook on_insert"); - world.commands().entity(entity).despawn(); + world.commands().entity(entity).remove::(); + world.commands().entity(entity).remove::(); } fn ord_a_hook_on_replace(mut world: DeferredWorld, _entity: Entity, _id: ComponentId) { @@ -5380,12 +5400,12 @@ mod tests { "OrdB observer on_insert", "OrdB command on_add", // command added by OrdB hook on_add, needs to run before despawn command "OrdA observer on_replace", // start of despawn - "OrdB observer on_replace", "OrdA hook on_replace", - "OrdB hook on_replace", "OrdA observer on_remove", - "OrdB observer on_remove", "OrdA hook on_remove", + "OrdB observer on_replace", + "OrdB hook on_replace", + "OrdB observer on_remove", "OrdB hook on_remove", ]; world.flush(); diff --git a/crates/bevy_ecs/src/world/mod.rs b/crates/bevy_ecs/src/world/mod.rs index 4b8be6cc1f..1bac1df44d 100644 --- a/crates/bevy_ecs/src/world/mod.rs +++ b/crates/bevy_ecs/src/world/mod.rs @@ -159,6 +159,9 @@ impl World { let on_remove = OnRemove::register_component_id(self); assert_eq!(ON_REMOVE, on_remove); + + let on_despawn = OnDespawn::register_component_id(self); + assert_eq!(ON_DESPAWN, on_despawn); } /// Creates a new empty [`World`]. /// @@ -1080,19 +1083,26 @@ impl World { self.flush(); let change_tick = self.change_tick(); let entity = self.entities.alloc(); - let entity_location = { - let mut bundle_spawner = BundleSpawner::new::(self, change_tick); - // SAFETY: bundle's type matches `bundle_info`, entity is allocated but non-existent - unsafe { - bundle_spawner.spawn_non_existent( - entity, - bundle, - #[cfg(feature = "track_location")] - Location::caller(), - ) - } + let mut bundle_spawner = BundleSpawner::new::(self, change_tick); + // SAFETY: bundle's type matches `bundle_info`, entity is allocated but non-existent + let mut entity_location = unsafe { + bundle_spawner.spawn_non_existent( + entity, + bundle, + #[cfg(feature = "track_location")] + Location::caller(), + ) }; + // SAFETY: command_queue is not referenced anywhere else + if !unsafe { self.command_queue.is_empty() } { + self.flush_commands(); + entity_location = self + .entities() + .get(entity) + .unwrap_or(EntityLocation::INVALID); + } + #[cfg(feature = "track_location")] self.entities .set_spawned_or_despawned_by(entity.index(), Location::caller()); @@ -1266,8 +1276,8 @@ impl World { /// /// # Note /// - /// This won't clean up external references to the entity (such as parent-child relationships - /// if you're using `bevy_hierarchy`), which may leave the world in an invalid state. + /// This will also despawn the entities in any [`RelationshipTarget`](crate::relationship::RelationshipTarget) that is configured + /// to despawn descendants. For example, this will recursively despawn [`Children`](crate::hierarchy::Children). /// /// ``` /// use bevy_ecs::{component::Component, world::World}; diff --git a/crates/bevy_gltf/Cargo.toml b/crates/bevy_gltf/Cargo.toml index 167ed3c19d..023194f8a8 100644 --- a/crates/bevy_gltf/Cargo.toml +++ b/crates/bevy_gltf/Cargo.toml @@ -24,7 +24,6 @@ bevy_asset = { path = "../bevy_asset", version = "0.16.0-dev" } bevy_color = { path = "../bevy_color", version = "0.16.0-dev" } bevy_core_pipeline = { path = "../bevy_core_pipeline", version = "0.16.0-dev" } bevy_ecs = { path = "../bevy_ecs", version = "0.16.0-dev" } -bevy_hierarchy = { path = "../bevy_hierarchy", version = "0.16.0-dev" } bevy_image = { path = "../bevy_image", version = "0.16.0-dev" } bevy_math = { path = "../bevy_math", version = "0.16.0-dev" } bevy_pbr = { path = "../bevy_pbr", version = "0.16.0-dev" } diff --git a/crates/bevy_gltf/src/loader.rs b/crates/bevy_gltf/src/loader.rs index e83ed9351f..5b9810d184 100644 --- a/crates/bevy_gltf/src/loader.rs +++ b/crates/bevy_gltf/src/loader.rs @@ -11,10 +11,10 @@ use bevy_color::{Color, LinearRgba}; use bevy_core_pipeline::prelude::Camera3d; use bevy_ecs::{ entity::{Entity, EntityHashMap}, + hierarchy::ChildSpawner, name::Name, world::World, }; -use bevy_hierarchy::{BuildChildren, ChildBuild, WorldChildBuilder}; use bevy_image::{ CompressedImageFormats, Image, ImageAddressMode, ImageFilterMode, ImageLoaderSettings, ImageSampler, ImageSamplerDescriptor, ImageType, TextureError, @@ -1377,7 +1377,7 @@ fn warn_on_differing_texture_transforms( )] fn load_node( gltf_node: &Node, - world_builder: &mut WorldChildBuilder, + child_spawner: &mut ChildSpawner, root_load_context: &LoadContext, load_context: &mut LoadContext, settings: &GltfLoaderSettings, @@ -1399,7 +1399,7 @@ fn load_node( // of negative scale factors is odd. if so we will assign a copy of the material with face // culling inverted, rather than modifying the mesh data directly. let is_scale_inverted = world_transform.scale.is_negative_bitmask().count_ones() & 1 == 1; - let mut node = world_builder.spawn((transform, Visibility::default())); + let mut node = child_spawner.spawn((transform, Visibility::default())); let name = node_name(gltf_node); node.insert(name.clone()); diff --git a/crates/bevy_hierarchy/Cargo.toml b/crates/bevy_hierarchy/Cargo.toml deleted file mode 100644 index 076428a613..0000000000 --- a/crates/bevy_hierarchy/Cargo.toml +++ /dev/null @@ -1,66 +0,0 @@ -[package] -name = "bevy_hierarchy" -version = "0.16.0-dev" -edition = "2021" -description = "Provides hierarchy functionality for Bevy Engine" -homepage = "https://bevyengine.org" -repository = "https://github.com/bevyengine/bevy" -license = "MIT OR Apache-2.0" -keywords = ["bevy"] - -[features] -default = ["std", "bevy_app", "reflect"] - -# Functionality - -## Adds integration with the `bevy_app` plugin API. -bevy_app = ["dep:bevy_app"] - -## Adds runtime reflection support using `bevy_reflect`. -reflect = ["bevy_ecs/bevy_reflect", "bevy_reflect", "bevy_app?/bevy_reflect"] - -# Debugging Features - -## Enables `tracing` integration, allowing spans and other metrics to be reported -## through that framework. -trace = ["dep:tracing"] - -# Platform Compatibility - -## Allows access to the `std` crate. Enabling this feature will prevent compilation -## on `no_std` targets, but provides access to certain additional features on -## supported platforms. -std = [ - "bevy_app?/std", - "bevy_ecs/std", - "bevy_reflect/std", - "bevy_utils/std", - "disqualified/alloc", -] - -[dependencies] -# bevy -bevy_app = { path = "../bevy_app", version = "0.16.0-dev", default-features = false, optional = true } -bevy_ecs = { path = "../bevy_ecs", version = "0.16.0-dev", default-features = false } -bevy_reflect = { path = "../bevy_reflect", version = "0.16.0-dev", features = [ - "smallvec", -], default-features = false, optional = true } -bevy_utils = { path = "../bevy_utils", version = "0.16.0-dev", default-features = false, features = [ - "alloc", -] } -disqualified = { version = "1.0", default-features = false } - -# other -smallvec = { version = "1.11", default-features = false, features = [ - "union", - "const_generics", -] } -tracing = { version = "0.1", default-features = false, optional = true } -log = { version = "0.4", default-features = false } - -[lints] -workspace = true - -[package.metadata.docs.rs] -rustdoc-args = ["-Zunstable-options", "--generate-link-to-definition"] -all-features = true diff --git a/crates/bevy_hierarchy/README.md b/crates/bevy_hierarchy/README.md deleted file mode 100644 index 22d7802e9a..0000000000 --- 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 811c4f4eaa..0000000000 --- a/crates/bevy_hierarchy/src/child_builder.rs +++ /dev/null @@ -1,1222 +0,0 @@ -use crate::{Children, HierarchyEvent, Parent}; -use bevy_ecs::{ - bundle::Bundle, - entity::Entity, - event::Events, - system::{Command, Commands, EntityCommands}, - world::{EntityWorldMut, World}, -}; -use smallvec::{smallvec, SmallVec}; - -// Do not use `world.send_event_batch` as it prints error message when the Events are not available in the world, -// even though it's a valid use case to execute commands on a world without events. Loading a GLTF file for example -fn push_events(world: &mut World, events: impl IntoIterator) { - if let Some(mut moved) = world.get_resource_mut::>() { - moved.extend(events); - } -} - -/// Adds `child` to `parent`'s [`Children`], without checking if it is already present there. -/// -/// This might cause unexpected results when removing duplicate children. -fn add_child_unchecked(world: &mut World, parent: Entity, child: Entity) { - let mut parent = world.entity_mut(parent); - if let Some(mut children) = parent.get_mut::() { - children.0.push(child); - } else { - parent.insert(Children(smallvec![child])); - } -} - -/// Sets [`Parent`] of the `child` to `new_parent`. Inserts [`Parent`] if `child` doesn't have one. -fn update_parent(world: &mut World, child: Entity, new_parent: Entity) -> Option { - let mut child = world.entity_mut(child); - if let Some(mut parent) = child.get_mut::() { - let previous = parent.0; - *parent = Parent(new_parent); - Some(previous) - } else { - child.insert(Parent(new_parent)); - None - } -} - -/// Remove child from the parent's [`Children`] component. -/// -/// Removes the [`Children`] component from the parent if it's empty. -fn remove_from_children(world: &mut World, parent: Entity, child: Entity) { - let Ok(mut parent) = world.get_entity_mut(parent) else { - return; - }; - let Some(mut children) = parent.get_mut::() else { - return; - }; - children.0.retain(|x| *x != child); - if children.is_empty() { - parent.remove::(); - } -} - -/// Update the [`Parent`] component of the `child`. -/// Removes the `child` from the previous parent's [`Children`]. -/// -/// Does not update the new parents [`Children`] component. -/// -/// Does nothing if `child` was already a child of `parent`. -/// -/// Sends [`HierarchyEvent`]'s. -fn update_old_parent(world: &mut World, child: Entity, parent: Entity) { - let previous = update_parent(world, child, parent); - if let Some(previous_parent) = previous { - // Do nothing if the child was already parented to this entity. - if previous_parent == parent { - return; - } - remove_from_children(world, previous_parent, child); - - push_events( - world, - [HierarchyEvent::ChildMoved { - child, - previous_parent, - new_parent: parent, - }], - ); - } else { - push_events(world, [HierarchyEvent::ChildAdded { child, parent }]); - } -} - -/// Update the [`Parent`] components of the `children`. -/// Removes the `children` from their previous parent's [`Children`]. -/// -/// Does not update the new parents [`Children`] component. -/// -/// Does nothing for a child if it was already a child of `parent`. -/// -/// Sends [`HierarchyEvent`]'s. -fn update_old_parents(world: &mut World, parent: Entity, children: &[Entity]) { - let mut events: SmallVec<[HierarchyEvent; 8]> = SmallVec::with_capacity(children.len()); - for &child in children { - if let Some(previous) = update_parent(world, child, parent) { - // Do nothing if the entity already has the correct parent. - if parent == previous { - continue; - } - - remove_from_children(world, previous, child); - events.push(HierarchyEvent::ChildMoved { - child, - previous_parent: previous, - new_parent: parent, - }); - } else { - events.push(HierarchyEvent::ChildAdded { child, parent }); - } - } - push_events(world, events); -} - -/// Removes entities in `children` from `parent`'s [`Children`], removing the component if it ends up empty. -/// Also removes [`Parent`] component from `children`. -fn remove_children(parent: Entity, children: &[Entity], world: &mut World) { - let mut events: SmallVec<[HierarchyEvent; 8]> = SmallVec::new(); - if let Some(parent_children) = world.get::(parent) { - for &child in children { - if parent_children.contains(&child) { - events.push(HierarchyEvent::ChildRemoved { child, parent }); - } - } - } else { - return; - } - for event in &events { - if let &HierarchyEvent::ChildRemoved { child, .. } = event { - world.entity_mut(child).remove::(); - } - } - push_events(world, events); - - let mut parent = world.entity_mut(parent); - if let Some(mut parent_children) = parent.get_mut::() { - parent_children - .0 - .retain(|parent_child| !children.contains(parent_child)); - - if parent_children.is_empty() { - parent.remove::(); - } - } -} - -/// Struct for building children entities and adding them to a parent entity. -/// -/// # Example -/// -/// This example creates three entities, a parent and two children. -/// -/// ``` -/// # use bevy_ecs::bundle::Bundle; -/// # use bevy_ecs::system::Commands; -/// # use bevy_hierarchy::{ChildBuild, BuildChildren}; -/// # #[derive(Bundle)] -/// # struct MyBundle {} -/// # #[derive(Bundle)] -/// # struct MyChildBundle {} -/// # -/// # fn test(mut commands: Commands) { -/// commands.spawn(MyBundle {}).with_children(|child_builder| { -/// child_builder.spawn(MyChildBundle {}); -/// child_builder.spawn(MyChildBundle {}); -/// }); -/// # } -/// ``` -pub struct ChildBuilder<'a> { - commands: Commands<'a, 'a>, - children: SmallVec<[Entity; 8]>, - parent: Entity, -} - -/// Trait for building children entities and adding them to a parent entity. This is used in -/// implementations of [`BuildChildren`] as a bound on the [`Builder`](BuildChildren::Builder) -/// associated type. The closure passed to [`BuildChildren::with_children`] accepts an -/// implementation of `ChildBuild` so that children can be spawned via [`ChildBuild::spawn`]. -pub trait ChildBuild { - /// Spawn output type. Both [`spawn`](Self::spawn) and [`spawn_empty`](Self::spawn_empty) return - /// an implementation of this type so that children can be operated on via method-chaining. - /// Implementations of `ChildBuild` reborrow `self` when spawning entities (see - /// [`Commands::spawn_empty`] and [`World::get_entity_mut`]). Lifetime `'a` corresponds to this - /// reborrowed self, and `Self` outlives it. - type SpawnOutput<'a>: BuildChildren - where - Self: 'a; - - /// Spawns an entity with the given bundle and inserts it into the parent entity's [`Children`]. - /// Also adds [`Parent`] component to the created entity. - fn spawn(&mut self, bundle: impl Bundle) -> Self::SpawnOutput<'_>; - - /// Spawns an [`Entity`] with no components and inserts it into the parent entity's [`Children`]. - /// Also adds [`Parent`] component to the created entity. - fn spawn_empty(&mut self) -> Self::SpawnOutput<'_>; - - /// Returns the parent entity. - fn parent_entity(&self) -> Entity; - - /// Adds a command to be executed, like [`Commands::queue`]. - fn queue_command(&mut self, command: C) -> &mut Self; -} - -impl ChildBuild for ChildBuilder<'_> { - type SpawnOutput<'a> - = EntityCommands<'a> - where - Self: 'a; - - fn spawn(&mut self, bundle: impl Bundle) -> EntityCommands { - let e = self.commands.spawn(bundle); - self.children.push(e.id()); - e - } - - fn spawn_empty(&mut self) -> EntityCommands { - let e = self.commands.spawn_empty(); - self.children.push(e.id()); - e - } - - fn parent_entity(&self) -> Entity { - self.parent - } - - fn queue_command(&mut self, command: C) -> &mut Self { - self.commands.queue(command); - self - } -} - -/// Trait for removing, adding and replacing children and parents of an entity. -pub trait BuildChildren { - /// Child builder type. - type Builder<'a>: ChildBuild; - - /// Takes a closure which builds children for this entity using [`ChildBuild`]. - /// - /// For convenient spawning of a single child, you can use [`with_child`]. - /// - /// [`with_child`]: BuildChildren::with_child - fn with_children(&mut self, f: impl FnOnce(&mut Self::Builder<'_>)) -> &mut Self; - - /// Spawns the passed bundle and adds it to this entity as a child. - /// - /// The bundle's [`Parent`] component will be updated to the new parent. - /// - /// For efficient spawning of multiple children, use [`with_children`]. - /// - /// [`with_children`]: BuildChildren::with_children - fn with_child(&mut self, bundle: B) -> &mut Self; - - /// Pushes children to the back of the builder's children. For any entities that are - /// already a child of this one, this method does nothing. - /// - /// The children's [`Parent`] component will be updated to the new parent. - /// - /// If the children were previously children of another parent, that parent's [`Children`] component - /// will have those children removed from its list. Removing all children from a parent causes its - /// [`Children`] component to be removed from the entity. - /// - /// # Panics - /// - /// Panics if any of the children are the same as the parent. - fn add_children(&mut self, children: &[Entity]) -> &mut Self; - - /// Inserts children at the given index. - /// - /// The children's [`Parent`] component will be updated to the new parent. - /// - /// If the children were previously children of another parent, that parent's [`Children`] component - /// will have those children removed from its list. Removing all children from a parent causes its - /// [`Children`] component to be removed from the entity. - /// - /// # Panics - /// - /// Panics if any of the children are the same as the parent. - fn insert_children(&mut self, index: usize, children: &[Entity]) -> &mut Self; - - /// Removes the given children. - /// - /// The removed children will have their [`Parent`] component removed. - /// - /// Removing all children from a parent causes its [`Children`] component to be removed from the entity. - fn remove_children(&mut self, children: &[Entity]) -> &mut Self; - - /// Adds a single child. - /// - /// The child's [`Parent`] component will be updated to the new parent. - /// - /// If the child was previously the child of another parent, that parent's [`Children`] component - /// will have the child removed from its list. Removing all children from a parent causes its - /// [`Children`] component to be removed from the entity. - /// - /// # Panics - /// - /// Panics if the child is the same as the parent. - fn add_child(&mut self, child: Entity) -> &mut Self; - - /// Removes all children from this entity. The [`Children`] component and the children's [`Parent`] component will be removed. - /// If the [`Children`] component is not present, this has no effect. - fn clear_children(&mut self) -> &mut Self; - - /// Removes all current children from this entity, replacing them with the specified list of entities. - /// - /// The added children's [`Parent`] component will be updated to the new parent. - /// The removed children will have their [`Parent`] component removed. - /// - /// # Panics - /// - /// Panics if any of the children are the same as the parent. - fn replace_children(&mut self, children: &[Entity]) -> &mut Self; - - /// Sets the parent of this entity. - /// - /// If this entity already had a parent, the parent's [`Children`] component will have this - /// child removed from its list. Removing all children from a parent causes its [`Children`] - /// component to be removed from the entity. - /// - /// # Panics - /// - /// Panics if the parent is the same as the child. - fn set_parent(&mut self, parent: Entity) -> &mut Self; - - /// Removes the [`Parent`] of this entity. - /// - /// Also removes this entity from its parent's [`Children`] component. Removing all children from a parent causes - /// its [`Children`] component to be removed from the entity. - fn remove_parent(&mut self) -> &mut Self; -} - -impl BuildChildren for EntityCommands<'_> { - type Builder<'a> = ChildBuilder<'a>; - - fn with_children(&mut self, spawn_children: impl FnOnce(&mut Self::Builder<'_>)) -> &mut Self { - let parent = self.id(); - let mut builder = ChildBuilder { - commands: self.commands(), - children: SmallVec::default(), - parent, - }; - - spawn_children(&mut builder); - - let children = builder.children; - if children.contains(&parent) { - panic!("Entity cannot be a child of itself."); - } - self.queue(move |mut entity: EntityWorldMut| { - entity.add_children(&children); - }) - } - - fn with_child(&mut self, bundle: B) -> &mut Self { - let child = self.commands().spawn(bundle).id(); - self.queue(move |mut entity: EntityWorldMut| { - entity.add_child(child); - }) - } - - fn add_children(&mut self, children: &[Entity]) -> &mut Self { - let parent = self.id(); - if children.contains(&parent) { - panic!("Cannot add entity as a child of itself."); - } - let children = SmallVec::<[Entity; 8]>::from_slice(children); - self.queue(move |mut entity: EntityWorldMut| { - entity.add_children(&children); - }) - } - - fn insert_children(&mut self, index: usize, children: &[Entity]) -> &mut Self { - let parent = self.id(); - if children.contains(&parent) { - panic!("Cannot insert entity as a child of itself."); - } - let children = SmallVec::<[Entity; 8]>::from_slice(children); - self.queue(move |mut entity: EntityWorldMut| { - entity.insert_children(index, &children); - }) - } - - fn remove_children(&mut self, children: &[Entity]) -> &mut Self { - let children = SmallVec::<[Entity; 8]>::from_slice(children); - self.queue(move |mut entity: EntityWorldMut| { - entity.remove_children(&children); - }) - } - - fn add_child(&mut self, child: Entity) -> &mut Self { - let parent = self.id(); - if child == parent { - panic!("Cannot add entity as a child of itself."); - } - self.queue(move |mut entity: EntityWorldMut| { - entity.add_child(child); - }) - } - - fn clear_children(&mut self) -> &mut Self { - self.queue(move |mut entity: EntityWorldMut| { - entity.clear_children(); - }) - } - - fn replace_children(&mut self, children: &[Entity]) -> &mut Self { - let parent = self.id(); - if children.contains(&parent) { - panic!("Cannot replace entity as a child of itself."); - } - let children = SmallVec::<[Entity; 8]>::from_slice(children); - self.queue(move |mut entity: EntityWorldMut| { - entity.replace_children(&children); - }) - } - - fn set_parent(&mut self, parent: Entity) -> &mut Self { - let child = self.id(); - if child == parent { - panic!("Cannot set parent to itself"); - } - self.queue(move |mut entity: EntityWorldMut| { - entity.world_scope(|world| { - world.entity_mut(parent).add_child(child); - }); - }) - } - - fn remove_parent(&mut self) -> &mut Self { - self.queue(move |mut entity: EntityWorldMut| { - entity.remove_parent(); - }) - } -} - -/// Struct for adding children to an entity directly through the [`World`] for use in exclusive systems. -#[derive(Debug)] -pub struct WorldChildBuilder<'w> { - world: &'w mut World, - parent: Entity, -} - -impl ChildBuild for WorldChildBuilder<'_> { - type SpawnOutput<'a> - = EntityWorldMut<'a> - where - Self: 'a; - - fn spawn(&mut self, bundle: impl Bundle) -> EntityWorldMut { - let entity = self.world.spawn((bundle, Parent(self.parent))).id(); - add_child_unchecked(self.world, self.parent, entity); - push_events( - self.world, - [HierarchyEvent::ChildAdded { - child: entity, - parent: self.parent, - }], - ); - self.world.entity_mut(entity) - } - - fn spawn_empty(&mut self) -> EntityWorldMut { - self.spawn(()) - } - - fn parent_entity(&self) -> Entity { - self.parent - } - - fn queue_command(&mut self, command: C) -> &mut Self { - self.world.commands().queue(command); - self - } -} - -impl WorldChildBuilder<'_> { - /// Calls the world's [`World::flush`] to apply any commands - /// queued by [`Self::queue_command`]. - pub fn flush_world(&mut self) { - self.world.flush(); - } -} - -impl BuildChildren for EntityWorldMut<'_> { - type Builder<'a> = WorldChildBuilder<'a>; - - fn with_children(&mut self, spawn_children: impl FnOnce(&mut WorldChildBuilder)) -> &mut Self { - let parent = self.id(); - self.world_scope(|world| { - spawn_children(&mut WorldChildBuilder { world, parent }); - }); - self - } - - fn with_child(&mut self, bundle: B) -> &mut Self { - let parent = self.id(); - let child = self.world_scope(|world| world.spawn((bundle, Parent(parent))).id()); - if let Some(mut children_component) = self.get_mut::() { - children_component.0.retain(|value| child != *value); - children_component.0.push(child); - } else { - self.insert(Children::from_entities(&[child])); - } - self - } - - fn add_child(&mut self, child: Entity) -> &mut Self { - let parent = self.id(); - if child == parent { - panic!("Cannot add entity as a child of itself."); - } - self.world_scope(|world| { - update_old_parent(world, child, parent); - }); - if let Some(mut children_component) = self.get_mut::() { - children_component.0.retain(|value| child != *value); - children_component.0.push(child); - } else { - self.insert(Children::from_entities(&[child])); - } - self - } - - fn add_children(&mut self, children: &[Entity]) -> &mut Self { - if children.is_empty() { - return self; - } - - let parent = self.id(); - if children.contains(&parent) { - panic!("Cannot push entity as a child of itself."); - } - self.world_scope(|world| { - update_old_parents(world, parent, children); - }); - if let Some(mut children_component) = self.get_mut::() { - children_component - .0 - .retain(|value| !children.contains(value)); - children_component.0.extend(children.iter().cloned()); - } else { - self.insert(Children::from_entities(children)); - } - self - } - - fn insert_children(&mut self, index: usize, children: &[Entity]) -> &mut Self { - let parent = self.id(); - if children.contains(&parent) { - panic!("Cannot insert entity as a child of itself."); - } - self.world_scope(|world| { - update_old_parents(world, parent, children); - }); - if let Some(mut children_component) = self.get_mut::() { - children_component - .0 - .retain(|value| !children.contains(value)); - children_component.0.insert_from_slice(index, children); - } else { - self.insert(Children::from_entities(children)); - } - self - } - - fn remove_children(&mut self, children: &[Entity]) -> &mut Self { - let parent = self.id(); - self.world_scope(|world| { - remove_children(parent, children, world); - }); - self - } - - fn set_parent(&mut self, parent: Entity) -> &mut Self { - let child = self.id(); - self.world_scope(|world| { - world.entity_mut(parent).add_child(child); - }); - self - } - - fn remove_parent(&mut self) -> &mut Self { - let child = self.id(); - if let Some(parent) = self.take::().map(|p| p.get()) { - self.world_scope(|world| { - remove_from_children(world, parent, child); - push_events(world, [HierarchyEvent::ChildRemoved { child, parent }]); - }); - } - self - } - - fn clear_children(&mut self) -> &mut Self { - let parent = self.id(); - self.world_scope(|world| { - if let Some(children) = world.entity_mut(parent).take::() { - for &child in &children.0 { - world.entity_mut(child).remove::(); - } - } - }); - self - } - - fn replace_children(&mut self, children: &[Entity]) -> &mut Self { - self.clear_children().add_children(children) - } -} - -#[cfg(test)] -mod tests { - use super::{BuildChildren, ChildBuild}; - use crate::{ - components::{Children, Parent}, - HierarchyEvent::{self, ChildAdded, ChildMoved, ChildRemoved}, - }; - use alloc::{vec, vec::Vec}; - use smallvec::{smallvec, SmallVec}; - - use bevy_ecs::{ - component::Component, - entity::Entity, - event::Events, - system::Commands, - world::{CommandQueue, World}, - }; - - /// Assert the (non)existence and state of the child's [`Parent`] component. - fn assert_parent(world: &World, child: Entity, parent: Option) { - assert_eq!(world.get::(child).map(Parent::get), parent); - } - - /// Assert the (non)existence and state of the parent's [`Children`] component. - fn assert_children(world: &World, parent: Entity, children: Option<&[Entity]>) { - assert_eq!(world.get::(parent).map(|c| &**c), children); - } - - /// Assert the number of children in the parent's [`Children`] component if it exists. - fn assert_num_children(world: &World, parent: Entity, num_children: usize) { - assert_eq!( - world.get::(parent).map(|c| c.len()).unwrap_or(0), - num_children - ); - } - - /// Used to omit a number of events that are not relevant to a particular test. - fn omit_events(world: &mut World, number: usize) { - let mut events_resource = world.resource_mut::>(); - let mut events: Vec<_> = events_resource.drain().collect(); - events_resource.extend(events.drain(number..)); - } - - fn assert_events(world: &mut World, expected_events: &[HierarchyEvent]) { - let events: Vec<_> = world - .resource_mut::>() - .drain() - .collect(); - assert_eq!(events, expected_events); - } - - #[test] - fn add_child() { - let world = &mut World::new(); - world.insert_resource(Events::::default()); - - let [a, b, c, d] = core::array::from_fn(|_| world.spawn_empty().id()); - - world.entity_mut(a).add_child(b); - - assert_parent(world, b, Some(a)); - assert_children(world, a, Some(&[b])); - assert_events( - world, - &[ChildAdded { - child: b, - parent: a, - }], - ); - - world.entity_mut(a).add_child(c); - - assert_children(world, a, Some(&[b, c])); - assert_parent(world, c, Some(a)); - assert_events( - world, - &[ChildAdded { - child: c, - parent: a, - }], - ); - // Children component should be removed when it's empty. - world.entity_mut(d).add_child(b).add_child(c); - assert_children(world, a, None); - } - - #[test] - fn set_parent() { - let world = &mut World::new(); - world.insert_resource(Events::::default()); - - let [a, b, c] = core::array::from_fn(|_| world.spawn_empty().id()); - - world.entity_mut(a).set_parent(b); - - assert_parent(world, a, Some(b)); - assert_children(world, b, Some(&[a])); - assert_events( - world, - &[ChildAdded { - child: a, - parent: b, - }], - ); - - world.entity_mut(a).set_parent(c); - - assert_parent(world, a, Some(c)); - assert_children(world, b, None); - assert_children(world, c, Some(&[a])); - assert_events( - world, - &[ChildMoved { - child: a, - previous_parent: b, - new_parent: c, - }], - ); - } - - // regression test for https://github.com/bevyengine/bevy/pull/8346 - #[test] - fn set_parent_of_orphan() { - let world = &mut World::new(); - - let [a, b, c] = core::array::from_fn(|_| world.spawn_empty().id()); - world.entity_mut(a).set_parent(b); - assert_parent(world, a, Some(b)); - assert_children(world, b, Some(&[a])); - - world.entity_mut(b).despawn(); - world.entity_mut(a).set_parent(c); - - assert_parent(world, a, Some(c)); - assert_children(world, c, Some(&[a])); - } - - #[test] - fn remove_parent() { - let world = &mut World::new(); - world.insert_resource(Events::::default()); - - let [a, b, c] = core::array::from_fn(|_| world.spawn_empty().id()); - - world.entity_mut(a).add_children(&[b, c]); - world.entity_mut(b).remove_parent(); - - assert_parent(world, b, None); - assert_parent(world, c, Some(a)); - assert_children(world, a, Some(&[c])); - omit_events(world, 2); // Omit ChildAdded events. - assert_events( - world, - &[ChildRemoved { - child: b, - parent: a, - }], - ); - - world.entity_mut(c).remove_parent(); - assert_parent(world, c, None); - assert_children(world, a, None); - assert_events( - world, - &[ChildRemoved { - child: c, - parent: a, - }], - ); - } - - #[expect( - dead_code, - reason = "The inner field is used to differentiate the different instances of this struct." - )] - #[derive(Component)] - struct C(u32); - - #[test] - fn build_children() { - let mut world = World::default(); - let mut queue = CommandQueue::default(); - let mut commands = Commands::new(&mut queue, &world); - - let parent = commands.spawn(C(1)).id(); - let mut children = Vec::new(); - commands.entity(parent).with_children(|parent| { - children.extend([ - parent.spawn(C(2)).id(), - parent.spawn(C(3)).id(), - parent.spawn(C(4)).id(), - ]); - }); - - queue.apply(&mut world); - assert_eq!( - world.get::(parent).unwrap().0.as_slice(), - children.as_slice(), - ); - assert_eq!(*world.get::(children[0]).unwrap(), Parent(parent)); - assert_eq!(*world.get::(children[1]).unwrap(), Parent(parent)); - - assert_eq!(*world.get::(children[0]).unwrap(), Parent(parent)); - assert_eq!(*world.get::(children[1]).unwrap(), Parent(parent)); - } - - #[test] - fn build_child() { - let mut world = World::default(); - let mut queue = CommandQueue::default(); - let mut commands = Commands::new(&mut queue, &world); - - let parent = commands.spawn(C(1)).id(); - commands.entity(parent).with_child(C(2)); - - queue.apply(&mut world); - assert_eq!(world.get::(parent).unwrap().0.len(), 1); - } - - #[test] - fn push_and_insert_and_remove_children_commands() { - let mut world = World::default(); - let entities = world - .spawn_batch(vec![C(1), C(2), C(3), C(4), C(5)]) - .collect::>(); - - let mut queue = CommandQueue::default(); - { - let mut commands = Commands::new(&mut queue, &world); - commands.entity(entities[0]).add_children(&entities[1..3]); - } - queue.apply(&mut world); - - let parent = entities[0]; - let child1 = entities[1]; - let child2 = entities[2]; - let child3 = entities[3]; - let child4 = entities[4]; - - let expected_children: SmallVec<[Entity; 8]> = smallvec![child1, child2]; - assert_eq!( - world.get::(parent).unwrap().0.clone(), - expected_children - ); - assert_eq!(*world.get::(child1).unwrap(), Parent(parent)); - assert_eq!(*world.get::(child2).unwrap(), Parent(parent)); - - assert_eq!(*world.get::(child1).unwrap(), Parent(parent)); - assert_eq!(*world.get::(child2).unwrap(), Parent(parent)); - - { - let mut commands = Commands::new(&mut queue, &world); - commands.entity(parent).insert_children(1, &entities[3..]); - } - queue.apply(&mut world); - - let expected_children: SmallVec<[Entity; 8]> = smallvec![child1, child3, child4, child2]; - assert_eq!( - world.get::(parent).unwrap().0.clone(), - expected_children - ); - assert_eq!(*world.get::(child3).unwrap(), Parent(parent)); - assert_eq!(*world.get::(child4).unwrap(), Parent(parent)); - - let remove_children = [child1, child4]; - { - let mut commands = Commands::new(&mut queue, &world); - commands.entity(parent).remove_children(&remove_children); - } - queue.apply(&mut world); - - let expected_children: SmallVec<[Entity; 8]> = smallvec![child3, child2]; - assert_eq!( - world.get::(parent).unwrap().0.clone(), - expected_children - ); - assert!(world.get::(child1).is_none()); - assert!(world.get::(child4).is_none()); - } - - #[test] - fn push_and_clear_children_commands() { - let mut world = World::default(); - let entities = world - .spawn_batch(vec![C(1), C(2), C(3), C(4), C(5)]) - .collect::>(); - - let mut queue = CommandQueue::default(); - { - let mut commands = Commands::new(&mut queue, &world); - commands.entity(entities[0]).add_children(&entities[1..3]); - } - queue.apply(&mut world); - - let parent = entities[0]; - let child1 = entities[1]; - let child2 = entities[2]; - - let expected_children: SmallVec<[Entity; 8]> = smallvec![child1, child2]; - assert_eq!( - world.get::(parent).unwrap().0.clone(), - expected_children - ); - assert_eq!(*world.get::(child1).unwrap(), Parent(parent)); - assert_eq!(*world.get::(child2).unwrap(), Parent(parent)); - - { - let mut commands = Commands::new(&mut queue, &world); - commands.entity(parent).clear_children(); - } - queue.apply(&mut world); - - assert!(world.get::(parent).is_none()); - - assert!(world.get::(child1).is_none()); - assert!(world.get::(child2).is_none()); - } - - #[test] - fn push_and_replace_children_commands() { - let mut world = World::default(); - let entities = world - .spawn_batch(vec![C(1), C(2), C(3), C(4), C(5)]) - .collect::>(); - - let mut queue = CommandQueue::default(); - { - let mut commands = Commands::new(&mut queue, &world); - commands.entity(entities[0]).add_children(&entities[1..3]); - } - queue.apply(&mut world); - - let parent = entities[0]; - let child1 = entities[1]; - let child2 = entities[2]; - let child4 = entities[4]; - - let expected_children: SmallVec<[Entity; 8]> = smallvec![child1, child2]; - assert_eq!( - world.get::(parent).unwrap().0.clone(), - expected_children - ); - assert_eq!(*world.get::(child1).unwrap(), Parent(parent)); - assert_eq!(*world.get::(child2).unwrap(), Parent(parent)); - - let replace_children = [child1, child4]; - { - let mut commands = Commands::new(&mut queue, &world); - commands.entity(parent).replace_children(&replace_children); - } - queue.apply(&mut world); - - let expected_children: SmallVec<[Entity; 8]> = smallvec![child1, child4]; - assert_eq!( - world.get::(parent).unwrap().0.clone(), - expected_children - ); - assert_eq!(*world.get::(child1).unwrap(), Parent(parent)); - assert_eq!(*world.get::(child4).unwrap(), Parent(parent)); - assert!(world.get::(child2).is_none()); - } - - #[test] - fn push_and_insert_and_remove_children_world() { - let mut world = World::default(); - let entities = world - .spawn_batch(vec![C(1), C(2), C(3), C(4), C(5)]) - .collect::>(); - - world.entity_mut(entities[0]).add_children(&entities[1..3]); - - let parent = entities[0]; - let child1 = entities[1]; - let child2 = entities[2]; - let child3 = entities[3]; - let child4 = entities[4]; - - let expected_children: SmallVec<[Entity; 8]> = smallvec![child1, child2]; - assert_eq!( - world.get::(parent).unwrap().0.clone(), - expected_children - ); - assert_eq!(*world.get::(child1).unwrap(), Parent(parent)); - assert_eq!(*world.get::(child2).unwrap(), Parent(parent)); - - world.entity_mut(parent).insert_children(1, &entities[3..]); - let expected_children: SmallVec<[Entity; 8]> = smallvec![child1, child3, child4, child2]; - assert_eq!( - world.get::(parent).unwrap().0.clone(), - expected_children - ); - assert_eq!(*world.get::(child3).unwrap(), Parent(parent)); - assert_eq!(*world.get::(child4).unwrap(), Parent(parent)); - - let remove_children = [child1, child4]; - world.entity_mut(parent).remove_children(&remove_children); - let expected_children: SmallVec<[Entity; 8]> = smallvec![child3, child2]; - assert_eq!( - world.get::(parent).unwrap().0.clone(), - expected_children - ); - assert!(world.get::(child1).is_none()); - assert!(world.get::(child4).is_none()); - } - - #[test] - fn push_and_insert_and_clear_children_world() { - let mut world = World::default(); - let entities = world - .spawn_batch(vec![C(1), C(2), C(3)]) - .collect::>(); - - world.entity_mut(entities[0]).add_children(&entities[1..3]); - - let parent = entities[0]; - let child1 = entities[1]; - let child2 = entities[2]; - - let expected_children: SmallVec<[Entity; 8]> = smallvec![child1, child2]; - assert_eq!( - world.get::(parent).unwrap().0.clone(), - expected_children - ); - assert_eq!(*world.get::(child1).unwrap(), Parent(parent)); - assert_eq!(*world.get::(child2).unwrap(), Parent(parent)); - - world.entity_mut(parent).clear_children(); - assert!(world.get::(parent).is_none()); - assert!(world.get::(child1).is_none()); - assert!(world.get::(child2).is_none()); - } - - #[test] - fn push_and_replace_children_world() { - let mut world = World::default(); - let entities = world - .spawn_batch(vec![C(1), C(2), C(3), C(4), C(5)]) - .collect::>(); - - world.entity_mut(entities[0]).add_children(&entities[1..3]); - - let parent = entities[0]; - let child1 = entities[1]; - let child2 = entities[2]; - let child3 = entities[3]; - let child4 = entities[4]; - - let expected_children: SmallVec<[Entity; 8]> = smallvec![child1, child2]; - assert_eq!( - world.get::(parent).unwrap().0.clone(), - expected_children - ); - assert_eq!(*world.get::(child1).unwrap(), Parent(parent)); - assert_eq!(*world.get::(child2).unwrap(), Parent(parent)); - - world.entity_mut(parent).replace_children(&entities[2..]); - let expected_children: SmallVec<[Entity; 8]> = smallvec![child2, child3, child4]; - assert_eq!( - world.get::(parent).unwrap().0.clone(), - expected_children - ); - assert!(world.get::(child1).is_none()); - assert_eq!(*world.get::(child2).unwrap(), Parent(parent)); - assert_eq!(*world.get::(child3).unwrap(), Parent(parent)); - assert_eq!(*world.get::(child4).unwrap(), Parent(parent)); - } - - /// Tests what happens when all children are removed from a parent using world functions - #[test] - fn children_removed_when_empty_world() { - let mut world = World::default(); - let entities = world - .spawn_batch(vec![C(1), C(2), C(3)]) - .collect::>(); - - let parent1 = entities[0]; - let parent2 = entities[1]; - let child = entities[2]; - - // add child into parent1 - world.entity_mut(parent1).add_children(&[child]); - assert_eq!( - world.get::(parent1).unwrap().0.as_slice(), - &[child] - ); - - // move only child from parent1 with `add_children` - world.entity_mut(parent2).add_children(&[child]); - assert!(world.get::(parent1).is_none()); - - // move only child from parent2 with `insert_children` - world.entity_mut(parent1).insert_children(0, &[child]); - assert!(world.get::(parent2).is_none()); - - // remove only child from parent1 with `remove_children` - world.entity_mut(parent1).remove_children(&[child]); - assert!(world.get::(parent1).is_none()); - } - - /// Tests what happens when all children are removed form a parent using commands - #[test] - fn children_removed_when_empty_commands() { - let mut world = World::default(); - let entities = world - .spawn_batch(vec![C(1), C(2), C(3)]) - .collect::>(); - - let parent1 = entities[0]; - let parent2 = entities[1]; - let child = entities[2]; - - let mut queue = CommandQueue::default(); - - // add child into parent1 - { - let mut commands = Commands::new(&mut queue, &world); - commands.entity(parent1).add_children(&[child]); - queue.apply(&mut world); - } - assert_eq!( - world.get::(parent1).unwrap().0.as_slice(), - &[child] - ); - - // move only child from parent1 with `add_children` - { - let mut commands = Commands::new(&mut queue, &world); - commands.entity(parent2).add_children(&[child]); - queue.apply(&mut world); - } - assert!(world.get::(parent1).is_none()); - - // move only child from parent2 with `insert_children` - { - let mut commands = Commands::new(&mut queue, &world); - commands.entity(parent1).insert_children(0, &[child]); - queue.apply(&mut world); - } - assert!(world.get::(parent2).is_none()); - - // move only child from parent1 with `add_child` - { - let mut commands = Commands::new(&mut queue, &world); - commands.entity(parent2).add_child(child); - queue.apply(&mut world); - } - assert!(world.get::(parent1).is_none()); - - // remove only child from parent2 with `remove_children` - { - let mut commands = Commands::new(&mut queue, &world); - commands.entity(parent2).remove_children(&[child]); - queue.apply(&mut world); - } - assert!(world.get::(parent2).is_none()); - } - - #[test] - fn regression_add_children_same_archetype() { - let mut world = World::new(); - let child = world.spawn_empty().id(); - world.spawn_empty().add_children(&[child]); - } - - #[test] - fn add_children_idempotent() { - let mut world = World::new(); - let child = world.spawn_empty().id(); - let parent = world - .spawn_empty() - .add_children(&[child]) - .add_children(&[child]) - .id(); - - let mut query = world.query::<&Children>(); - let children = query.get(&world, parent).unwrap(); - assert_eq!(**children, [child]); - } - - #[test] - fn add_children_does_not_insert_empty_children() { - let mut world = World::new(); - let parent = world.spawn_empty().add_children(&[]).id(); - - let mut query = world.query::<&Children>(); - let children = query.get(&world, parent); - assert!(children.is_err()); - } - - #[test] - fn with_child() { - let world = &mut World::new(); - world.insert_resource(Events::::default()); - - let a = world.spawn_empty().id(); - let b = (); - let c = (); - let d = (); - - world.entity_mut(a).with_child(b); - - assert_num_children(world, a, 1); - - world.entity_mut(a).with_child(c).with_child(d); - - assert_num_children(world, a, 3); - } -} diff --git a/crates/bevy_hierarchy/src/components/children.rs b/crates/bevy_hierarchy/src/components/children.rs deleted file mode 100644 index 4780d31eb2..0000000000 --- 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 3c8b544850..0000000000 --- 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 4fc97aa914..0000000000 --- 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 5a667fef77..0000000000 --- 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 5b89149154..0000000000 --- a/crates/bevy_hierarchy/src/hierarchy.rs +++ /dev/null @@ -1,505 +0,0 @@ -use crate::{ - components::{Children, Parent}, - BuildChildren, -}; -use bevy_ecs::{ - component::ComponentCloneHandler, - entity::{ComponentCloneCtx, Entity, EntityCloneBuilder}, - system::{error_handler, EntityCommands}, - world::{DeferredWorld, EntityWorldMut, World}, -}; -use log::debug; - -/// Function for despawning an entity and all its children -pub fn despawn_with_children_recursive(world: &mut World, entity: Entity, warn: bool) { - // first, make the entity's own parent forget about it - if let Some(parent) = world.get::(entity).map(|parent| parent.0) { - if let Some(mut children) = world.get_mut::(parent) { - children.0.retain(|c| *c != entity); - } - } - - // then despawn the entity and all of its children - despawn_with_children_recursive_inner(world, entity, warn); -} - -// Should only be called by `despawn_with_children_recursive` and `despawn_children_recursive`! -fn despawn_with_children_recursive_inner(world: &mut World, entity: Entity, warn: bool) { - if let Some(mut children) = world.get_mut::(entity) { - for e in core::mem::take(&mut children.0) { - despawn_with_children_recursive_inner(world, e, warn); - } - } - - if warn { - if !world.despawn(entity) { - debug!("Failed to despawn entity {}", entity); - } - } else if !world.try_despawn(entity) { - debug!("Failed to despawn entity {}", entity); - } -} - -fn despawn_children_recursive(world: &mut World, entity: Entity, warn: bool) { - if let Some(children) = world.entity_mut(entity).take::() { - for e in children.0 { - despawn_with_children_recursive_inner(world, e, warn); - } - } -} - -/// Trait that holds functions for despawning recursively down the transform hierarchy -pub trait DespawnRecursiveExt { - /// Despawns the provided entity alongside all descendants. - fn despawn_recursive(self); - - /// Despawns all descendants of the given entity. - fn despawn_descendants(&mut self) -> &mut Self; - - /// Similar to [`Self::despawn_recursive`] but does not emit warnings - fn try_despawn_recursive(self); - - /// Similar to [`Self::despawn_descendants`] but does not emit warnings - fn try_despawn_descendants(&mut self) -> &mut Self; -} - -impl DespawnRecursiveExt for EntityCommands<'_> { - /// Despawns the provided entity and its children. - /// This will emit warnings for any entity that does not exist. - fn despawn_recursive(mut self) { - let warn = true; - self.queue_handled( - move |mut entity: EntityWorldMut| { - let id = entity.id(); - #[cfg(feature = "trace")] - let _span = tracing::info_span!( - "command", - name = "DespawnRecursive", - entity = tracing::field::debug(id), - warn = tracing::field::debug(warn) - ) - .entered(); - entity.world_scope(|world| { - despawn_with_children_recursive(world, id, warn); - }); - }, - error_handler::warn(), - ); - } - - fn despawn_descendants(&mut self) -> &mut Self { - let warn = true; - self.queue_handled( - move |mut entity: EntityWorldMut| { - let id = entity.id(); - #[cfg(feature = "trace")] - let _span = tracing::info_span!( - "command", - name = "DespawnChildrenRecursive", - entity = tracing::field::debug(id), - warn = tracing::field::debug(warn) - ) - .entered(); - entity.world_scope(|world| { - despawn_children_recursive(world, id, warn); - }); - }, - error_handler::warn(), - ); - self - } - - /// Despawns the provided entity and its children. - /// This will never emit warnings. - fn try_despawn_recursive(mut self) { - let warn = false; - self.queue_handled( - move |mut entity: EntityWorldMut| { - let id = entity.id(); - #[cfg(feature = "trace")] - let _span = tracing::info_span!( - "command", - name = "TryDespawnRecursive", - entity = tracing::field::debug(id), - warn = tracing::field::debug(warn) - ) - .entered(); - entity.world_scope(|world| { - despawn_with_children_recursive(world, id, warn); - }); - }, - error_handler::silent(), - ); - } - - fn try_despawn_descendants(&mut self) -> &mut Self { - let warn = false; - self.queue_handled( - move |mut entity: EntityWorldMut| { - let id = entity.id(); - #[cfg(feature = "trace")] - let _span = tracing::info_span!( - "command", - name = "TryDespawnChildrenRecursive", - entity = tracing::field::debug(id), - warn = tracing::field::debug(warn) - ) - .entered(); - entity.world_scope(|world| { - despawn_children_recursive(world, id, warn); - }); - }, - error_handler::silent(), - ); - self - } -} - -fn despawn_recursive_inner(world: EntityWorldMut, warn: bool) { - let entity = world.id(); - - #[cfg(feature = "trace")] - let _span = tracing::info_span!( - "despawn_recursive", - entity = tracing::field::debug(entity), - warn = tracing::field::debug(warn) - ) - .entered(); - - despawn_with_children_recursive(world.into_world_mut(), entity, warn); -} - -fn despawn_descendants_inner<'v, 'w>( - world: &'v mut EntityWorldMut<'w>, - warn: bool, -) -> &'v mut EntityWorldMut<'w> { - let entity = world.id(); - - #[cfg(feature = "trace")] - let _span = tracing::info_span!( - "despawn_descendants", - entity = tracing::field::debug(entity), - warn = tracing::field::debug(warn) - ) - .entered(); - - world.world_scope(|world| { - despawn_children_recursive(world, entity, warn); - }); - world -} - -impl<'w> DespawnRecursiveExt for EntityWorldMut<'w> { - /// Despawns the provided entity and its children. - /// This will emit warnings for any entity that does not exist. - fn despawn_recursive(self) { - despawn_recursive_inner(self, true); - } - - fn despawn_descendants(&mut self) -> &mut Self { - despawn_descendants_inner(self, true) - } - - /// Despawns the provided entity and its children. - /// This will not emit warnings. - fn try_despawn_recursive(self) { - despawn_recursive_inner(self, false); - } - - fn try_despawn_descendants(&mut self) -> &mut Self { - despawn_descendants_inner(self, false) - } -} - -/// Trait that holds functions for cloning entities recursively down the hierarchy -pub trait CloneEntityHierarchyExt { - /// Sets the option to recursively clone entities. - /// When set to true all children will be cloned with the same options as the parent. - fn recursive(&mut self, recursive: bool) -> &mut Self; - /// Sets the option to add cloned entity as a child to the parent entity. - fn as_child(&mut self, as_child: bool) -> &mut Self; -} - -impl CloneEntityHierarchyExt for EntityCloneBuilder<'_> { - fn recursive(&mut self, recursive: bool) -> &mut Self { - if recursive { - self.override_component_clone_handler::( - ComponentCloneHandler::custom_handler(component_clone_children), - ) - } else { - self.remove_component_clone_handler_override::() - } - } - fn as_child(&mut self, as_child: bool) -> &mut Self { - if as_child { - self.override_component_clone_handler::(ComponentCloneHandler::custom_handler( - component_clone_parent, - )) - } else { - self.remove_component_clone_handler_override::() - } - } -} - -/// Clone handler for the [`Children`] component. Allows to clone the entity recursively. -fn component_clone_children(world: &mut DeferredWorld, ctx: &mut ComponentCloneCtx) { - let children = ctx - .read_source_component::() - .expect("Source entity must have Children component") - .iter(); - let parent = ctx.target(); - for child in children { - let child_clone = world.commands().spawn_empty().id(); - let mut clone_entity = ctx - .entity_cloner() - .with_source_and_target(*child, child_clone); - world.commands().queue(move |world: &mut World| { - clone_entity.clone_entity(world); - world.entity_mut(child_clone).set_parent(parent); - }); - } -} - -/// Clone handler for the [`Parent`] component. Allows to add clone as a child to the parent entity. -fn component_clone_parent(world: &mut DeferredWorld, ctx: &mut ComponentCloneCtx) { - let parent = ctx - .read_source_component::() - .map(|p| p.0) - .expect("Source entity must have Parent component"); - world.commands().entity(ctx.target()).set_parent(parent); -} - -#[cfg(test)] -mod tests { - use alloc::{borrow::ToOwned, string::String, vec, vec::Vec}; - use bevy_ecs::{ - component::Component, - system::Commands, - world::{CommandQueue, World}, - }; - - use super::DespawnRecursiveExt; - use crate::{ - child_builder::{BuildChildren, ChildBuild}, - components::Children, - CloneEntityHierarchyExt, - }; - - #[derive(Component, Clone, Copy, PartialEq, Eq, Ord, PartialOrd, Debug)] - struct Idx(u32); - - #[derive(Component, Clone, PartialEq, Eq, Ord, PartialOrd, Debug)] - struct N(String); - - #[test] - fn despawn_recursive() { - let mut world = World::default(); - let mut queue = CommandQueue::default(); - let grandparent_entity; - { - let mut commands = Commands::new(&mut queue, &world); - - commands - .spawn((N("Another parent".to_owned()), Idx(0))) - .with_children(|parent| { - parent.spawn((N("Another child".to_owned()), Idx(1))); - }); - - // Create a grandparent entity which will _not_ be deleted - grandparent_entity = commands.spawn((N("Grandparent".to_owned()), Idx(2))).id(); - commands.entity(grandparent_entity).with_children(|parent| { - // Add a child to the grandparent (the "parent"), which will get deleted - parent - .spawn((N("Parent, to be deleted".to_owned()), Idx(3))) - // All descendants of the "parent" should also be deleted. - .with_children(|parent| { - parent - .spawn((N("First Child, to be deleted".to_owned()), Idx(4))) - .with_children(|parent| { - // child - parent.spawn(( - N("First grand child, to be deleted".to_owned()), - Idx(5), - )); - }); - parent.spawn((N("Second child, to be deleted".to_owned()), Idx(6))); - }); - }); - - commands.spawn((N("An innocent bystander".to_owned()), Idx(7))); - } - queue.apply(&mut world); - - let parent_entity = world.get::(grandparent_entity).unwrap()[0]; - - { - let mut commands = Commands::new(&mut queue, &world); - commands.entity(parent_entity).despawn_recursive(); - // despawning the same entity twice should not panic - commands.entity(parent_entity).despawn_recursive(); - } - queue.apply(&mut world); - - let mut results = world - .query::<(&N, &Idx)>() - .iter(&world) - .map(|(a, b)| (a.clone(), *b)) - .collect::>(); - results.sort_unstable_by_key(|(_, index)| *index); - - { - let children = world.get::(grandparent_entity).unwrap(); - assert!( - !children.iter().any(|&i| i == parent_entity), - "grandparent should no longer know about its child which has been removed" - ); - } - - assert_eq!( - results, - vec![ - (N("Another parent".to_owned()), Idx(0)), - (N("Another child".to_owned()), Idx(1)), - (N("Grandparent".to_owned()), Idx(2)), - (N("An innocent bystander".to_owned()), Idx(7)) - ] - ); - } - - #[test] - fn despawn_descendants() { - let mut world = World::default(); - let mut queue = CommandQueue::default(); - let mut commands = Commands::new(&mut queue, &world); - - let parent = commands.spawn_empty().id(); - let child = commands.spawn_empty().id(); - - commands - .entity(parent) - .add_child(child) - .despawn_descendants(); - - queue.apply(&mut world); - - // The parent's Children component should be removed. - assert!(world.entity(parent).get::().is_none()); - // The child should be despawned. - assert!(world.get_entity(child).is_err()); - } - - #[test] - fn spawn_children_after_despawn_descendants() { - let mut world = World::default(); - let mut queue = CommandQueue::default(); - let mut commands = Commands::new(&mut queue, &world); - - let parent = commands.spawn_empty().id(); - let child = commands.spawn_empty().id(); - - commands - .entity(parent) - .add_child(child) - .despawn_descendants() - .with_children(|parent| { - parent.spawn_empty(); - parent.spawn_empty(); - }); - - queue.apply(&mut world); - - // The parent's Children component should still have two children. - let children = world.entity(parent).get::(); - assert!(children.is_some()); - assert_eq!(children.unwrap().len(), 2_usize); - // The original child should be despawned. - assert!(world.get_entity(child).is_err()); - } - - #[test] - fn clone_entity_recursive() { - #[derive(Component, PartialEq, Eq, Clone)] - struct Component1 { - field: usize, - } - - let parent_component = Component1 { field: 10 }; - let child1_component = Component1 { field: 20 }; - let child1_1_component = Component1 { field: 30 }; - let child2_component = Component1 { field: 21 }; - let child2_1_component = Component1 { field: 31 }; - - let mut world = World::default(); - - let mut queue = CommandQueue::default(); - let e_clone = { - let mut commands = Commands::new(&mut queue, &world); - let e = commands - .spawn(parent_component.clone()) - .with_children(|children| { - children - .spawn(child1_component.clone()) - .with_children(|children| { - children.spawn(child1_1_component.clone()); - }); - children - .spawn(child2_component.clone()) - .with_children(|children| { - children.spawn(child2_1_component.clone()); - }); - }) - .id(); - let e_clone = commands - .entity(e) - .clone_and_spawn_with(|builder| { - builder.recursive(true); - }) - .id(); - e_clone - }; - queue.apply(&mut world); - - assert!(world - .get::(e_clone) - .is_some_and(|c| *c == parent_component)); - - let children = world.get::(e_clone).unwrap(); - for (child, (component1, component2)) in children.iter().zip([ - (child1_component, child1_1_component), - (child2_component, child2_1_component), - ]) { - assert!(world - .get::(*child) - .is_some_and(|c| *c == component1)); - for child2 in world.get::(*child).unwrap().iter() { - assert!(world - .get::(*child2) - .is_some_and(|c| *c == component2)); - } - } - } - - #[test] - fn clone_entity_as_child() { - let mut world = World::default(); - let mut queue = CommandQueue::default(); - let mut commands = Commands::new(&mut queue, &world); - - let child = commands.spawn_empty().id(); - let parent = commands.spawn_empty().add_child(child).id(); - - let child_clone = commands - .entity(child) - .clone_and_spawn_with(|builder| { - builder.as_child(true); - }) - .id(); - - queue.apply(&mut world); - - assert!(world - .entity(parent) - .get::() - .is_some_and(|c| c.contains(&child_clone))); - } -} diff --git a/crates/bevy_hierarchy/src/lib.rs b/crates/bevy_hierarchy/src/lib.rs deleted file mode 100644 index 2e8beea501..0000000000 --- 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 1ab3ec9385..0000000000 --- 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 c17059c3f3..0000000000 --- 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 628d50e4bf..96c82a0507 100644 --- a/crates/bevy_input_focus/Cargo.toml +++ b/crates/bevy_input_focus/Cargo.toml @@ -25,7 +25,6 @@ bevy_reflect = [ bevy_app = { path = "../bevy_app", version = "0.16.0-dev", default-features = false } bevy_ecs = { path = "../bevy_ecs", version = "0.16.0-dev", default-features = false } bevy_input = { path = "../bevy_input", version = "0.16.0-dev", default-features = false } -bevy_hierarchy = { path = "../bevy_hierarchy", version = "0.16.0-dev", default-features = false } bevy_math = { path = "../bevy_math", version = "0.16.0-dev", default-features = false } bevy_window = { path = "../bevy_window", version = "0.16.0-dev", default-features = false } bevy_reflect = { path = "../bevy_reflect", version = "0.16.0-dev", features = [ diff --git a/crates/bevy_input_focus/src/lib.rs b/crates/bevy_input_focus/src/lib.rs index 2e63ad339c..c894c6f3d7 100644 --- a/crates/bevy_input_focus/src/lib.rs +++ b/crates/bevy_input_focus/src/lib.rs @@ -26,7 +26,6 @@ pub use autofocus::*; use bevy_app::{App, Plugin, PreUpdate, Startup}; use bevy_ecs::{prelude::*, query::QueryData, system::SystemParam, traversal::Traversal}; -use bevy_hierarchy::{HierarchyQueryExt, Parent}; use bevy_input::{gamepad::GamepadButtonChangedEvent, keyboard::KeyboardInput, mouse::MouseWheel}; #[cfg(feature = "bevy_reflect")] use bevy_reflect::{prelude::*, Reflect}; @@ -356,7 +355,6 @@ mod tests { use bevy_ecs::{ component::ComponentId, observer::Trigger, system::RunSystemOnce, world::DeferredWorld, }; - use bevy_hierarchy::BuildChildren; use bevy_input::{ keyboard::{Key, KeyCode}, ButtonState, InputPlugin, diff --git a/crates/bevy_input_focus/src/tab_navigation.rs b/crates/bevy_input_focus/src/tab_navigation.rs index 47a939cd19..8039938649 100644 --- a/crates/bevy_input_focus/src/tab_navigation.rs +++ b/crates/bevy_input_focus/src/tab_navigation.rs @@ -29,11 +29,11 @@ use bevy_ecs::prelude::ReflectComponent; use bevy_ecs::{ component::Component, entity::Entity, + hierarchy::{Children, Parent}, observer::Trigger, query::{With, Without}, system::{Commands, Query, Res, ResMut, SystemParam}, }; -use bevy_hierarchy::{Children, HierarchyQueryExt, Parent}; use bevy_input::{ keyboard::{KeyCode, KeyboardInput}, ButtonInput, ButtonState, @@ -362,7 +362,6 @@ pub fn handle_tab_navigation( #[cfg(test)] mod tests { use bevy_ecs::system::SystemState; - use bevy_hierarchy::BuildChildren; use super::*; @@ -371,10 +370,9 @@ mod tests { let mut app = App::new(); let world = app.world_mut(); - let tab_entity_1 = world.spawn(TabIndex(0)).id(); - let tab_entity_2 = world.spawn(TabIndex(1)).id(); - let mut tab_group_entity = world.spawn(TabGroup::new(0)); - tab_group_entity.replace_children(&[tab_entity_1, tab_entity_2]); + let tab_group_entity = world.spawn(TabGroup::new(0)).id(); + let tab_entity_1 = world.spawn((TabIndex(0), Parent(tab_group_entity))).id(); + let tab_entity_2 = world.spawn((TabIndex(1), Parent(tab_group_entity))).id(); let mut system_state: SystemState = SystemState::new(world); let tab_navigation = system_state.get(world); diff --git a/crates/bevy_internal/Cargo.toml b/crates/bevy_internal/Cargo.toml index d3c66ee4c5..6011046b59 100644 --- a/crates/bevy_internal/Cargo.toml +++ b/crates/bevy_internal/Cargo.toml @@ -18,7 +18,6 @@ trace = [ "bevy_log/trace", "bevy_pbr?/trace", "bevy_render?/trace", - "bevy_hierarchy/trace", "bevy_winit?/trace", ] trace_chrome = ["bevy_log/tracing-chrome"] @@ -273,7 +272,6 @@ bevy_derive = { path = "../bevy_derive", version = "0.16.0-dev" } bevy_diagnostic = { path = "../bevy_diagnostic", version = "0.16.0-dev" } bevy_ecs = { path = "../bevy_ecs", version = "0.16.0-dev" } bevy_state = { path = "../bevy_state", optional = true, version = "0.16.0-dev" } -bevy_hierarchy = { path = "../bevy_hierarchy", version = "0.16.0-dev" } bevy_input = { path = "../bevy_input", version = "0.16.0-dev" } bevy_input_focus = { path = "../bevy_input_focus", version = "0.16.0-dev" } bevy_log = { path = "../bevy_log", version = "0.16.0-dev" } diff --git a/crates/bevy_internal/src/default_plugins.rs b/crates/bevy_internal/src/default_plugins.rs index fe3cf6f9cf..d9aee3017a 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 e9d25adbdf..8750c566d0 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 42c84f134a..1c19c7ccc1 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 011f55c995..4f22e0b27d 100644 --- a/crates/bevy_picking/Cargo.toml +++ b/crates/bevy_picking/Cargo.toml @@ -17,7 +17,6 @@ bevy_app = { path = "../bevy_app", version = "0.16.0-dev" } bevy_asset = { path = "../bevy_asset", version = "0.16.0-dev" } bevy_derive = { path = "../bevy_derive", version = "0.16.0-dev" } bevy_ecs = { path = "../bevy_ecs", version = "0.16.0-dev" } -bevy_hierarchy = { path = "../bevy_hierarchy", version = "0.16.0-dev" } bevy_input = { path = "../bevy_input", version = "0.16.0-dev" } bevy_math = { path = "../bevy_math", version = "0.16.0-dev" } bevy_mesh = { path = "../bevy_mesh", version = "0.16.0-dev", optional = true } diff --git a/crates/bevy_picking/src/events.rs b/crates/bevy_picking/src/events.rs index ae3d879e9a..c580c1e594 100644 --- a/crates/bevy_picking/src/events.rs +++ b/crates/bevy_picking/src/events.rs @@ -40,7 +40,6 @@ use core::{fmt::Debug, time::Duration}; use bevy_ecs::{prelude::*, query::QueryData, system::SystemParam, traversal::Traversal}; -use bevy_hierarchy::Parent; use bevy_math::Vec2; use bevy_reflect::prelude::*; use bevy_render::camera::NormalizedRenderTarget; diff --git a/crates/bevy_picking/src/input.rs b/crates/bevy_picking/src/input.rs index 94e195e4b3..3c03ba11dd 100644 --- a/crates/bevy_picking/src/input.rs +++ b/crates/bevy_picking/src/input.rs @@ -13,7 +13,6 @@ use bevy_app::prelude::*; use bevy_ecs::prelude::*; -use bevy_hierarchy::DespawnRecursiveExt; use bevy_input::{ prelude::*, touch::{TouchInput, TouchPhase}, @@ -268,6 +267,6 @@ pub fn deactivate_touch_pointers( // A hash set is used to prevent despawning the same entity twice. for (entity, pointer) in despawn_list.drain() { debug!("Despawning pointer {:?}", pointer); - commands.entity(entity).despawn_recursive(); + commands.entity(entity).despawn(); } } diff --git a/crates/bevy_remote/Cargo.toml b/crates/bevy_remote/Cargo.toml index 92a261ea79..c56be57330 100644 --- a/crates/bevy_remote/Cargo.toml +++ b/crates/bevy_remote/Cargo.toml @@ -19,7 +19,6 @@ bevy_derive = { path = "../bevy_derive", version = "0.16.0-dev" } bevy_ecs = { path = "../bevy_ecs", version = "0.16.0-dev", features = [ "serialize", ] } -bevy_hierarchy = { path = "../bevy_hierarchy", version = "0.16.0-dev" } bevy_reflect = { path = "../bevy_reflect", version = "0.16.0-dev" } bevy_tasks = { path = "../bevy_tasks", version = "0.16.0-dev" } bevy_utils = { path = "../bevy_utils", version = "0.16.0-dev" } diff --git a/crates/bevy_remote/src/builtin_methods.rs b/crates/bevy_remote/src/builtin_methods.rs index 91069aba5c..9723da739b 100644 --- a/crates/bevy_remote/src/builtin_methods.rs +++ b/crates/bevy_remote/src/builtin_methods.rs @@ -7,13 +7,13 @@ use bevy_ecs::{ component::ComponentId, entity::Entity, event::EventCursor, + hierarchy::Parent, query::QueryBuilder, reflect::{AppTypeRegistry, ReflectComponent, ReflectResource}, removal_detection::RemovedComponentEntity, system::{In, Local}, world::{EntityRef, EntityWorldMut, FilteredEntityRef, World}, }; -use bevy_hierarchy::BuildChildren as _; use bevy_reflect::{ prelude::ReflectDefault, serde::{ReflectSerializer, TypedReflectDeserializer}, @@ -841,7 +841,7 @@ pub fn process_remote_reparent_request( // If `None`, remove the entities' parents. else { for entity in entities { - get_entity_mut(world, entity)?.remove_parent(); + get_entity_mut(world, entity)?.remove::(); } } diff --git a/crates/bevy_render/Cargo.toml b/crates/bevy_render/Cargo.toml index 577ff685bb..91c79a4a67 100644 --- a/crates/bevy_render/Cargo.toml +++ b/crates/bevy_render/Cargo.toml @@ -44,7 +44,6 @@ bevy_derive = { path = "../bevy_derive", version = "0.16.0-dev" } bevy_diagnostic = { path = "../bevy_diagnostic", version = "0.16.0-dev" } bevy_ecs = { path = "../bevy_ecs", version = "0.16.0-dev" } bevy_encase_derive = { path = "../bevy_encase_derive", version = "0.16.0-dev" } -bevy_hierarchy = { path = "../bevy_hierarchy", version = "0.16.0-dev" } bevy_math = { path = "../bevy_math", version = "0.16.0-dev" } bevy_reflect = { path = "../bevy_reflect", version = "0.16.0-dev", features = [ "bevy", diff --git a/crates/bevy_render/src/lib.rs b/crates/bevy_render/src/lib.rs index 32bc6d0305..b267d4bf5a 100644 --- a/crates/bevy_render/src/lib.rs +++ b/crates/bevy_render/src/lib.rs @@ -72,7 +72,6 @@ use bevy_ecs::schedule::ScheduleBuildSettings; use bevy_utils::prelude::default; pub use extract_param::Extract; -use bevy_hierarchy::ValidParentCheckPlugin; use bevy_window::{PrimaryWindow, RawHandleWrapperHolder}; use extract_resource::ExtractResourcePlugin; use globals::GlobalsPlugin; @@ -349,7 +348,6 @@ impl Plugin for RenderPlugin { }; app.add_plugins(( - ValidParentCheckPlugin::::default(), WindowRenderPlugin, CameraPlugin, ViewPlugin, diff --git a/crates/bevy_render/src/mesh/mod.rs b/crates/bevy_render/src/mesh/mod.rs index 703333675d..8725c0e334 100644 --- a/crates/bevy_render/src/mesh/mod.rs +++ b/crates/bevy_render/src/mesh/mod.rs @@ -1,4 +1,3 @@ -use bevy_hierarchy::Children; use bevy_math::Vec3; pub use bevy_mesh::*; use morph::{MeshMorphWeights, MorphWeights}; @@ -16,13 +15,7 @@ use allocator::MeshAllocatorPlugin; use bevy_app::{App, Plugin, PostUpdate}; use bevy_asset::{AssetApp, AssetId, RenderAssetUsages}; use bevy_ecs::{ - entity::Entity, - query::{Changed, With}, - schedule::IntoSystemConfigs, - system::Query, -}; -use bevy_ecs::{ - query::Without, + prelude::*, system::{ lifetimeless::{SRes, SResMut}, SystemParamItem, diff --git a/crates/bevy_render/src/view/visibility/mod.rs b/crates/bevy_render/src/view/visibility/mod.rs index b3b7c15bb2..d0c84bd70a 100644 --- a/crates/bevy_render/src/view/visibility/mod.rs +++ b/crates/bevy_render/src/view/visibility/mod.rs @@ -12,19 +12,18 @@ pub use render_layers::*; use bevy_app::{Plugin, PostUpdate}; use bevy_asset::Assets; -use bevy_ecs::prelude::*; -use bevy_hierarchy::{Children, Parent}; +use bevy_ecs::{hierarchy::validate_parent_has_component, prelude::*}; use bevy_reflect::{std_traits::ReflectDefault, Reflect}; use bevy_transform::{components::GlobalTransform, TransformSystem}; use bevy_utils::{Parallel, TypeIdMap}; use smallvec::SmallVec; use super::NoCpuCulling; -use crate::{camera::Projection, sync_world::MainEntity}; use crate::{ - camera::{Camera, CameraProjection}, + camera::{Camera, CameraProjection, Projection}, mesh::{Mesh, Mesh3d, MeshAabb}, primitives::{Aabb, Frustum, Sphere}, + sync_world::MainEntity, }; /// User indication of whether an entity is visible. Propagates down the entity hierarchy. @@ -111,6 +110,7 @@ impl PartialEq<&Visibility> for Visibility { /// [`VisibilityPropagate`]: VisibilitySystems::VisibilityPropagate #[derive(Component, Deref, Debug, Default, Clone, Copy, Reflect, PartialEq, Eq)] #[reflect(Component, Default, Debug, PartialEq)] +#[component(on_insert = validate_parent_has_component::)] pub struct InheritedVisibility(bool); impl InheritedVisibility { @@ -316,7 +316,7 @@ pub enum VisibilitySystems { /// Label for [`update_frusta`] in [`CameraProjectionPlugin`](crate::camera::CameraProjectionPlugin). UpdateFrusta, /// Label for the system propagating the [`InheritedVisibility`] in a - /// [`hierarchy`](bevy_hierarchy). + /// [`Parent`] / [`Children`] hierarchy. VisibilityPropagate, /// Label for the [`check_visibility`] system updating [`ViewVisibility`] /// of each entity and the [`VisibleEntities`] of each view.\ @@ -645,7 +645,6 @@ where mod test { use super::*; use bevy_app::prelude::*; - use bevy_hierarchy::BuildChildren; #[test] fn visibility_propagation() { @@ -762,7 +761,7 @@ mod test { .entity_mut(parent2) .insert(Visibility::Visible); // Simulate a change in the parent component - app.world_mut().entity_mut(child2).set_parent(parent2); // example of changing parent + app.world_mut().entity_mut(child2).insert(Parent(parent2)); // example of changing parent // Run the system again to propagate changes app.update(); diff --git a/crates/bevy_render/src/view/window/screenshot.rs b/crates/bevy_render/src/view/window/screenshot.rs index 01460efcad..81adc30486 100644 --- a/crates/bevy_render/src/view/window/screenshot.rs +++ b/crates/bevy_render/src/view/window/screenshot.rs @@ -22,7 +22,6 @@ use bevy_derive::{Deref, DerefMut}; use bevy_ecs::{ entity::EntityHashMap, event::event_update_system, prelude::*, system::SystemState, }; -use bevy_hierarchy::DespawnRecursiveExt; use bevy_image::{Image, TextureFormatPixelInfo}; use bevy_reflect::Reflect; use bevy_tasks::AsyncComputeTaskPool; @@ -185,7 +184,7 @@ pub fn save_to_disk(path: impl AsRef) -> impl FnMut(Trigger>) { for entity in screenshots.iter() { - commands.entity(entity).despawn_recursive(); + commands.entity(entity).despawn(); } } @@ -240,7 +239,7 @@ fn extract_screenshots( entity, render_target ); // If we don't despawn the entity here, it will be captured again in the next frame - commands.entity(entity).despawn_recursive(); + commands.entity(entity).despawn(); continue; } seen_targets.insert(render_target.clone()); diff --git a/crates/bevy_scene/Cargo.toml b/crates/bevy_scene/Cargo.toml index 69a288fe5c..60004b2662 100644 --- a/crates/bevy_scene/Cargo.toml +++ b/crates/bevy_scene/Cargo.toml @@ -21,7 +21,6 @@ bevy_ecs = { path = "../bevy_ecs", version = "0.16.0-dev" } bevy_reflect = { path = "../bevy_reflect", version = "0.16.0-dev", features = [ "bevy", ] } -bevy_hierarchy = { path = "../bevy_hierarchy", version = "0.16.0-dev" } bevy_transform = { path = "../bevy_transform", version = "0.16.0-dev" } bevy_utils = { path = "../bevy_utils", version = "0.16.0-dev" } bevy_render = { path = "../bevy_render", version = "0.16.0-dev", optional = true } diff --git a/crates/bevy_scene/src/dynamic_scene.rs b/crates/bevy_scene/src/dynamic_scene.rs index f29c925f7e..f8adb6d070 100644 --- a/crates/bevy_scene/src/dynamic_scene.rs +++ b/crates/bevy_scene/src/dynamic_scene.rs @@ -202,11 +202,11 @@ mod tests { entity::{ Entity, EntityHashMap, EntityMapper, MapEntities, VisitEntities, VisitEntitiesMut, }, + hierarchy::Parent, reflect::{AppTypeRegistry, ReflectComponent, ReflectMapEntities, ReflectResource}, system::Resource, world::World, }; - use bevy_hierarchy::{BuildChildren, Parent}; use bevy_reflect::Reflect; use crate::dynamic_scene::DynamicScene; @@ -296,7 +296,7 @@ mod tests { // We then reload the scene to make sure that from_scene_parent_entity's parent component // isn't updated with the entity map, since this component isn't defined in the scene. - // With bevy_hierarchy, this can cause serious errors and malformed hierarchies. + // With [`bevy_ecs::hierarchy`], this can cause serious errors and malformed hierarchies. scene.write_to_world(&mut world, &mut entity_map).unwrap(); assert_eq!( diff --git a/crates/bevy_scene/src/scene_spawner.rs b/crates/bevy_scene/src/scene_spawner.rs index 0f47710446..9d3c99996d 100644 --- a/crates/bevy_scene/src/scene_spawner.rs +++ b/crates/bevy_scene/src/scene_spawner.rs @@ -3,11 +3,11 @@ use bevy_asset::{AssetEvent, AssetId, Assets, Handle}; use bevy_ecs::{ entity::{Entity, EntityHashMap}, event::{Event, EventCursor, Events}, + hierarchy::Parent, reflect::AppTypeRegistry, system::Resource, world::{Mut, World}, }; -use bevy_hierarchy::{BuildChildren, DespawnRecursiveExt, Parent}; use bevy_reflect::Reflect; use bevy_utils::{HashMap, HashSet}; use thiserror::Error; @@ -201,9 +201,8 @@ impl SceneSpawner { pub fn despawn_instance_sync(&mut self, world: &mut World, instance_id: &InstanceId) { if let Some(instance) = self.spawned_instances.remove(instance_id) { for &entity in instance.entity_map.values() { - if let Ok(mut entity_mut) = world.get_entity_mut(entity) { - entity_mut.remove_parent(); - entity_mut.despawn_recursive(); + if let Ok(entity_mut) = world.get_entity_mut(entity) { + entity_mut.despawn(); }; } } @@ -519,6 +518,7 @@ mod tests { use bevy_asset::{AssetPlugin, AssetServer, Handle}; use bevy_ecs::{ component::Component, + hierarchy::Children, observer::Trigger, prelude::ReflectComponent, query::With, @@ -536,7 +536,6 @@ mod tests { entity::Entity, prelude::{AppTypeRegistry, World}, }; - use bevy_hierarchy::{Children, HierarchyPlugin}; #[derive(Component, Reflect, Default)] #[reflect(Component)] @@ -550,7 +549,6 @@ mod tests { let mut app = App::new(); app.add_plugins(ScheduleRunnerPlugin::default()) - .add_plugins(HierarchyPlugin) .add_plugins(AssetPlugin::default()) .add_plugins(ScenePlugin) .register_type::(); @@ -854,7 +852,7 @@ mod tests { .run_system_once( |mut commands: Commands, query: Query>| { for entity in query.iter() { - commands.entity(entity).despawn_recursive(); + commands.entity(entity).despawn(); } }, ) diff --git a/crates/bevy_state/Cargo.toml b/crates/bevy_state/Cargo.toml index bc32d18f6d..18f75afec8 100644 --- a/crates/bevy_state/Cargo.toml +++ b/crates/bevy_state/Cargo.toml @@ -9,7 +9,7 @@ license = "MIT OR Apache-2.0" keywords = ["bevy"] [features] -default = ["std", "bevy_reflect", "bevy_app", "bevy_hierarchy"] +default = ["std", "bevy_reflect", "bevy_app"] # Functionality @@ -17,28 +17,18 @@ default = ["std", "bevy_reflect", "bevy_app", "bevy_hierarchy"] bevy_reflect = [ "dep:bevy_reflect", "bevy_ecs/bevy_reflect", - "bevy_hierarchy?/reflect", "bevy_app?/bevy_reflect", ] ## Adds integration with the `bevy_app` plugin API. -bevy_app = ["dep:bevy_app", "bevy_hierarchy?/bevy_app"] - -## Adds integration with the `bevy_hierarchy` `Parent` and `Children` API. -bevy_hierarchy = ["dep:bevy_hierarchy"] +bevy_app = ["dep:bevy_app"] # Platform Compatibility ## Allows access to the `std` crate. Enabling this feature will prevent compilation ## on `no_std` targets, but provides access to certain additional features on ## supported platforms. -std = [ - "bevy_ecs/std", - "bevy_utils/std", - "bevy_reflect?/std", - "bevy_app?/std", - "bevy_hierarchy?/std", -] +std = ["bevy_ecs/std", "bevy_utils/std", "bevy_reflect?/std", "bevy_app?/std"] ## `critical-section` provides the building blocks for synchronization primitives ## on all platforms, including `no_std`. @@ -63,7 +53,6 @@ bevy_state_macros = { path = "macros", version = "0.16.0-dev" } bevy_utils = { path = "../bevy_utils", version = "0.16.0-dev", default-features = false } bevy_reflect = { path = "../bevy_reflect", version = "0.16.0-dev", default-features = false, optional = true } bevy_app = { path = "../bevy_app", version = "0.16.0-dev", default-features = false, optional = true } -bevy_hierarchy = { path = "../bevy_hierarchy", version = "0.16.0-dev", default-features = false, optional = true } variadics_please = "1.1" # other diff --git a/crates/bevy_state/src/state_scoped.rs b/crates/bevy_state/src/state_scoped.rs index 02a8c75cdc..747c2705a2 100644 --- a/crates/bevy_state/src/state_scoped.rs +++ b/crates/bevy_state/src/state_scoped.rs @@ -6,8 +6,6 @@ use bevy_ecs::{ event::EventReader, system::{Commands, Query}, }; -#[cfg(feature = "bevy_hierarchy")] -use bevy_hierarchy::DespawnRecursiveExt; #[cfg(feature = "bevy_reflect")] use bevy_reflect::prelude::*; @@ -19,8 +17,6 @@ use crate::state::{StateTransitionEvent, States}; /// To enable this feature remember to add the attribute `#[states(scoped_entities)]` when deriving [`States`]. /// It's also possible to enable it when adding the state to an app with [`enable_state_scoped_entities`](crate::app::AppExtStates::enable_state_scoped_entities). /// -/// If `bevy_hierarchy` feature is enabled, which it is by default, the despawn will be recursive. -/// /// ``` /// use bevy_state::prelude::*; /// use bevy_ecs::prelude::*; @@ -71,8 +67,6 @@ where /// 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>, @@ -92,9 +86,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 2dcee021df..dd4ed912a6 100644 --- a/crates/bevy_text/Cargo.toml +++ b/crates/bevy_text/Cargo.toml @@ -18,7 +18,6 @@ bevy_asset = { path = "../bevy_asset", version = "0.16.0-dev" } bevy_color = { path = "../bevy_color", version = "0.16.0-dev" } bevy_derive = { path = "../bevy_derive", version = "0.16.0-dev" } bevy_ecs = { path = "../bevy_ecs", version = "0.16.0-dev" } -bevy_hierarchy = { path = "../bevy_hierarchy", version = "0.16.0-dev" } bevy_image = { path = "../bevy_image", version = "0.16.0-dev" } bevy_math = { path = "../bevy_math", version = "0.16.0-dev" } bevy_reflect = { path = "../bevy_reflect", version = "0.16.0-dev", features = [ diff --git a/crates/bevy_text/src/text.rs b/crates/bevy_text/src/text.rs index 06f8adf86c..0555d7a338 100644 --- a/crates/bevy_text/src/text.rs +++ b/crates/bevy_text/src/text.rs @@ -8,7 +8,6 @@ use bevy_asset::Handle; use bevy_color::Color; use bevy_derive::{Deref, DerefMut}; use bevy_ecs::{prelude::*, reflect::ReflectComponent}; -use bevy_hierarchy::{Children, Parent}; use bevy_reflect::prelude::*; use bevy_utils::once; use cosmic_text::{Buffer, Metrics}; @@ -169,7 +168,6 @@ impl TextLayout { /// # use bevy_color::palettes::basic::{RED, BLUE}; /// # use bevy_ecs::world::World; /// # use bevy_text::{Font, TextLayout, TextFont, TextSpan, TextColor}; -/// # use bevy_hierarchy::BuildChildren; /// /// # let font_handle: Handle = Default::default(); /// # let mut world = World::default(); @@ -510,7 +508,7 @@ pub fn detect_text_needs_rerender( )); continue; }; - let mut parent: Entity = **span_parent; + let mut parent: Entity = span_parent.0; // Search for the nearest ancestor with ComputedTextBlock. // Note: We assume the perf cost from duplicate visits in the case that multiple spans in a block are visited @@ -541,7 +539,7 @@ pub fn detect_text_needs_rerender( )); break; }; - parent = **next_parent; + parent = next_parent.0; } } } diff --git a/crates/bevy_text/src/text_access.rs b/crates/bevy_text/src/text_access.rs index 84943ea566..7aafa28ef6 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 c9072ddf82..706f38b682 100644 --- a/crates/bevy_transform/Cargo.toml +++ b/crates/bevy_transform/Cargo.toml @@ -12,9 +12,6 @@ keywords = ["bevy"] # bevy bevy_app = { path = "../bevy_app", version = "0.16.0-dev", default-features = false, optional = true } bevy_ecs = { path = "../bevy_ecs", version = "0.16.0-dev", default-features = false, optional = true } -bevy_hierarchy = { path = "../bevy_hierarchy", version = "0.16.0-dev", default-features = false, features = [ - "bevy_app", -], optional = true } bevy_math = { path = "../bevy_math", version = "0.16.0-dev", default-features = false } bevy_reflect = { path = "../bevy_reflect", version = "0.16.0-dev", default-features = false, optional = true } serde = { version = "1", default-features = false, features = [ @@ -41,7 +38,7 @@ default = ["std", "bevy-support", "bevy_reflect"] ## systems for transform propagation and more. ## This exists because it allows opting out of all of this, leaving only a bare-bones transform struct, ## which enables users to depend on that without needing the larger Bevy dependency tree. -bevy-support = ["alloc", "dep:bevy_app", "dep:bevy_ecs", "dep:bevy_hierarchy"] +bevy-support = ["alloc", "dep:bevy_app", "dep:bevy_ecs"] ## Adds serialization support through `serde`. serialize = ["dep:serde", "bevy_math/serialize"] @@ -64,7 +61,6 @@ std = [ "alloc", "bevy_app?/std", "bevy_ecs?/std", - "bevy_hierarchy?/std", "bevy_math/std", "bevy_reflect?/std", "serde?/std", diff --git a/crates/bevy_transform/src/commands.rs b/crates/bevy_transform/src/commands.rs index dab6245669..a3bc817ae2 100644 --- a/crates/bevy_transform/src/commands.rs +++ b/crates/bevy_transform/src/commands.rs @@ -1,17 +1,16 @@ -//! Extension to [`EntityCommands`] to modify `bevy_hierarchy` hierarchies +//! Extension to [`EntityCommands`] to modify [`bevy_ecs::hierarchy`] hierarchies. //! while preserving [`GlobalTransform`]. use crate::prelude::{GlobalTransform, Transform}; -use bevy_ecs::{entity::Entity, system::EntityCommands, world::EntityWorldMut}; -use bevy_hierarchy::BuildChildren; +use bevy_ecs::{entity::Entity, hierarchy::Parent, system::EntityCommands, world::EntityWorldMut}; -/// Collection of methods similar to [`BuildChildren`], but preserving each +/// Collection of methods similar to the built-in parenting methods on [`EntityWorldMut`] and [`EntityCommands`], but preserving each /// entity's [`GlobalTransform`]. pub trait BuildChildrenTransformExt { /// Change this entity's parent while preserving this entity's [`GlobalTransform`] /// by updating its [`Transform`]. /// - /// See [`BuildChildren::set_parent`] for a method that doesn't update the [`Transform`]. + /// Insert the [`Parent`] component directly if you don't want to also update the [`Transform`]. /// /// Note that both the hierarchy and transform updates will only execute /// the next time commands are applied @@ -21,7 +20,7 @@ pub trait BuildChildrenTransformExt { /// Make this entity parentless while preserving this entity's [`GlobalTransform`] /// by updating its [`Transform`] to be equal to its current [`GlobalTransform`]. /// - /// See [`BuildChildren::remove_parent`] for a method that doesn't update the [`Transform`]. + /// See [`EntityWorldMut::remove_parent`] or [`EntityCommands::remove_parent`] for a method that doesn't update the [`Transform`]. /// /// Note that both the hierarchy and transform updates will only execute /// the next time commands are applied @@ -65,7 +64,7 @@ impl BuildChildrenTransformExt for EntityWorldMut<'_> { fn remove_parent_in_place(&mut self) -> &mut Self { let child = self.id(); self.world_scope(|world| { - world.entity_mut(child).remove_parent(); + world.entity_mut(child).remove::(); // FIXME: Replace this closure with a `try` block. See: https://github.com/rust-lang/rust/issues/31436. let mut update_transform = || { let child_global = *world.get_entity(child).ok()?.get::()?; diff --git a/crates/bevy_transform/src/components/global_transform.rs b/crates/bevy_transform/src/components/global_transform.rs index eb244cae8e..86858a20fb 100644 --- a/crates/bevy_transform/src/components/global_transform.rs +++ b/crates/bevy_transform/src/components/global_transform.rs @@ -8,7 +8,7 @@ use derive_more::derive::From; use bevy_reflect::{ReflectDeserialize, ReflectSerialize}; #[cfg(feature = "bevy-support")] -use bevy_ecs::component::Component; +use bevy_ecs::{component::Component, hierarchy::validate_parent_has_component}; #[cfg(feature = "bevy_reflect")] use { @@ -28,7 +28,7 @@ use { /// ## [`Transform`] and [`GlobalTransform`] /// /// [`Transform`] transforms an entity relative to its parent's reference frame, or relative to world space coordinates, -/// if it doesn't have a [`Parent`](bevy_hierarchy::Parent). +/// if it doesn't have a [`Parent`](bevy_ecs::hierarchy::Parent). /// /// [`GlobalTransform`] is managed by Bevy; it is computed by successively applying the [`Transform`] of each ancestor /// entity which has a Transform. This is done automatically by Bevy-internal systems in the system set @@ -45,7 +45,11 @@ use { /// [transform_example]: https://github.com/bevyengine/bevy/blob/latest/examples/transforms/transform.rs #[derive(Debug, PartialEq, Clone, Copy, From)] #[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))] -#[cfg_attr(feature = "bevy-support", derive(Component))] +#[cfg_attr( + feature = "bevy-support", + derive(Component), + component(on_insert = validate_parent_has_component::) +)] #[cfg_attr( feature = "bevy_reflect", derive(Reflect), @@ -156,7 +160,6 @@ impl GlobalTransform { /// ``` /// # use bevy_transform::prelude::{GlobalTransform, Transform}; /// # use bevy_ecs::prelude::{Entity, Query, Component, Commands}; - /// # use bevy_hierarchy::{prelude::Parent, BuildChildren}; /// #[derive(Component)] /// struct ToReparent { /// new_parent: Entity, diff --git a/crates/bevy_transform/src/components/transform.rs b/crates/bevy_transform/src/components/transform.rs index 718ca118de..2b25f0b1cf 100644 --- a/crates/bevy_transform/src/components/transform.rs +++ b/crates/bevy_transform/src/components/transform.rs @@ -19,7 +19,7 @@ use {bevy_ecs::reflect::ReflectComponent, bevy_reflect::prelude::*}; /// ## [`Transform`] and [`GlobalTransform`] /// /// [`Transform`] is the position of an entity relative to its parent position, or the reference -/// frame if it doesn't have a [`Parent`](bevy_hierarchy::Parent). +/// frame if it doesn't have a [`Parent`](bevy_ecs::hierarchy::Parent). /// /// [`GlobalTransform`] is the position of an entity relative to the reference frame. /// diff --git a/crates/bevy_transform/src/helper.rs b/crates/bevy_transform/src/helper.rs index 75e9e313d3..acff381eac 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 e1339962ef..1add9713a0 100644 --- a/crates/bevy_transform/src/plugins.rs +++ b/crates/bevy_transform/src/plugins.rs @@ -1,23 +1,15 @@ +use crate::systems::{propagate_transforms, sync_simple_transforms}; use bevy_app::{App, Plugin, PostStartup, PostUpdate}; use bevy_ecs::schedule::{IntoSystemConfigs, IntoSystemSetConfigs, SystemSet}; -use bevy_hierarchy::ValidParentCheckPlugin; - -use crate::{ - components::GlobalTransform, - systems::{propagate_transforms, sync_simple_transforms}, -}; - -#[cfg(feature = "bevy_reflect")] -use crate::components::Transform; /// Set enum for the systems relating to transform propagation #[derive(Debug, Hash, PartialEq, Eq, Clone, SystemSet)] pub enum TransformSystem { - /// Propagates changes in transform to children's [`GlobalTransform`] + /// Propagates changes in transform to children's [`GlobalTransform`](crate::components::GlobalTransform) TransformPropagate, } -/// The base plugin for handling [`Transform`] components +/// The base plugin for handling [`Transform`](crate::components::Transform) components #[derive(Default)] pub struct TransformPlugin; @@ -29,39 +21,38 @@ impl Plugin for TransformPlugin { struct PropagateTransformsSet; #[cfg(feature = "bevy_reflect")] - app.register_type::() - .register_type::(); + app.register_type::() + .register_type::(); - app.add_plugins(ValidParentCheckPlugin::::default()) - .configure_sets( - PostStartup, - PropagateTransformsSet.in_set(TransformSystem::TransformPropagate), - ) - // add transform systems to startup so the first update is "correct" - .add_systems( - PostStartup, - ( - sync_simple_transforms - .in_set(TransformSystem::TransformPropagate) - // FIXME: https://github.com/bevyengine/bevy/issues/4381 - // These systems cannot access the same entities, - // due to subtle query filtering that is not yet correctly computed in the ambiguity detector - .ambiguous_with(PropagateTransformsSet), - propagate_transforms.in_set(PropagateTransformsSet), - ), - ) - .configure_sets( - PostUpdate, - PropagateTransformsSet.in_set(TransformSystem::TransformPropagate), - ) - .add_systems( - PostUpdate, - ( - sync_simple_transforms - .in_set(TransformSystem::TransformPropagate) - .ambiguous_with(PropagateTransformsSet), - propagate_transforms.in_set(PropagateTransformsSet), - ), - ); + app.configure_sets( + PostStartup, + PropagateTransformsSet.in_set(TransformSystem::TransformPropagate), + ) + // add transform systems to startup so the first update is "correct" + .add_systems( + PostStartup, + ( + sync_simple_transforms + .in_set(TransformSystem::TransformPropagate) + // FIXME: https://github.com/bevyengine/bevy/issues/4381 + // These systems cannot access the same entities, + // due to subtle query filtering that is not yet correctly computed in the ambiguity detector + .ambiguous_with(PropagateTransformsSet), + propagate_transforms.in_set(PropagateTransformsSet), + ), + ) + .configure_sets( + PostUpdate, + PropagateTransformsSet.in_set(TransformSystem::TransformPropagate), + ) + .add_systems( + PostUpdate, + ( + sync_simple_transforms + .in_set(TransformSystem::TransformPropagate) + .ambiguous_with(PropagateTransformsSet), + propagate_transforms.in_set(PropagateTransformsSet), + ), + ); } } diff --git a/crates/bevy_transform/src/systems.rs b/crates/bevy_transform/src/systems.rs index e3f544d865..1db3be25a1 100644 --- a/crates/bevy_transform/src/systems.rs +++ b/crates/bevy_transform/src/systems.rs @@ -1,13 +1,6 @@ use crate::components::{GlobalTransform, Transform}; use alloc::vec::Vec; -use bevy_ecs::{ - change_detection::Ref, - prelude::{Changed, DetectChanges, Entity, Query, With, Without}, - query::{Added, Or}, - removal_detection::RemovedComponents, - system::{Local, ParamSet}, -}; -use bevy_hierarchy::{Children, Parent}; +use bevy_ecs::prelude::*; /// Update [`GlobalTransform`] component of entities that aren't in the hierarchy /// @@ -193,7 +186,6 @@ mod test { use bevy_tasks::{ComputeTaskPool, TaskPool}; use crate::systems::*; - use bevy_hierarchy::{BuildChildren, ChildBuild}; #[test] fn correct_parent_removed() { @@ -211,8 +203,8 @@ mod test { let root = commands.spawn(offset_transform(3.3)).id(); let parent = commands.spawn(offset_transform(4.4)).id(); let child = commands.spawn(offset_transform(5.5)).id(); - commands.entity(parent).set_parent(root); - commands.entity(child).set_parent(parent); + commands.entity(parent).insert(Parent(root)); + commands.entity(child).insert(Parent(parent)); command_queue.apply(&mut world); schedule.run(&mut world); @@ -225,7 +217,7 @@ mod test { // Remove parent of `parent` let mut command_queue = CommandQueue::default(); let mut commands = Commands::new(&mut command_queue, &world); - commands.entity(parent).remove_parent(); + commands.entity(parent).remove::(); command_queue.apply(&mut world); schedule.run(&mut world); @@ -238,7 +230,7 @@ mod test { // Remove parent of `child` let mut command_queue = CommandQueue::default(); let mut commands = Commands::new(&mut command_queue, &world); - commands.entity(child).remove_parent(); + commands.entity(child).remove::(); command_queue.apply(&mut world); schedule.run(&mut world); @@ -462,8 +454,29 @@ mod test { .spawn(Transform::IDENTITY) .add_children(&[child]); core::mem::swap( - &mut *app.world_mut().get_mut::(child).unwrap(), - &mut *temp.get_mut::(grandchild).unwrap(), + #[expect( + unsafe_code, + reason = "Parent is not mutable but this is for a test to produce a scenario that cannot happen" + )] + // SAFETY: Parent is not mutable but this is for a test to produce a scenario that cannot happen + unsafe { + &mut *app + .world_mut() + .entity_mut(child) + .get_mut_assume_mutable::() + .unwrap() + }, + // SAFETY: Parent is not mutable but this is for a test to produce a scenario that cannot happen + #[expect( + unsafe_code, + reason = "Parent is not mutable but this is for a test to produce a scenario that cannot happen" + )] + unsafe { + &mut *temp + .entity_mut(grandchild) + .get_mut_assume_mutable::() + .unwrap() + }, ); app.update(); @@ -501,7 +514,7 @@ mod test { .abs_diff_eq(2. * translation, 0.1)); // Reparent child - world.entity_mut(child).remove_parent(); + world.entity_mut(child).remove::(); world.entity_mut(parent).add_child(child); // Run schedule to propagate transforms diff --git a/crates/bevy_ui/Cargo.toml b/crates/bevy_ui/Cargo.toml index 5cad6baa4a..9ab0dadfda 100644 --- a/crates/bevy_ui/Cargo.toml +++ b/crates/bevy_ui/Cargo.toml @@ -17,7 +17,6 @@ bevy_color = { path = "../bevy_color", version = "0.16.0-dev" } bevy_core_pipeline = { path = "../bevy_core_pipeline", version = "0.16.0-dev" } bevy_derive = { path = "../bevy_derive", version = "0.16.0-dev" } bevy_ecs = { path = "../bevy_ecs", version = "0.16.0-dev" } -bevy_hierarchy = { path = "../bevy_hierarchy", version = "0.16.0-dev" } bevy_image = { path = "../bevy_image", version = "0.16.0-dev" } bevy_input = { path = "../bevy_input", version = "0.16.0-dev" } bevy_math = { path = "../bevy_math", version = "0.16.0-dev" } diff --git a/crates/bevy_ui/src/experimental/ghost_hierarchy.rs b/crates/bevy_ui/src/experimental/ghost_hierarchy.rs index 75444ce8e3..28b07c4903 100644 --- a/crates/bevy_ui/src/experimental/ghost_hierarchy.rs +++ b/crates/bevy_ui/src/experimental/ghost_hierarchy.rs @@ -2,14 +2,11 @@ use crate::Node; use bevy_ecs::{prelude::*, system::SystemParam}; -use bevy_hierarchy::{Children, Parent}; use bevy_reflect::prelude::*; use bevy_render::view::Visibility; use bevy_transform::prelude::Transform; use core::marker::PhantomData; -#[cfg(feature = "ghost_nodes")] -use bevy_hierarchy::HierarchyQueryExt; #[cfg(feature = "ghost_nodes")] use smallvec::SmallVec; @@ -169,10 +166,7 @@ impl<'w, 's> UiChildren<'w, 's> { /// Returns the UI parent of the provided entity. pub fn get_parent(&'s self, entity: Entity) -> Option { - self.parents_query - .get(entity) - .ok() - .map(|parent| parent.entity()) + self.parents_query.get(entity).ok().map(|parent| parent.0) } /// Given an entity in the UI hierarchy, check if its set of children has changed, e.g if children has been added/removed or if the order has changed. @@ -222,7 +216,6 @@ mod tests { system::{Query, SystemState}, world::World, }; - use bevy_hierarchy::{BuildChildren, ChildBuild}; use super::{GhostNode, Node, UiChildren, UiRootNodes}; diff --git a/crates/bevy_ui/src/layout/mod.rs b/crates/bevy_ui/src/layout/mod.rs index 1397b1c755..3fa17fd998 100644 --- a/crates/bevy_ui/src/layout/mod.rs +++ b/crates/bevy_ui/src/layout/mod.rs @@ -4,15 +4,10 @@ use crate::{ OverflowAxis, ScrollPosition, TargetCamera, UiScale, Val, }; use bevy_ecs::{ - change_detection::{DetectChanges, DetectChangesMut}, - entity::{Entity, EntityBorrow, EntityHashMap, EntityHashSet}, - event::EventReader, - query::With, - removal_detection::RemovedComponents, - system::{Commands, Local, Query, Res, ResMut, SystemParam}, - world::Ref, + entity::{EntityHashMap, EntityHashSet}, + prelude::*, + system::SystemParam, }; -use bevy_hierarchy::{Children, Parent}; use bevy_math::{UVec2, Vec2}; use bevy_render::camera::{Camera, NormalizedRenderTarget}; use bevy_sprite::BorderRect; @@ -473,18 +468,7 @@ mod tests { use bevy_asset::{AssetEvent, Assets}; use bevy_core_pipeline::core_2d::Camera2d; - use bevy_ecs::{ - entity::Entity, - event::Events, - prelude::{Commands, Component, In, Query, With}, - query::Without, - schedule::{ApplyDeferred, IntoSystemConfigs, Schedule}, - system::RunSystemOnce, - world::World, - }; - use bevy_hierarchy::{ - despawn_with_children_recursive, BuildChildren, ChildBuild, Children, Parent, - }; + use bevy_ecs::{prelude::*, system::RunSystemOnce}; use bevy_image::Image; use bevy_math::{Rect, UVec2, Vec2}; use bevy_render::{camera::ManualTextureViews, prelude::Camera}; @@ -781,7 +765,7 @@ mod tests { } // despawn the parent entity and its descendants - despawn_with_children_recursive(&mut world, ui_parent_entity, true); + world.entity_mut(ui_parent_entity).despawn(); ui_schedule.run(&mut world); diff --git a/crates/bevy_ui/src/stack.rs b/crates/bevy_ui/src/stack.rs index b8031df371..fdfc2fd9c6 100644 --- a/crates/bevy_ui/src/stack.rs +++ b/crates/bevy_ui/src/stack.rs @@ -132,7 +132,6 @@ mod tests { system::Commands, world::{CommandQueue, World}, }; - use bevy_hierarchy::{BuildChildren, ChildBuild}; use crate::{GlobalZIndex, Node, UiStack, ZIndex}; diff --git a/crates/bevy_winit/Cargo.toml b/crates/bevy_winit/Cargo.toml index 602063990c..fc0cb747bb 100644 --- a/crates/bevy_winit/Cargo.toml +++ b/crates/bevy_winit/Cargo.toml @@ -26,7 +26,6 @@ bevy_a11y = { path = "../bevy_a11y", version = "0.16.0-dev" } bevy_app = { path = "../bevy_app", version = "0.16.0-dev" } bevy_derive = { path = "../bevy_derive", version = "0.16.0-dev" } bevy_ecs = { path = "../bevy_ecs", version = "0.16.0-dev" } -bevy_hierarchy = { path = "../bevy_hierarchy", version = "0.16.0-dev" } bevy_input = { path = "../bevy_input", version = "0.16.0-dev" } bevy_input_focus = { path = "../bevy_input_focus", version = "0.16.0-dev" } bevy_log = { path = "../bevy_log", version = "0.16.0-dev" } diff --git a/crates/bevy_winit/src/accessibility.rs b/crates/bevy_winit/src/accessibility.rs index 57672ec3b4..939916f6eb 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 60195426e8..0b2616c187 100644 --- a/examples/3d/color_grading.rs +++ b/examples/3d/color_grading.rs @@ -164,7 +164,7 @@ fn add_buttons(commands: &mut Commands, font: &Handle, color_grading: &Col /// Adds the buttons for the global controls (those that control the scene as a /// whole as opposed to shadows, midtones, or highlights). fn add_buttons_for_global_controls( - parent: &mut ChildBuilder, + parent: &mut ChildSpawnerCommands, color_grading: &ColorGrading, font: &Handle, ) { @@ -196,7 +196,7 @@ fn add_buttons_for_global_controls( /// Adds the buttons that control color grading for individual sections /// (highlights, midtones, shadows). fn add_buttons_for_section( - parent: &mut ChildBuilder, + parent: &mut ChildSpawnerCommands, section: SelectedColorGradingSection, color_grading: &ColorGrading, font: &Handle, @@ -234,7 +234,7 @@ fn add_buttons_for_section( /// Adds a button that controls one of the color grading values. fn add_button_for_value( - parent: &mut ChildBuilder, + parent: &mut ChildSpawnerCommands, option: SelectedColorGradingOption, color_grading: &ColorGrading, font: &Handle, @@ -315,7 +315,7 @@ fn add_help_text( /// Adds some text to the scene. fn add_text<'a>( - parent: &'a mut ChildBuilder, + parent: &'a mut ChildSpawnerCommands, label: &str, font: &Handle, color: Color, diff --git a/examples/3d/mixed_lighting.rs b/examples/3d/mixed_lighting.rs index 80d139714a..378070afc3 100644 --- a/examples/3d/mixed_lighting.rs +++ b/examples/3d/mixed_lighting.rs @@ -460,7 +460,7 @@ fn move_sphere( }; // Grab its transform. - let Ok(mut transform) = transforms.get_mut(**parent) else { + let Ok(mut transform) = transforms.get_mut(parent.0) else { return; }; diff --git a/examples/3d/order_independent_transparency.rs b/examples/3d/order_independent_transparency.rs index 74e8edc4d1..62854957f2 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 2faf8a1e68..a37727d1e9 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 17bc26439b..d8d4857846 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 3dc80f3eba..f31b2ccd5e 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 1bde5c909f..72408260d6 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 0df4f71aec..5ec34b0d46 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 c5fce88570..13a8f5425c 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 dbd5b63432..76209cd109 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 2206a5f101..e302ab6e27 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 15f1ca5483..3e05fe2b55 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 c8a91ab5fd..8749fe988b 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 ef12bb352c..ad6bb46042 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 40f9f96776..8ca179f55e 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 ae8e5626af..3c47650949 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 e6fc7e41e6..4c5ea224e3 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 820c140462..f5b1415d3e 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 3e83b6fda9..69f81b64b9 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 a09f4f81dc..767e3fb05d 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 314252c0b5..15620d3a52 100644 --- a/examples/stress_tests/many_buttons.rs +++ b/examples/stress_tests/many_buttons.rs @@ -248,7 +248,7 @@ fn setup_grid(mut commands: Commands, asset_server: Res, args: Res< } fn spawn_button( - commands: &mut ChildBuilder, + commands: &mut ChildSpawnerCommands, background_color: Color, buttons: f32, column: usize, @@ -296,5 +296,5 @@ fn spawn_button( } fn despawn_ui(mut commands: Commands, root_node: Single, Without)>) { - commands.entity(*root_node).despawn_recursive(); + commands.entity(*root_node).despawn(); } diff --git a/examples/ui/display_and_visibility.rs b/examples/ui/display_and_visibility.rs index 48cf17c767..239b9814db 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 901a4af124..2a155eafb1 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 6315283bec..60a95c8e9f 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 cfa1fb4348..400c257166 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 c98f8a500a..d0bcc6af33 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 6c8dc504c4..d3e608ca2c 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 94ef68b751..c6060bd848 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 930aca48f9..9e64399f0d 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 a2cb86a680..660d457495 100644 --- a/tests/how_to_test_systems.rs +++ b/tests/how_to_test_systems.rs @@ -26,7 +26,7 @@ fn despawn_dead_enemies( ) { for (entity, enemy) in &enemies { if enemy.hit_points == 0 { - commands.entity(entity).despawn_recursive(); + commands.entity(entity).despawn(); dead_enemies.send(EnemyDied(enemy.score_value)); } } diff --git a/tools/ci/src/commands/compile_check_no_std.rs b/tools/ci/src/commands/compile_check_no_std.rs index 83c9aec0d0..b76194d1c2 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 ae6e869c98..45169abb24 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