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