bevy_ecs_macros/
component.rs

1use proc_macro::TokenStream;
2use proc_macro2::{Span, TokenStream as TokenStream2};
3use quote::quote;
4use syn::{parse_macro_input, parse_quote, DeriveInput, Ident, LitStr, Path, Result};
5
6pub fn derive_event(input: TokenStream) -> TokenStream {
7    let mut ast = parse_macro_input!(input as DeriveInput);
8    let bevy_ecs_path: Path = crate::bevy_ecs_path();
9
10    ast.generics
11        .make_where_clause()
12        .predicates
13        .push(parse_quote! { Self: Send + Sync + 'static });
14
15    let struct_name = &ast.ident;
16    let (impl_generics, type_generics, where_clause) = &ast.generics.split_for_impl();
17
18    TokenStream::from(quote! {
19        impl #impl_generics #bevy_ecs_path::event::Event for #struct_name #type_generics #where_clause {
20        }
21
22        impl #impl_generics #bevy_ecs_path::component::Component for #struct_name #type_generics #where_clause {
23            const STORAGE_TYPE: #bevy_ecs_path::component::StorageType = #bevy_ecs_path::component::StorageType::SparseSet;
24        }
25    })
26}
27
28pub fn derive_resource(input: TokenStream) -> TokenStream {
29    let mut ast = parse_macro_input!(input as DeriveInput);
30    let bevy_ecs_path: Path = crate::bevy_ecs_path();
31
32    ast.generics
33        .make_where_clause()
34        .predicates
35        .push(parse_quote! { Self: Send + Sync + 'static });
36
37    let struct_name = &ast.ident;
38    let (impl_generics, type_generics, where_clause) = &ast.generics.split_for_impl();
39
40    TokenStream::from(quote! {
41        impl #impl_generics #bevy_ecs_path::system::Resource for #struct_name #type_generics #where_clause {
42        }
43    })
44}
45
46pub fn derive_component(input: TokenStream) -> TokenStream {
47    let mut ast = parse_macro_input!(input as DeriveInput);
48    let bevy_ecs_path: Path = crate::bevy_ecs_path();
49
50    let attrs = match parse_component_attr(&ast) {
51        Ok(attrs) => attrs,
52        Err(e) => return e.into_compile_error().into(),
53    };
54
55    let storage = storage_path(&bevy_ecs_path, attrs.storage);
56
57    ast.generics
58        .make_where_clause()
59        .predicates
60        .push(parse_quote! { Self: Send + Sync + 'static });
61
62    let struct_name = &ast.ident;
63    let (impl_generics, type_generics, where_clause) = &ast.generics.split_for_impl();
64
65    TokenStream::from(quote! {
66        impl #impl_generics #bevy_ecs_path::component::Component for #struct_name #type_generics #where_clause {
67            const STORAGE_TYPE: #bevy_ecs_path::component::StorageType = #storage;
68        }
69    })
70}
71
72pub const COMPONENT: &str = "component";
73pub const STORAGE: &str = "storage";
74
75struct Attrs {
76    storage: StorageTy,
77}
78
79#[derive(Clone, Copy)]
80enum StorageTy {
81    Table,
82    SparseSet,
83}
84
85// values for `storage` attribute
86const TABLE: &str = "Table";
87const SPARSE_SET: &str = "SparseSet";
88
89fn parse_component_attr(ast: &DeriveInput) -> Result<Attrs> {
90    let mut attrs = Attrs {
91        storage: StorageTy::Table,
92    };
93
94    for meta in ast.attrs.iter().filter(|a| a.path().is_ident(COMPONENT)) {
95        meta.parse_nested_meta(|nested| {
96            if nested.path.is_ident(STORAGE) {
97                attrs.storage = match nested.value()?.parse::<LitStr>()?.value() {
98                    s if s == TABLE => StorageTy::Table,
99                    s if s == SPARSE_SET => StorageTy::SparseSet,
100                    s => {
101                        return Err(nested.error(format!(
102                            "Invalid storage type `{s}`, expected '{TABLE}' or '{SPARSE_SET}'.",
103                        )));
104                    }
105                };
106                Ok(())
107            } else {
108                Err(nested.error("Unsupported attribute"))
109            }
110        })?;
111    }
112
113    Ok(attrs)
114}
115
116fn storage_path(bevy_ecs_path: &Path, ty: StorageTy) -> TokenStream2 {
117    let storage_type = match ty {
118        StorageTy::Table => Ident::new("Table", Span::call_site()),
119        StorageTy::SparseSet => Ident::new("SparseSet", Span::call_site()),
120    };
121
122    quote! { #bevy_ecs_path::component::StorageType::#storage_type }
123}