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<Relationship = Self>;
    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<Entity>);
```

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::<Children>().unwrap(), &[b]);
```

There are also convenience APIs for spawning more than one entity with
the same relationship:

```rust
world.spawn_empty().with_related::<Children>(|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<Entity>);
```

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::<Children>()
    .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::<Children>()`
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::<Children>()`.
* 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 <alice.i.cecile@gmail.com>
This commit is contained in:
Carter Anderson 2025-01-18 14:20:30 -08:00 committed by GitHub
parent b66c3ceb0e
commit 21f1e3045c
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
114 changed files with 1889 additions and 3108 deletions

View File

@ -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",

View File

@ -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<B: Bundle + Default + GetTypeRegistration>(
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);
}
}

View File

@ -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};

View File

@ -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();
});
});
});

View File

@ -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"] }

View File

@ -104,6 +104,8 @@ impl Default for App {
{
app.init_resource::<AppTypeRegistry>();
app.register_type::<Name>();
app.register_type::<Parent>();
app.register_type::<Children>();
}
#[cfg(feature = "reflect_functions")]

View File

@ -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",

View File

@ -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<T: Decodable + Asset>(
) {
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 {

View File

@ -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" }

View File

@ -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::{

View File

@ -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<TokenStream2> =
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(<Self as #bevy_ecs_path::relationship::Relationship>::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(<Self as #bevy_ecs_path::relationship::Relationship>::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(<Self as #bevy_ecs_path::relationship::RelationshipTarget>::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(<Self as #bevy_ecs_path::relationship::RelationshipTarget>::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::<Self>::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::<Self>::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<ExprPath>,
on_replace: Option<ExprPath>,
on_remove: Option<ExprPath>,
on_despawn: Option<ExprPath>,
relationship: Option<Relationship>,
relationship_target: Option<RelationshipTarget>,
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<Attrs> {
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<Attrs> {
} else if nested.path.is_ident(ON_REMOVE) {
attrs.on_remove = Some(nested.value()?.parse::<ExprPath>()?);
Ok(())
} else if nested.path.is_ident(ON_DESPAWN) {
attrs.on_despawn = Some(nested.value()?.parse::<ExprPath>()?);
Ok(())
} else if nested.path.is_ident(IMMUTABLE) {
attrs.immutable = true;
Ok(())
@ -301,6 +403,12 @@ fn parse_component_attr(ast: &DeriveInput) -> Result<Attrs> {
} else {
attrs.requires = Some(punctuated);
}
} else if attr.path().is_ident(RELATIONSHIP) {
let relationship = attr.parse_args::<Relationship>()?;
attrs.relationship = Some(relationship);
} else if attr.path().is_ident(RELATIONSHIP_TARGET) {
let relationship_target = attr.parse_args::<RelationshipTarget>()?;
attrs.relationship_target = Some(relationship_target);
}
}
@ -341,3 +449,158 @@ fn hook_register_function_call(
) -> Option<TokenStream2> {
function.map(|meta| quote! { hooks. #hook (#meta); })
}
impl Parse for Relationship {
fn parse(input: syn::parse::ParseStream) -> Result<Self> {
syn::custom_keyword!(relationship_target);
input.parse::<relationship_target>()?;
input.parse::<Token![=]>()?;
Ok(Relationship {
relationship_target: input.parse::<Ident>()?,
})
}
}
impl Parse for RelationshipTarget {
fn parse(input: syn::parse::ParseStream) -> Result<Self> {
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::<relationship>()?;
input.parse::<Token![=]>()?;
relationship_ident = Some(input.parse::<Ident>()?);
} else if input.peek(despawn_descendants) {
input.parse::<despawn_descendants>()?;
despawn_descendants_exists = true;
} else {
done = true;
}
if input.peek(Token![,]) {
input.parse::<Token![,]>()?;
}
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<Option<TokenStream2>> {
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<Option<TokenStream2>> {
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<Entity>))";
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)
}
}
}))
}

View File

@ -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)
}

View File

@ -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.

View File

@ -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();

View File

@ -563,6 +563,7 @@ pub struct ComponentHooks {
pub(crate) on_insert: Option<ComponentHook>,
pub(crate) on_replace: Option<ComponentHook>,
pub(crate) on_remove: Option<ComponentHook>,
pub(crate) on_despawn: Option<ComponentHook>,
}
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`]

View File

@ -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::<Children>(
ComponentCloneHandler::custom_handler(component_clone_children),
)
} else {
self.remove_component_clone_handler_override::<Children>()
}
}
/// 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::<Parent>(ComponentCloneHandler::custom_handler(
component_clone_parent,
))
} else {
self.remove_component_clone_handler_override::<Parent>()
}
}
/// 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::<Children>()
.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::<Parent>()
.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;

View File

@ -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::<Children>().unwrap(), &[child1, child2]);
/// assert_eq!(&**world.entity(child1).get::<Children>().unwrap(), &[grandchild]);
///
/// world.entity_mut(child2).remove::<Parent>();
/// assert_eq!(&**world.entity(root).get::<Children>().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::<Children>().unwrap(), &[child1, child2]);
/// assert_eq!(&**world.entity(child1).get::<Children>().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<Entity>);
impl<'a> IntoIterator for &'a Children {
type Item = <Self::IntoIter as Iterator>::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::<Parent>(children)
}
/// Adds the given child to this entity
pub fn add_child(&mut self, child: Entity) -> &mut Self {
self.add_related::<Parent>(&[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::<Parent>()")]
pub fn remove_parent(&mut self) -> &mut Self {
self.remove::<Parent>();
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<Parent>),
) -> &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::<Parent>(children)
}
/// Adds the given child to this entity
pub fn add_child(&mut self, child: Entity) -> &mut Self {
self.add_related::<Parent>(&[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::<Parent>()")]
pub fn remove_parent(&mut self) -> &mut Self {
self.remove::<Parent>();
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<C: Component>(
world: DeferredWorld,
entity: Entity,
_: ComponentId,
) {
let entity_ref = world.entity(entity);
let Some(child_of) = entity_ref.get::<Parent>() else {
return;
};
if !world
.get_entity(child_of.get())
.is_ok_and(|e| e.contains::<C>())
{
// TODO: print name here once Name lives in bevy_ecs
let name: Option<String> = 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::<C>(),
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<Node>,
}
impl Node {
fn new(entity: Entity) -> Self {
Self {
entity,
children: Vec::new(),
}
}
fn new_with(entity: Entity, children: Vec<Node>) -> Self {
Self { entity, children }
}
}
fn get_hierarchy(world: &World, entity: Entity) -> Node {
Node {
entity,
children: world
.entity(entity)
.get::<Children>()
.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::<Parent>();
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::<Parent>().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::<Parent>().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>(),
"Parent should still be there"
);
}
}

View File

@ -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},

View File

@ -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<ComponentId, CachedObservers>,
}
@ -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::<ResB>().is_none());
}
#[test]

View File

@ -301,14 +301,16 @@ impl<C: Component + Reflect + TypePath> FromType<C> for ReflectComponent {
component.apply(reflected_component);
},
apply_or_insert: |entity, reflected_component, registry| {
if !C::Mutability::MUTABLE {
let name = ShortName::of::<C>();
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::<C>() } {
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::<C>() } {
component.apply(reflected_component.as_partial_reflect());
} else {
let component = entity.world_scope(|world| {
from_reflect_with_fallback::<C>(reflected_component, world, registry)
});
entity.insert(component);
}
} else {
let component = entity.world_scope(|world| {
from_reflect_with_fallback::<C>(reflected_component, world, registry)

View File

@ -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<Entity>);
/// ```
///
/// 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<Entity>);
/// ```
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<Relationship = Self>;
/// 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::<Self>().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::<Self>(),
core::any::type_name::<Self>()
);
world.commands().entity(entity).remove::<Self>();
}
if let Ok(mut target_entity_mut) = world.get_entity_mut(target_entity) {
if let Some(mut relationship_target) =
target_entity_mut.get_mut::<Self::RelationshipTarget>()
{
relationship_target.collection_mut_risky().add(entity);
} else {
let mut target = <Self::RelationshipTarget as RelationshipTarget>::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::<Self>(),
core::any::type_name::<Self>()
);
world.commands().entity(entity).remove::<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) {
let target_entity = world.entity(entity).get::<Self>().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::<Self::RelationshipTarget>()
{
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::<Self::RelationshipTarget>()
.is_some_and(RelationshipTarget::is_empty)
{
entity.remove::<Self::RelationshipTarget>();
}
});
}
}
}
}
}
}
/// 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<Mutability = Mutable> + Sized {
/// The [`Relationship`] that populates this [`RelationshipTarget`] collection.
type Relationship: Relationship<RelationshipTarget = Self>;
/// 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::<Self>().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::<Self::Relationship>()
.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::<Self>().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 =
<Self::Collection as RelationshipSourceCollection>::with_capacity(capacity);
Self::from_collection_risky(collection)
}
/// Iterates the entities stored in this collection.
#[inline]
fn iter(&self) -> impl DoubleEndedIterator<Item = Entity> {
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<Entity>);
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::<LikedBy>().unwrap().0, &[b, c]);
}
}

View File

@ -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<R: Relationship>(
&mut self,
func: impl FnOnce(&mut RelatedSpawner<R>),
) -> &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<R: Relationship>(&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<S: RelationshipTarget>(&mut self) -> &mut Self {
if let Some(sources) = self.take::<S>() {
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<R: Relationship>(
&mut self,
func: impl FnOnce(&mut RelatedSpawnerCommands<R>),
) -> &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<R: Relationship>(&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<S: RelationshipTarget>(&mut self) -> &mut Self {
let id = self.id();
self.commands.queue(move |world: &mut World| {
world.entity_mut(id).despawn_related::<S>();
});
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<R>,
}
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<R>,
}
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
}
}

View File

@ -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<R: Relationship>(&'w self, entity: Entity) -> Option<Entity>
where
<D as QueryData>::ReadOnly: WorldQuery<Item<'w> = &'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<S: RelationshipTarget>(
&'w self,
entity: Entity,
) -> impl Iterator<Item = Entity> + 'w
where
<D as QueryData>::ReadOnly: WorldQuery<Item<'w> = &'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<R: Relationship>(&'w self, entity: Entity) -> Entity
where
<D as QueryData>::ReadOnly: WorldQuery<Item<'w> = &'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<S: RelationshipTarget>(
&'w self,
entity: Entity,
) -> impl Iterator<Item = Entity> + 'w
where
<D as QueryData>::ReadOnly: WorldQuery<Item<'w> = &'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<R: Relationship>(
&'w self,
entity: Entity,
) -> impl Iterator<Item = Entity> + 'w
where
D::ReadOnly: WorldQuery<Item<'w> = (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<S: RelationshipTarget>(
&'w self,
entity: Entity,
) -> DescendantIter<'w, 's, D, F, S>
where
D::ReadOnly: WorldQuery<Item<'w> = &'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<S: RelationshipTarget>(
&'w self,
entity: Entity,
) -> DescendantDepthFirstIter<'w, 's, D, F, S>
where
D::ReadOnly: WorldQuery<Item<'w> = &'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<R: Relationship>(
&'w self,
entity: Entity,
) -> AncestorIter<'w, 's, D, F, R>
where
D::ReadOnly: WorldQuery<Item<'w> = &'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<Item<'w> = &'w S>,
{
children_query: &'w Query<'w, 's, D, F>,
vecdeque: VecDeque<Entity>,
}
impl<'w, 's, D: QueryData, F: QueryFilter, S: RelationshipTarget> DescendantIter<'w, 's, D, F, S>
where
D::ReadOnly: WorldQuery<Item<'w> = &'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<Item<'w> = &'w S>,
{
type Item = Entity;
fn next(&mut self) -> Option<Self::Item> {
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<Item<'w> = &'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<Item<'w> = &'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<Item<'w> = &'w S>,
{
type Item = Entity;
fn next(&mut self) -> Option<Self::Item> {
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<Item<'w> = &'w R>,
{
parent_query: &'w Query<'w, 's, D, F>,
next: Option<Entity>,
}
impl<'w, 's, D: QueryData, F: QueryFilter, R: Relationship> AncestorIter<'w, 's, D, F, R>
where
D::ReadOnly: WorldQuery<Item<'w> = &'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<Item<'w> = &'w R>,
{
type Item = Entity;
fn next(&mut self) -> Option<Self::Item> {
self.next = self.parent_query.get(self.next?).ok().map(R::get);
self.next
}
}

View File

@ -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<Item = Entity>;
/// 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<Entity> {
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<Item = Entity> {
<[Entity]>::iter(self).copied()
}
fn len(&self) -> usize {
Vec::len(self)
}
}

View File

@ -254,8 +254,8 @@ pub fn retain<T: Bundle>() -> 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();

View File

@ -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());
}

View File

@ -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<D> Traversal<D> 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<R: Relationship, D> Traversal<D> for &R {
fn traverse(item: Self::Item<'_>, _data: &D) -> Option<Entity> {
Some(item.get())
}
}

View File

@ -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;

View File

@ -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<Item = ComponentId>,
) {
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

View File

@ -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::<TestVec>()
.0
.push("OrdA hook on_insert");
world.commands().entity(entity).despawn();
world.commands().entity(entity).remove::<OrdA>();
world.commands().entity(entity).remove::<OrdB>();
}
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();

View File

@ -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::<B>(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::<B>(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};

View File

@ -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" }

View File

@ -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());

View File

@ -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

View File

@ -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)

File diff suppressed because it is too large Load Diff

View File

@ -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<F>(&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<K, F>(&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<K, F>(&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<F>(&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<K, F>(&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 = <Self::IntoIter as Iterator>::Item;
type IntoIter = slice::Iter<'a, Entity>;
#[inline(always)]
fn into_iter(self) -> Self::IntoIter {
self.0.iter()
}
}

View File

@ -1,5 +0,0 @@
mod children;
mod parent;
pub use children::Children;
pub use parent::Parent;

View File

@ -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<D> Traversal<D> for &Parent {
fn traverse(item: Self::Item<'_>, _data: &D) -> Option<Entity> {
Some(item.0)
}
}

View File

@ -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,
},
}

View File

@ -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::<Parent>(entity).map(|parent| parent.0) {
if let Some(mut children) = world.get_mut::<Children>(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::<Children>(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::<Children>() {
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::<Children>(
ComponentCloneHandler::custom_handler(component_clone_children),
)
} else {
self.remove_component_clone_handler_override::<Children>()
}
}
fn as_child(&mut self, as_child: bool) -> &mut Self {
if as_child {
self.override_component_clone_handler::<Parent>(ComponentCloneHandler::custom_handler(
component_clone_parent,
))
} else {
self.remove_component_clone_handler_override::<Parent>()
}
}
}
/// 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::<Children>()
.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::<Parent>()
.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::<Children>(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::<Vec<_>>();
results.sort_unstable_by_key(|(_, index)| *index);
{
let children = world.get::<Children>(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::<Children>().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::<Children>();
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::<Component1>(e_clone)
.is_some_and(|c| *c == parent_component));
let children = world.get::<Children>(e_clone).unwrap();
for (child, (component1, component2)) in children.iter().zip([
(child1_component, child1_1_component),
(child2_component, child2_1_component),
]) {
assert!(world
.get::<Component1>(*child)
.is_some_and(|c| *c == component1));
for child2 in world.get::<Children>(*child).unwrap().iter() {
assert!(world
.get::<Component1>(*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::<Children>()
.is_some_and(|c| c.contains(&child_clone)));
}
}

View File

@ -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::<Children>().register_type::<Parent>();
app.add_event::<HierarchyEvent>();
}
}

View File

@ -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<Entity>
where
D::ReadOnly: WorldQuery<Item<'w> = &'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<Item<'w> = &'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<Item<'w> = &'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<Item = Entity> + 'w
where
D::ReadOnly: WorldQuery<Item<'w> = &'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<Item = Entity>
where
D::ReadOnly: WorldQuery<Item<'w> = (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<Entity, With<Marker>>, 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<Item<'w> = &'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<Item<'w> = &'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<Entity, With<Marker>>, 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<Item<'w> = &'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<Entity>
where
<D as QueryData>::ReadOnly: WorldQuery<Item<'w> = &'w Parent>,
{
self.get(entity).map(Parent::get).ok()
}
fn children(&'w self, entity: Entity) -> &'w [Entity]
where
<D as QueryData>::ReadOnly: WorldQuery<Item<'w> = &'w Children>,
{
self.get(entity)
.map_or(&[] as &[Entity], |children| children)
}
fn root_ancestor(&'w self, entity: Entity) -> Entity
where
<D as QueryData>::ReadOnly: WorldQuery<Item<'w> = &'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<Item = Entity>
where
<D as QueryData>::ReadOnly: WorldQuery<Item<'w> = &'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<Item = Entity>
where
D::ReadOnly: WorldQuery<Item<'w> = (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<Item<'w> = &'w Children>,
{
DescendantIter::new(self, entity)
}
fn iter_descendants_depth_first(
&'w self,
entity: Entity,
) -> DescendantDepthFirstIter<'w, 's, D, F>
where
D::ReadOnly: WorldQuery<Item<'w> = &'w Children>,
{
DescendantDepthFirstIter::new(self, entity)
}
fn iter_ancestors(&'w self, entity: Entity) -> AncestorIter<'w, 's, D, F>
where
D::ReadOnly: WorldQuery<Item<'w> = &'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<Item<'w> = &'w Children>,
{
children_query: &'w Query<'w, 's, D, F>,
vecdeque: VecDeque<Entity>,
}
impl<'w, 's, D: QueryData, F: QueryFilter> DescendantIter<'w, 's, D, F>
where
D::ReadOnly: WorldQuery<Item<'w> = &'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<Item<'w> = &'w Children>,
{
type Item = Entity;
fn next(&mut self) -> Option<Self::Item> {
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<Item<'w> = &'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<Item<'w> = &'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<Item<'w> = &'w Children>,
{
type Item = Entity;
fn next(&mut self) -> Option<Self::Item> {
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<Item<'w> = &'w Parent>,
{
parent_query: &'w Query<'w, 's, D, F>,
next: Option<Entity>,
}
impl<'w, 's, D: QueryData, F: QueryFilter> AncestorIter<'w, 's, D, F>
where
D::ReadOnly: WorldQuery<Item<'w> = &'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<Item<'w> = &'w Parent>,
{
type Item = Entity;
fn next(&mut self) -> Option<Self::Item> {
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::<Query<&Parent>>::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());
}
}

View File

@ -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<T>`].
///
/// This resource is added by [`ValidParentCheckPlugin<T>`].
/// 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<T> {
/// Whether to run [`check_hierarchy_component_has_valid_parent<T>`].
pub enabled: bool,
_comp: PhantomData<fn(T)>,
}
impl<T> ReportHierarchyIssue<T> {
/// Constructs a new object
pub fn new(enabled: bool) -> Self {
ReportHierarchyIssue {
enabled,
_comp: Default::default(),
}
}
}
impl<T> PartialEq for ReportHierarchyIssue<T> {
fn eq(&self, other: &Self) -> bool {
self.enabled == other.enabled
}
}
impl<T> Default for ReportHierarchyIssue<T> {
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<T: Component>(
parent_query: Query<
(Entity, &Parent, Option<&Name>),
(With<T>, Or<(Changed<Parent>, Added<T>)>),
>,
component_query: Query<(), With<T>>,
mut already_diagnosed: Local<HashSet<Entity>>,
) {
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::<T>(),
name = name.map_or_else(|| format!("Entity {}", entity), |s| format!("The {s} entity")),
);
}
}
}
/// Run criteria that only allows running when [`ReportHierarchyIssue<T>`] is enabled.
pub fn on_hierarchy_reports_enabled<T>(report: Res<ReportHierarchyIssue<T>>) -> 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<T: Component>(PhantomData<fn() -> T>);
impl<T: Component> Default for ValidParentCheckPlugin<T> {
fn default() -> Self {
Self(PhantomData)
}
}
#[cfg(feature = "bevy_app")]
impl<T: Component> bevy_app::Plugin for ValidParentCheckPlugin<T> {
fn build(&self, app: &mut bevy_app::App) {
app.init_resource::<ReportHierarchyIssue<T>>().add_systems(
bevy_app::Last,
check_hierarchy_component_has_valid_parent::<T>
.run_if(resource_equals(ReportHierarchyIssue::<T>::new(true))),
);
}
}

View File

@ -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 = [

View File

@ -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,

View File

@ -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<TabNavigation> = SystemState::new(world);
let tab_navigation = system_state.get(world);

View File

@ -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" }

View File

@ -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")))]

View File

@ -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;

View File

@ -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)]

View File

@ -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 }

View File

@ -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;

View File

@ -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();
}
}

View File

@ -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" }

View File

@ -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::<Parent>();
}
}

View File

@ -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",

View File

@ -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::<view::InheritedVisibility>::default(),
WindowRenderPlugin,
CameraPlugin,
ViewPlugin,

View File

@ -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,

View File

@ -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::<Self>)]
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();

View File

@ -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<Path>) -> impl FnMut(Trigger<ScreenshotCapt
fn clear_screenshots(mut commands: Commands, screenshots: Query<Entity, With<Captured>>) {
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());

View File

@ -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 }

View File

@ -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!(

View File

@ -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::<ComponentA>();
@ -854,7 +852,7 @@ mod tests {
.run_system_once(
|mut commands: Commands, query: Query<Entity, With<ComponentF>>| {
for entity in query.iter() {
commands.entity(entity).despawn_recursive();
commands.entity(entity).despawn();
}
},
)

View File

@ -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

View File

@ -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<S>`]
/// 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<S: States>(
mut commands: Commands,
mut transitions: EventReader<StateTransitionEvent<S>>,
@ -92,9 +86,6 @@ pub fn clear_state_scoped_entities<S: States>(
};
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();
}
}

View File

@ -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 = [

View File

@ -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<Font> = Default::default();
/// # let mut world = World::default();
@ -510,7 +508,7 @@ pub fn detect_text_needs_rerender<Root: Component>(
));
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<Root: Component>(
));
break;
};
parent = **next_parent;
parent = next_parent.0;
}
}
}

View File

@ -4,7 +4,6 @@ use bevy_ecs::{
prelude::*,
system::{Query, SystemParam},
};
use bevy_hierarchy::Children;
use crate::{TextColor, TextFont, TextSpan};

View File

@ -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",

View File

@ -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::<Parent>();
// 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::<GlobalTransform>()?;

View File

@ -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::<GlobalTransform>)
)]
#[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,

View File

@ -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.
///

View File

@ -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());

View File

@ -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::<Transform>()
.register_type::<GlobalTransform>();
app.register_type::<crate::components::Transform>()
.register_type::<crate::components::GlobalTransform>();
app.add_plugins(ValidParentCheckPlugin::<GlobalTransform>::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),
),
);
}
}

View File

@ -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::<Parent>();
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::<Parent>();
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::<Parent>(child).unwrap(),
&mut *temp.get_mut::<Parent>(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::<Parent>()
.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::<Parent>()
.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::<Parent>();
world.entity_mut(parent).add_child(child);
// Run schedule to propagate transforms

View File

@ -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" }

View File

@ -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<Entity> {
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};

View File

@ -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);

View File

@ -132,7 +132,6 @@ mod tests {
system::Commands,
world::{CommandQueue, World},
};
use bevy_hierarchy::{BuildChildren, ChildBuild};
use crate::{GlobalZIndex, Node, UiStack, ZIndex};

View File

@ -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" }

View File

@ -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.

View File

@ -164,7 +164,7 @@ fn add_buttons(commands: &mut Commands, font: &Handle<Font>, 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<Font>,
) {
@ -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<Font>,
@ -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<Font>,
@ -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<Font>,
color: Color,

View File

@ -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;
};

View File

@ -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;

View File

@ -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),

View File

@ -187,7 +187,7 @@ fn set_visibility_ranges(
break;
}
match parent {
Some(parent) => current = **parent,
Some(parent) => current = parent.0,
None => break,
}
}

View File

@ -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"),

View File

@ -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,

View File

@ -268,7 +268,7 @@ fn get_async_loading_state(
fn despawn_loading_state_entities(mut commands: Commands, loading: Query<Entity, With<Loading>>) {
// 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.

View File

@ -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();
}
}
}

View File

@ -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<T: Component>(mut commands: Commands, query: Query<Entity, With<T>>) {
for e in &query {
commands.entity(e).despawn_recursive();
commands.entity(e).despawn();
}
}

View File

@ -24,7 +24,7 @@ fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
))
// 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();
}
}
}

View File

@ -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);
}

View File

@ -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);

View File

@ -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<T: Component>(to_despawn: Query<Entity, With<T>>, mut commands: Commands) {
for entity in &to_despawn {
commands.entity(entity).despawn_recursive();
commands.entity(entity).despawn();
}
}

View File

@ -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();
}
}

View File

@ -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<T>(
parent: &mut ChildBuilder,
parent: &mut ChildSpawnerCommands,
option_value: T,
option_name: &str,
is_selected: bool,
@ -91,8 +91,11 @@ pub fn spawn_option_button<T>(
/// 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<T>(parent: &mut ChildBuilder, title: &str, options: &[(T, &str)])
where
pub fn spawn_option_buttons<T>(
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> {

View File

@ -409,7 +409,7 @@ mod ui {
}
pub fn cleanup_menu(mut commands: Commands, menu_data: Res<MenuData>) {
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<AssetServer>) {

View File

@ -163,7 +163,7 @@ fn menu(
}
fn cleanup_menu(mut commands: Commands, menu_data: Res<MenuData>) {
commands.entity(menu_data.button_entity).despawn_recursive();
commands.entity(menu_data.button_entity).despawn();
}
const SPEED: f32 = 100.0;

Some files were not shown because too many files have changed in this diff Show More