Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
bevyengine
GitHub Repository: bevyengine/bevy
Path: blob/main/crates/bevy_asset/macros/src/lib.rs
6600 views
1
#![cfg_attr(docsrs, feature(doc_auto_cfg))]
2
3
//! Macros for deriving asset traits.
4
5
use bevy_macro_utils::BevyManifest;
6
use proc_macro::{Span, TokenStream};
7
use quote::{format_ident, quote};
8
use syn::{parse_macro_input, Data, DeriveInput, Path};
9
10
pub(crate) fn bevy_asset_path() -> Path {
11
BevyManifest::shared().get_path("bevy_asset")
12
}
13
14
const DEPENDENCY_ATTRIBUTE: &str = "dependency";
15
16
/// Implement the `Asset` trait.
17
#[proc_macro_derive(Asset, attributes(dependency))]
18
pub fn derive_asset(input: TokenStream) -> TokenStream {
19
let ast = parse_macro_input!(input as DeriveInput);
20
let bevy_asset_path: Path = bevy_asset_path();
21
22
let struct_name = &ast.ident;
23
let (impl_generics, type_generics, where_clause) = &ast.generics.split_for_impl();
24
let dependency_visitor = match derive_dependency_visitor_internal(&ast, &bevy_asset_path) {
25
Ok(dependency_visitor) => dependency_visitor,
26
Err(err) => return err.into_compile_error().into(),
27
};
28
29
TokenStream::from(quote! {
30
impl #impl_generics #bevy_asset_path::Asset for #struct_name #type_generics #where_clause { }
31
#dependency_visitor
32
})
33
}
34
35
/// Implement the `VisitAssetDependencies` trait.
36
#[proc_macro_derive(VisitAssetDependencies, attributes(dependency))]
37
pub fn derive_asset_dependency_visitor(input: TokenStream) -> TokenStream {
38
let ast = parse_macro_input!(input as DeriveInput);
39
let bevy_asset_path: Path = bevy_asset_path();
40
match derive_dependency_visitor_internal(&ast, &bevy_asset_path) {
41
Ok(dependency_visitor) => TokenStream::from(dependency_visitor),
42
Err(err) => err.into_compile_error().into(),
43
}
44
}
45
46
fn derive_dependency_visitor_internal(
47
ast: &DeriveInput,
48
bevy_asset_path: &Path,
49
) -> Result<proc_macro2::TokenStream, syn::Error> {
50
let struct_name = &ast.ident;
51
let (impl_generics, type_generics, where_clause) = &ast.generics.split_for_impl();
52
53
let visit_dep = |to_read| quote!(#bevy_asset_path::VisitAssetDependencies::visit_dependencies(#to_read, visit););
54
let is_dep_attribute = |a: &syn::Attribute| a.path().is_ident(DEPENDENCY_ATTRIBUTE);
55
let field_has_dep = |f: &syn::Field| f.attrs.iter().any(is_dep_attribute);
56
57
let body = match &ast.data {
58
Data::Struct(data_struct) => {
59
let fields = data_struct.fields.iter();
60
let field_visitors = fields.enumerate().filter(|(_, f)| field_has_dep(f));
61
let field_visitors = field_visitors.map(|(i, field)| match &field.ident {
62
Some(ident) => visit_dep(quote!(&self.#ident)),
63
None => {
64
let index = syn::Index::from(i);
65
visit_dep(quote!(&self.#index))
66
}
67
});
68
Some(quote!( #(#field_visitors)* ))
69
}
70
Data::Enum(data_enum) => {
71
let variant_has_dep = |v: &syn::Variant| v.fields.iter().any(field_has_dep);
72
let any_case_required = data_enum.variants.iter().any(variant_has_dep);
73
let cases = data_enum.variants.iter().filter(|v| variant_has_dep(v));
74
let cases = cases.map(|variant| {
75
let ident = &variant.ident;
76
let fields = &variant.fields;
77
78
let field_visitors = fields.iter().enumerate().filter(|(_, f)| field_has_dep(f));
79
80
let field_visitors = field_visitors.map(|(i, field)| match &field.ident {
81
Some(ident) => visit_dep(quote!(#ident)),
82
None => {
83
let ident = format_ident!("member{i}");
84
visit_dep(quote!(#ident))
85
}
86
});
87
let fields = match fields {
88
syn::Fields::Named(fields) => {
89
let named = fields.named.iter().map(|f| f.ident.as_ref());
90
quote!({ #(#named,)* .. })
91
}
92
syn::Fields::Unnamed(fields) => {
93
let named = (0..fields.unnamed.len()).map(|i| format_ident!("member{i}"));
94
quote!( ( #(#named,)* ) )
95
}
96
syn::Fields::Unit => unreachable!("Can't pass filter is_dep_attribute"),
97
};
98
quote!(Self::#ident #fields => {
99
#(#field_visitors)*
100
})
101
});
102
103
any_case_required.then(|| quote!(match self { #(#cases)*, _ => {} }))
104
}
105
Data::Union(_) => {
106
return Err(syn::Error::new(
107
Span::call_site().into(),
108
"Asset derive currently doesn't work on unions",
109
));
110
}
111
};
112
113
// prevent unused variable warning in case there are no dependencies
114
let visit = if body.is_none() {
115
quote! { _visit }
116
} else {
117
quote! { visit }
118
};
119
120
Ok(quote! {
121
impl #impl_generics #bevy_asset_path::VisitAssetDependencies for #struct_name #type_generics #where_clause {
122
fn visit_dependencies(&self, #visit: &mut impl FnMut(#bevy_asset_path::UntypedAssetId)) {
123
#body
124
}
125
}
126
})
127
}
128
129