Skip to content

Preserve spawned RelationshipTarget order and other improvements #17858

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 12 commits into from
Mar 5, 2025
6 changes: 3 additions & 3 deletions benches/benches/bevy_ecs/entity_cloning.rs
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ type ComplexBundle = (C1, C2, C3, C4, C5, C6, C7, C8, C9, C10);
/// use the [`Reflect`] trait instead of [`Clone`].
fn reflection_cloner<B: Bundle + GetTypeRegistration>(
world: &mut World,
recursive: bool,
linked_cloning: bool,
) -> EntityCloner {
// Get mutable access to the type registry, creating it if it does not exist yet.
let registry = world.get_resource_or_init::<AppTypeRegistry>();
Expand All @@ -77,7 +77,7 @@ fn reflection_cloner<B: Bundle + GetTypeRegistration>(
for component in component_ids {
builder.override_clone_behavior_with_id(component, ComponentCloneBehavior::reflect());
}
builder.recursive(recursive);
builder.linked_cloning(linked_cloning);

builder.finish()
}
Expand Down Expand Up @@ -136,7 +136,7 @@ fn bench_clone_hierarchy<B: Bundle + Default + GetTypeRegistration>(
reflection_cloner::<B>(&mut world, true)
} else {
let mut builder = EntityCloner::build(&mut world);
builder.recursive(true);
builder.linked_cloning(true);
builder.finish()
};

Expand Down
20 changes: 16 additions & 4 deletions crates/bevy_ecs/macros/src/component.rs
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,12 @@ pub fn derive_component(input: TokenStream) -> TokenStream {
Err(err) => err.into_compile_error().into(),
};

let visit_entities = visit_entities(&ast.data, &bevy_ecs_path, relationship.is_some());
let visit_entities = visit_entities(
&ast.data,
&bevy_ecs_path,
relationship.is_some(),
relationship_target.is_some(),
);

let storage = storage_path(&bevy_ecs_path, attrs.storage);

Expand Down Expand Up @@ -207,7 +212,7 @@ pub fn derive_component(input: TokenStream) -> TokenStream {
.unwrap_or(quote! { #bevy_ecs_path::component::Mutable });

let clone_behavior = if relationship_target.is_some() {
quote!(#bevy_ecs_path::component::ComponentCloneBehavior::RelationshipTarget(#bevy_ecs_path::relationship::clone_relationship_target::<Self>))
quote!(#bevy_ecs_path::component::ComponentCloneBehavior::Custom(#bevy_ecs_path::relationship::clone_relationship_target::<Self>))
} else {
quote!(
use #bevy_ecs_path::component::{DefaultCloneBehaviorBase, DefaultCloneBehaviorViaClone};
Expand Down Expand Up @@ -255,7 +260,12 @@ pub fn derive_component(input: TokenStream) -> TokenStream {
})
}

fn visit_entities(data: &Data, bevy_ecs_path: &Path, is_relationship: bool) -> TokenStream2 {
fn visit_entities(
data: &Data,
bevy_ecs_path: &Path,
is_relationship: bool,
is_relationship_target: bool,
) -> TokenStream2 {
match data {
Data::Struct(DataStruct { fields, .. }) => {
let mut visited_fields = Vec::new();
Expand Down Expand Up @@ -288,7 +298,9 @@ fn visit_entities(data: &Data, bevy_ecs_path: &Path, is_relationship: bool) -> T
}
Fields::Unnamed(fields) => {
for (index, field) in fields.unnamed.iter().enumerate() {
if field
if index == 0 && is_relationship_target {
visited_indices.push(Index::from(0));
} else if field
.attrs
.iter()
.any(|a| a.meta.path().is_ident(ENTITIES_ATTR))
Expand Down
5 changes: 5 additions & 0 deletions crates/bevy_ecs/src/bundle.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ use crate::{
observer::Observers,
prelude::World,
query::DebugCheckedUnwrap,
relationship::RelationshipInsertHookMode,
storage::{SparseSetIndex, SparseSets, Storages, Table, TableRow},
world::{unsafe_world_cell::UnsafeWorldCell, EntityWorldMut, ON_ADD, ON_INSERT, ON_REPLACE},
};
Expand Down Expand Up @@ -1100,6 +1101,7 @@ impl<'w> BundleInserter<'w> {
bundle: T,
insert_mode: InsertMode,
caller: MaybeLocation,
relationship_insert_hook_mode: RelationshipInsertHookMode,
) -> (EntityLocation, T::Effect) {
let bundle_info = self.bundle_info.as_ref();
let archetype_after_insert = self.archetype_after_insert.as_ref();
Expand Down Expand Up @@ -1312,6 +1314,7 @@ impl<'w> BundleInserter<'w> {
entity,
archetype_after_insert.iter_inserted(),
caller,
relationship_insert_hook_mode,
);
if new_archetype.has_insert_observer() {
deferred_world.trigger_observers(
Expand All @@ -1330,6 +1333,7 @@ impl<'w> BundleInserter<'w> {
entity,
archetype_after_insert.iter_added(),
caller,
relationship_insert_hook_mode,
);
if new_archetype.has_insert_observer() {
deferred_world.trigger_observers(
Expand Down Expand Up @@ -1474,6 +1478,7 @@ impl<'w> BundleSpawner<'w> {
entity,
bundle_info.iter_contributed_components(),
caller,
RelationshipInsertHookMode::Run,
);
if archetype.has_insert_observer() {
deferred_world.trigger_observers(
Expand Down
35 changes: 21 additions & 14 deletions crates/bevy_ecs/src/component.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,9 @@ use crate::{
archetype::ArchetypeFlags,
bundle::BundleInfo,
change_detection::{MaybeLocation, MAX_CHANGE_AGE},
entity::{ComponentCloneCtx, Entity},
entity::{ComponentCloneCtx, Entity, SourceComponent},
query::DebugCheckedUnwrap,
relationship::RelationshipInsertHookMode,
resource::Resource,
storage::{SparseSetIndex, SparseSets, Table, TableRow},
system::{Commands, Local, SystemParam},
Expand Down Expand Up @@ -544,6 +545,8 @@ pub struct HookContext {
pub component_id: ComponentId,
/// The caller location is `Some` if the `track_caller` feature is enabled.
pub caller: MaybeLocation,
/// Configures how relationship hooks will run
pub relationship_insert_hook_mode: RelationshipInsertHookMode,
}

/// [`World`]-mutating functions that run as part of lifecycle events of a [`Component`].
Expand Down Expand Up @@ -1085,7 +1088,7 @@ impl ComponentDescriptor {
}

/// Function type that can be used to clone an entity.
pub type ComponentCloneFn = fn(&mut Commands, &mut ComponentCloneCtx);
pub type ComponentCloneFn = fn(&mut Commands, &SourceComponent, &mut ComponentCloneCtx);

/// The clone behavior to use when cloning a [`Component`].
#[derive(Clone, Debug, Default, PartialEq, Eq)]
Expand All @@ -1097,11 +1100,6 @@ pub enum ComponentCloneBehavior {
Ignore,
/// Uses a custom [`ComponentCloneFn`].
Custom(ComponentCloneFn),
/// Uses a [`ComponentCloneFn`] that produces an empty version of the given relationship target.
// TODO: this exists so that the current scene spawning code can know when to skip these components.
// When we move to actually cloning entities in scene spawning code, this should be removed in favor of Custom, as the
// distinction will no longer be necessary.
RelationshipTarget(ComponentCloneFn),
}

impl ComponentCloneBehavior {
Expand Down Expand Up @@ -1132,8 +1130,7 @@ impl ComponentCloneBehavior {
match self {
ComponentCloneBehavior::Default => default,
ComponentCloneBehavior::Ignore => component_clone_ignore,
ComponentCloneBehavior::Custom(custom)
| ComponentCloneBehavior::RelationshipTarget(custom) => *custom,
ComponentCloneBehavior::Custom(custom) => *custom,
}
}
}
Expand Down Expand Up @@ -2166,9 +2163,10 @@ pub fn enforce_no_required_components_recursion(
///
pub fn component_clone_via_clone<C: Clone + Component>(
_commands: &mut Commands,
source: &SourceComponent,
ctx: &mut ComponentCloneCtx,
) {
if let Some(component) = ctx.read_source_component::<C>() {
if let Some(component) = source.read::<C>() {
ctx.write_target_component(component.clone());
}
}
Expand All @@ -2189,17 +2187,21 @@ pub fn component_clone_via_clone<C: Clone + Component>(
///
/// See [`EntityClonerBuilder`](crate::entity::EntityClonerBuilder) for details.
#[cfg(feature = "bevy_reflect")]
pub fn component_clone_via_reflect(commands: &mut Commands, ctx: &mut ComponentCloneCtx) {
pub fn component_clone_via_reflect(
commands: &mut Commands,
source: &SourceComponent,
ctx: &mut ComponentCloneCtx,
) {
let Some(app_registry) = ctx.type_registry().cloned() else {
return;
};
let Some(source_component_reflect) = ctx.read_source_component_reflect() else {
let registry = app_registry.read();
let Some(source_component_reflect) = source.read_reflect(&registry) else {
return;
};
let component_info = ctx.component_info();
// checked in read_source_component_reflect
let type_id = component_info.type_id().unwrap();
let registry = app_registry.read();

// Try to clone using ReflectFromReflect
if let Some(reflect_from_reflect) =
Expand Down Expand Up @@ -2284,7 +2286,12 @@ pub fn component_clone_via_reflect(commands: &mut Commands, ctx: &mut ComponentC
/// Noop implementation of component clone handler function.
///
/// See [`EntityClonerBuilder`](crate::entity::EntityClonerBuilder) for details.
pub fn component_clone_ignore(_commands: &mut Commands, _ctx: &mut ComponentCloneCtx) {}
pub fn component_clone_ignore(
_commands: &mut Commands,
_source: &SourceComponent,
_ctx: &mut ComponentCloneCtx,
) {
}

/// Wrapper for components clone specialization using autoderef.
#[doc(hidden)]
Expand Down
Loading