bevy_descendant_collector_derive/
lib.rs1use proc_macro2::TokenStream;
2use quote::quote;
3use syn::{Attribute, DeriveInput, Meta, parse_macro_input};
4
5fn read_attribute(attrs: Vec<Attribute>, attribute_name: &str) -> Option<TokenStream> {
6 let name_path_attr = attrs
7 .iter()
8 .find(|attr| attr.path().is_ident(attribute_name));
9
10 if let Some(attr) = name_path_attr {
11 if let Meta::List(list) = &attr.meta {
12 Some(list.tokens.clone())
13 } else {
14 panic!("{attribute_name} has the wrong type");
15 }
16 } else {
17 None
18 }
19}
20
21#[proc_macro_derive(EntityCollectorTarget, attributes(name_path))]
23pub fn entity_collector_target(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
24 let derive_input = parse_macro_input!(input as DeriveInput);
25 let struct_name = derive_input.ident;
26 let generics = derive_input.generics;
27 let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
28
29 let fields = match derive_input.data {
30 syn::Data::Struct(ref data) => match &data.fields {
31 syn::Fields::Named(fields) => &fields.named,
32 _ => panic!("Only named fields are supported"),
33 },
34 _ => panic!("Only structs are supported"),
35 };
36
37 let armature_entry_name = read_attribute(derive_input.attrs, "name_path").expect("no root name_path is defined, define the name of the root element (The first element in blender in the exported collection)");
38
39 let assignments = fields
40 .iter()
41 .map(|field| {
42 let armature_field_entry_name = read_attribute(field.attrs.clone(), "name_path").expect("Field's that are not name_path's are not permitted");
43 let ident = field.ident.as_ref().expect("The variable must have a name!");
44 (ident, armature_field_entry_name)
45 })
46 .map(|(ident, path)| {
47 let error_msg = if path.is_empty() {
48 format!("Root element not found for {struct_name}. Actual name paths are:\n")
49 } else {
50 format!("Named element not found for {struct_name} at {path}. Actual name paths are:\n")
51 };
52 quote! {
53 #ident: bevy_descendant_collector::find_named_entity(entity_source_root, &named_query, &[#path]).unwrap_or_else(|| {
54 let named_entity_paths = bevy_descendant_collector::collect_named_entity_paths(entity_source_root, &named_query);
55 panic!("{} {:#?}", #error_msg, named_entity_paths);
56 }),
57 }
58 })
59 .collect::<Vec<_>>();
60
61 proc_macro::TokenStream::from(quote! {
62 impl #impl_generics bevy_descendant_collector::DescendantLoader for #struct_name #ty_generics #where_clause {
63
64 fn get_root_entity_name() -> &'static str {
65 #armature_entry_name
66 }
67
68 fn collect_descendants(
69 commands: &mut Commands,
70 entity_source_root: Entity,
71 entity_map_target: Entity,
72 named_query: &Query<(Entity, Option<&Name>, Option<&Children>)>) {
73 let armature = Self {
74 #(#assignments)*
75 };
76 commands.entity(entity_map_target).insert(armature);
77 }
78 }
79 })
80}