bevy/crates/bevy_ecs/macros/src/component.rs
Volodymyr Enhelhardt db1915a1f0
Use the fully qualified name for Component in the require attribute (#16378)
# Objective

- Describe the objective or issue this PR addresses.
Use the fully qualified name for `Component` in the `require` attribute

- If you're fixing a specific issue, say "Fixes #X".
Fixes #16377

## Solution

- Describe the solution used to achieve the objective above.
Use the fully qualified name for `Component` in the `require` attribute,
i.e.,`<#ident as #bevy_ecs_path::component::Component>`

## Testing

- Did you test these changes? If so, how?
`cargo run -p ci -- lints`
`cargo run -p ci -- compile`
`cargo run -p ci -- test`
- Are there any parts that need more testing?
no
- How can other people (reviewers) test your changes? Is there anything
specific they need to know?
try to compile
```rust
#[derive(::bevy::ecs::component::Component, Default)]
pub struct A;

#[derive(::bevy::ecs::component::Component)]
#[require(A)]
pub struct B;
```
- If relevant, what platforms did you test these changes on, and are
there any important ones you can't test?
Mac only
---


</details>

## Migration Guide

> This section is optional. If there are no breaking changes, you can
delete this section.

- If this PR is a breaking change (relative to the last release of
Bevy), describe how a user might need to migrate their code to support
these changes
- Simply adding new functionality is not a breaking change.
- Fixing behavior that was definitely a bug, rather than a questionable
design choice is not a breaking change.

Co-authored-by: Volodymyr Enhelhardt <volodymyr.enhelhardt@ambr.net>
2024-11-13 20:37:50 +00:00

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