Derive clone_behavior for Components (#18811)

Allow Derive(Component) to specify a clone_behavior

```rust
#[derive(Component)]
#[component(clone_behavior = Ignore)]
MyComponent;
```
This commit is contained in:
Tim Overbeek 2025-05-06 02:32:59 +02:00 committed by GitHub
parent b19f644c2f
commit 60cdefd128
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 57 additions and 20 deletions

View File

@ -251,6 +251,8 @@ pub fn derive_component(input: TokenStream) -> TokenStream {
let clone_behavior = if relationship_target.is_some() {
quote!(#bevy_ecs_path::component::ComponentCloneBehavior::Custom(#bevy_ecs_path::relationship::clone_relationship_target::<Self>))
} else if let Some(behavior) = attrs.clone_behavior {
quote!(#bevy_ecs_path::component::ComponentCloneBehavior::#behavior)
} else {
quote!(
use #bevy_ecs_path::component::{DefaultCloneBehaviorBase, DefaultCloneBehaviorViaClone};
@ -396,6 +398,7 @@ pub const ON_REMOVE: &str = "on_remove";
pub const ON_DESPAWN: &str = "on_despawn";
pub const IMMUTABLE: &str = "immutable";
pub const CLONE_BEHAVIOR: &str = "clone_behavior";
/// All allowed attribute value expression kinds for component hooks
#[derive(Debug)]
@ -458,6 +461,7 @@ struct Attrs {
relationship: Option<Relationship>,
relationship_target: Option<RelationshipTarget>,
immutable: bool,
clone_behavior: Option<Expr>,
}
#[derive(Clone, Copy)]
@ -496,6 +500,7 @@ fn parse_component_attr(ast: &DeriveInput) -> Result<Attrs> {
relationship: None,
relationship_target: None,
immutable: false,
clone_behavior: None,
};
let mut require_paths = HashSet::new();
@ -531,6 +536,9 @@ fn parse_component_attr(ast: &DeriveInput) -> Result<Attrs> {
} else if nested.path.is_ident(IMMUTABLE) {
attrs.immutable = true;
Ok(())
} else if nested.path.is_ident(CLONE_BEHAVIOR) {
attrs.clone_behavior = Some(nested.value()?.parse()?);
Ok(())
} else {
Err(nested.error("Unsupported attribute"))
}
@ -560,6 +568,13 @@ fn parse_component_attr(ast: &DeriveInput) -> Result<Attrs> {
}
}
if attrs.relationship_target.is_some() && attrs.clone_behavior.is_some() {
return Err(syn::Error::new(
attrs.clone_behavior.span(),
"A Relationship Target already has its own clone behavior, please remove `clone_behavior = ...`",
));
}
Ok(attrs)
}

View File

@ -418,6 +418,21 @@ use thiserror::Error;
/// println!("{message}");
/// }
/// }
///
/// ```
/// # Setting the clone behavior
///
/// You can specify how the [`Component`] is cloned when deriving it.
///
/// Your options are the functions and variants of [`ComponentCloneBehavior`]
/// See [Handlers section of `EntityClonerBuilder`](crate::entity::EntityClonerBuilder#handlers) to understand how this affects handler priority.
/// ```
/// # use bevy_ecs::prelude::*;
///
/// #[derive(Component)]
/// #[component(clone_behavior = Ignore)]
/// struct MyComponent;
///
/// ```
///
/// # Implementing the trait for foreign types

View File

@ -324,16 +324,10 @@ impl<'a, 'b> ComponentCloneCtx<'a, 'b> {
/// ```
/// # use bevy_ecs::prelude::*;
/// # use bevy_ecs::component::{StorageType, ComponentCloneBehavior, Mutable};
/// #[derive(Clone)]
/// #[derive(Clone, Component)]
/// #[component(clone_behavior = clone::<Self>())]
/// struct SomeComponent;
///
/// impl Component for SomeComponent {
/// const STORAGE_TYPE: StorageType = StorageType::Table;
/// type Mutability = Mutable;
/// fn clone_behavior() -> ComponentCloneBehavior {
/// ComponentCloneBehavior::clone::<Self>()
/// }
/// }
/// ```
///
/// # Clone Behaviors

View File

@ -2751,4 +2751,27 @@ mod tests {
)]
#[derive(Component)]
struct MyEntitiesTuple(#[entities] Vec<Entity>, #[entities] Entity, usize);
#[test]
fn clone_entities() {
use crate::entity::{ComponentCloneCtx, SourceComponent};
#[derive(Component)]
#[component(clone_behavior = Ignore)]
struct IgnoreClone;
#[derive(Component)]
#[component(clone_behavior = Default)]
struct DefaultClone;
#[derive(Component)]
#[component(clone_behavior = Custom(custom_clone))]
struct CustomClone;
#[derive(Component, Clone)]
#[component(clone_behavior = clone::<Self>())]
struct CloneFunction;
fn custom_clone(_source: &SourceComponent, _ctx: &mut ComponentCloneCtx) {}
}
}

View File

@ -1,6 +1,5 @@
use bevy_app::Plugin;
use bevy_derive::{Deref, DerefMut};
use bevy_ecs::component::{ComponentCloneBehavior, Mutable, StorageType};
use bevy_ecs::entity::EntityHash;
use bevy_ecs::{
component::Component,
@ -127,7 +126,8 @@ pub struct SyncToRenderWorld;
/// Component added on the main world entities that are synced to the Render World in order to keep track of the corresponding render world entity.
///
/// Can also be used as a newtype wrapper for render world entities.
#[derive(Deref, Copy, Clone, Debug, Eq, Hash, PartialEq)]
#[derive(Deref, Copy, Clone, Debug, Eq, Hash, PartialEq, Component)]
#[component(clone_behavior = Ignore)]
pub struct RenderEntity(Entity);
impl RenderEntity {
#[inline]
@ -136,16 +136,6 @@ impl RenderEntity {
}
}
impl Component for RenderEntity {
const STORAGE_TYPE: StorageType = StorageType::Table;
type Mutability = Mutable;
fn clone_behavior() -> ComponentCloneBehavior {
ComponentCloneBehavior::Ignore
}
}
impl From<Entity> for RenderEntity {
fn from(entity: Entity) -> Self {
RenderEntity(entity)