bevy/crates/bevy_ecs/macros/src/component.rs
eugineerd 2e267bba5a
Entity cloning (#16132)
## Objective

Fixes #1515 

This PR implements a flexible entity cloning system. The primary use
case for it is to clone dynamically-generated entities.

Example:
```rs
#[derive(Component, Clone)]
pub struct Projectile;

#[derive(Component, Clone)]
pub struct Damage {
    value: f32,
}

fn player_input(
    mut commands: Commands,
    projectiles: Query<Entity, With<Projectile>>,
    input: Res<ButtonInput<KeyCode>>,
) {
    // Fire a projectile
    if input.just_pressed(KeyCode::KeyF) {
        commands.spawn((Projectile, Damage { value: 10.0 }));
    }

    // Triplicate all active projectiles
    if input.just_pressed(KeyCode::KeyT) {
        for projectile in projectiles.iter() {
            // To triplicate a projectile we need to create 2 more clones
            for _ in 0..2{
                commands.clone_entity(projectile)
            }
        }
    }
}
```

## Solution

### Commands
Add a `clone_entity` command to create a clone of an entity with all
components that can be cloned. Components that can't be cloned will be
ignored.
```rs
commands.clone_entity(entity)
```
If there is a need to configure the cloning process (like set to clone
recursively), there is a second command:
```rs
commands.clone_entity_with(entity, |builder| {
    builder.recursive(true)
});
```
Both of these commands return `EntityCommands` of the cloned entity, so
the copy can be modified afterwards.

### Builder
All these commands use `EntityCloneBuilder` internally. If there is a
need to clone an entity using `World` instead, it is also possible:
```rs
let entity = world.spawn(Component).id();
let entity_clone = world.spawn_empty().id();
EntityCloneBuilder::new(&mut world).clone_entity(entity, entity_clone);
```

Builder has methods to `allow` or `deny` certain components during
cloning if required and can be extended by implementing traits on it.
This PR includes two `EntityCloneBuilder` extensions:
`CloneEntityWithObserversExt` to configure adding cloned entity to
observers of the original entity, and `CloneEntityRecursiveExt` to
configure cloning an entity recursively.

### Clone implementations
By default, all components that implement either `Clone` or `Reflect`
will be cloned (with `Clone`-based implementation preferred in case
component implements both).

This can be overriden on a per-component basis:
```rs
impl Component for SomeComponent {
    const STORAGE_TYPE: StorageType = StorageType::Table;

    fn get_component_clone_handler() -> ComponentCloneHandler {
        // Don't clone this component
        ComponentCloneHandler::Ignore
    }
}
```

### `ComponentCloneHandlers`
Clone implementation specified in `get_component_clone_handler` will get
registered in `ComponentCloneHandlers` (stored in
`bevy_ecs::component::Components`) at component registration time.

The clone handler implementation provided by a component can be
overriden after registration like so:
```rs
let component_id = world.components().component_id::<Component>().unwrap()
world.get_component_clone_handlers_mut()
     .set_component_handler(component_id, ComponentCloneHandler::Custom(component_clone_custom))
```
The default clone handler for all components that do not explicitly
define one (or don't derive `Component`) is
`component_clone_via_reflect` if `bevy_reflect` feature is enabled, and
`component_clone_ignore` (noop) otherwise.
Default handler can be overriden using
`ComponentCloneHandlers::set_default_handler`

### Handlers
Component clone handlers can be used to modify component cloning
behavior. The general signature for a handler that can be used in
`ComponentCloneHandler::Custom` is as follows:
```rs
pub fn component_clone_custom(
    world: &mut DeferredWorld,
    entity_cloner: &EntityCloner,
) {
    // implementation
}
```
The `EntityCloner` implementation (used internally by
`EntityCloneBuilder`) assumes that after calling this custom handler,
the `target` entity has the desired version of the component from the
`source` entity.

### Builder handler overrides
Besides component-defined and world-overriden handlers,
`EntityCloneBuilder` also has a way to override handlers locally. It is
mainly used to allow configuration methods like `recursive` and
`add_observers`.
```rs
// From observer clone handler implementation
impl CloneEntityWithObserversExt for EntityCloneBuilder<'_> {
    fn add_observers(&mut self, add_observers: bool) -> &mut Self {
        if add_observers {
            self.override_component_clone_handler::<ObservedBy>(ComponentCloneHandler::Custom(
                component_clone_observed_by,
            ))
        } else {
            self.remove_component_clone_handler_override::<ObservedBy>()
        }
    }
}
```

## Testing
Includes some basic functionality tests and doctests.

Performance-wise this feature is the same as calling `clone` followed by
`insert` for every entity component. There is also some inherent
overhead due to every component clone handler having to access component
data through `World`, but this can be reduced without breaking current
public API in a later PR.
2024-12-03 17:38:10 +00:00

311 lines
11 KiB
Rust

use proc_macro::TokenStream;
use proc_macro2::{Span, TokenStream as TokenStream2};
use quote::{quote, ToTokens};
use std::collections::HashSet;
use syn::{
parenthesized,
parse::Parse,
parse_macro_input, parse_quote,
punctuated::Punctuated,
spanned::Spanned,
token::{Comma, Paren},
DeriveInput, ExprClosure, ExprPath, Ident, LitStr, Path, Result,
};
pub fn derive_event(input: TokenStream) -> TokenStream {
let mut ast = parse_macro_input!(input as DeriveInput);
let bevy_ecs_path: Path = crate::bevy_ecs_path();
ast.generics
.make_where_clause()
.predicates
.push(parse_quote! { Self: Send + Sync + 'static });
let struct_name = &ast.ident;
let (impl_generics, type_generics, where_clause) = &ast.generics.split_for_impl();
TokenStream::from(quote! {
impl #impl_generics #bevy_ecs_path::event::Event for #struct_name #type_generics #where_clause {
type Traversal = ();
const AUTO_PROPAGATE: bool = false;
}
impl #impl_generics #bevy_ecs_path::component::Component for #struct_name #type_generics #where_clause {
const STORAGE_TYPE: #bevy_ecs_path::component::StorageType = #bevy_ecs_path::component::StorageType::SparseSet;
}
})
}
pub fn derive_resource(input: TokenStream) -> TokenStream {
let mut ast = parse_macro_input!(input as DeriveInput);
let bevy_ecs_path: Path = crate::bevy_ecs_path();
ast.generics
.make_where_clause()
.predicates
.push(parse_quote! { Self: Send + Sync + 'static });
let struct_name = &ast.ident;
let (impl_generics, type_generics, where_clause) = &ast.generics.split_for_impl();
TokenStream::from(quote! {
impl #impl_generics #bevy_ecs_path::system::Resource for #struct_name #type_generics #where_clause {
}
})
}
pub fn derive_component(input: TokenStream) -> TokenStream {
let mut ast = parse_macro_input!(input as DeriveInput);
let bevy_ecs_path: Path = crate::bevy_ecs_path();
let attrs = match parse_component_attr(&ast) {
Ok(attrs) => attrs,
Err(e) => return e.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);
ast.generics
.make_where_clause()
.predicates
.push(parse_quote! { Self: Send + Sync + 'static });
let requires = &attrs.requires;
let mut register_required = Vec::with_capacity(attrs.requires.iter().len());
let mut register_recursive_requires = Vec::with_capacity(attrs.requires.iter().len());
if let Some(requires) = requires {
for require in requires {
let ident = &require.path;
register_recursive_requires.push(quote! {
<#ident as #bevy_ecs_path::component::Component>::register_required_components(
requiree,
components,
storages,
required_components,
inheritance_depth + 1
);
});
match &require.func {
Some(RequireFunc::Path(func)) => {
register_required.push(quote! {
components.register_required_components_manual::<Self, #ident>(
storages,
required_components,
|| { let x: #ident = #func().into(); x },
inheritance_depth
);
});
}
Some(RequireFunc::Closure(func)) => {
register_required.push(quote! {
components.register_required_components_manual::<Self, #ident>(
storages,
required_components,
|| { let x: #ident = (#func)().into(); x },
inheritance_depth
);
});
}
None => {
register_required.push(quote! {
components.register_required_components_manual::<Self, #ident>(
storages,
required_components,
<#ident as Default>::default,
inheritance_depth
);
});
}
}
}
}
let struct_name = &ast.ident;
let (impl_generics, type_generics, where_clause) = &ast.generics.split_for_impl();
let required_component_docs = attrs.requires.map(|r| {
let paths = r
.iter()
.map(|r| format!("[`{}`]", r.path.to_token_stream()))
.collect::<Vec<_>>()
.join(", ");
let doc = format!("Required Components: {paths}. \n\n A component's Required Components are inserted whenever it is inserted. Note that this will also insert the required components _of_ the required components, recursively, in depth-first order.");
quote! {
#[doc = #doc]
}
});
// 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! {
#required_component_docs
impl #impl_generics #bevy_ecs_path::component::Component for #struct_name #type_generics #where_clause {
const STORAGE_TYPE: #bevy_ecs_path::component::StorageType = #storage;
fn register_required_components(
requiree: #bevy_ecs_path::component::ComponentId,
components: &mut #bevy_ecs_path::component::Components,
storages: &mut #bevy_ecs_path::storage::Storages,
required_components: &mut #bevy_ecs_path::component::RequiredComponents,
inheritance_depth: u16,
) {
#(#register_required)*
#(#register_recursive_requires)*
}
#[allow(unused_variables)]
fn register_component_hooks(hooks: &mut #bevy_ecs_path::component::ComponentHooks) {
#on_add
#on_insert
#on_replace
#on_remove
}
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()
}
}
})
}
pub const COMPONENT: &str = "component";
pub const STORAGE: &str = "storage";
pub const REQUIRE: &str = "require";
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";
struct Attrs {
storage: StorageTy,
requires: Option<Punctuated<Require, Comma>>,
on_add: Option<ExprPath>,
on_insert: Option<ExprPath>,
on_replace: Option<ExprPath>,
on_remove: Option<ExprPath>,
}
#[derive(Clone, Copy)]
enum StorageTy {
Table,
SparseSet,
}
struct Require {
path: Path,
func: Option<RequireFunc>,
}
enum RequireFunc {
Path(Path),
Closure(ExprClosure),
}
// values for `storage` attribute
const TABLE: &str = "Table";
const SPARSE_SET: &str = "SparseSet";
fn parse_component_attr(ast: &DeriveInput) -> Result<Attrs> {
let mut attrs = Attrs {
storage: StorageTy::Table,
on_add: None,
on_insert: None,
on_replace: None,
on_remove: None,
requires: None,
};
let mut require_paths = HashSet::new();
for attr in ast.attrs.iter() {
if attr.path().is_ident(COMPONENT) {
attr.parse_nested_meta(|nested| {
if nested.path.is_ident(STORAGE) {
attrs.storage = match nested.value()?.parse::<LitStr>()?.value() {
s if s == TABLE => StorageTy::Table,
s if s == SPARSE_SET => StorageTy::SparseSet,
s => {
return Err(nested.error(format!(
"Invalid storage type `{s}`, expected '{TABLE}' or '{SPARSE_SET}'.",
)));
}
};
Ok(())
} else if nested.path.is_ident(ON_ADD) {
attrs.on_add = Some(nested.value()?.parse::<ExprPath>()?);
Ok(())
} else if nested.path.is_ident(ON_INSERT) {
attrs.on_insert = Some(nested.value()?.parse::<ExprPath>()?);
Ok(())
} else if nested.path.is_ident(ON_REPLACE) {
attrs.on_replace = Some(nested.value()?.parse::<ExprPath>()?);
Ok(())
} else if nested.path.is_ident(ON_REMOVE) {
attrs.on_remove = Some(nested.value()?.parse::<ExprPath>()?);
Ok(())
} else {
Err(nested.error("Unsupported attribute"))
}
})?;
} else if attr.path().is_ident(REQUIRE) {
let punctuated =
attr.parse_args_with(Punctuated::<Require, Comma>::parse_terminated)?;
for require in punctuated.iter() {
if !require_paths.insert(require.path.to_token_stream().to_string()) {
return Err(syn::Error::new(
require.path.span(),
"Duplicate required components are not allowed.",
));
}
}
if let Some(current) = &mut attrs.requires {
current.extend(punctuated);
} else {
attrs.requires = Some(punctuated);
}
}
}
Ok(attrs)
}
impl Parse for Require {
fn parse(input: syn::parse::ParseStream) -> Result<Self> {
let path = input.parse::<Path>()?;
let func = if input.peek(Paren) {
let content;
parenthesized!(content in input);
if let Ok(func) = content.parse::<ExprClosure>() {
Some(RequireFunc::Closure(func))
} else {
let func = content.parse::<Path>()?;
Some(RequireFunc::Path(func))
}
} else {
None
};
Ok(Require { path, func })
}
}
fn storage_path(bevy_ecs_path: &Path, ty: StorageTy) -> TokenStream2 {
let storage_type = match ty {
StorageTy::Table => Ident::new("Table", Span::call_site()),
StorageTy::SparseSet => Ident::new("SparseSet", Span::call_site()),
};
quote! { #bevy_ecs_path::component::StorageType::#storage_type }
}
fn hook_register_function_call(
hook: TokenStream2,
function: Option<ExprPath>,
) -> Option<TokenStream2> {
function.map(|meta| quote! { hooks. #hook (#meta); })
}