use syn::{ punctuated::Punctuated, token::Comma, Data, DataStruct, DeriveInput, Field, Fields, FieldsNamed, FieldsUnnamed, Ident, Lifetime, Type, WhereClause, WherePredicate, }; pub fn execute(ast: &DeriveInput) -> proc_macro2::TokenStream { let name = &ast.ident; let mut generics = ast.generics.clone(); let (fetch_return, tys) = gen_from_body(&ast.data, name); let tys = &tys; let def_fetch_lt = ast .generics .lifetimes() .next() .expect("There has to be at least one lifetime"); let impl_fetch_lt = &def_fetch_lt.lifetime; { let where_clause = generics.make_where_clause(); constrain_system_data_types(where_clause, impl_fetch_lt, tys); } let (impl_generics, ty_generics, where_clause) = generics.split_for_impl(); quote! { impl #impl_generics async_ecs::system::SystemData< #impl_fetch_lt > for #name #ty_generics #where_clause { fn setup(resources: &mut async_ecs::resources::Resources) { #( <#tys as async_ecs::system::SystemData> :: setup(world); )* } fn fetch(world: & #impl_fetch_lt async_ecs::resources::Resources) -> Self { #fetch_return } fn reads() -> Vec { let mut r = Vec::new(); #( { let mut reads = <#tys as async_ecs::system::SystemData> :: reads(); r.append(&mut reads); } )* r } fn writes() -> Vec { let mut r = Vec::new(); #( { let mut writes = <#tys as async_ecs::system::SystemData> :: writes(); r.append(&mut writes); } )* r } } } } fn collect_field_types(fields: &Punctuated) -> Vec { fields.iter().map(|x| x.ty.clone()).collect() } fn gen_identifiers(fields: &Punctuated) -> Vec { fields.iter().map(|x| x.ident.clone().unwrap()).collect() } fn constrain_system_data_types(clause: &mut WhereClause, fetch_lt: &Lifetime, tys: &[Type]) { for ty in tys.iter() { let where_predicate: WherePredicate = parse_quote!(#ty : async_ecs::system::SystemData< #fetch_lt >); clause.predicates.push(where_predicate); } } fn gen_from_body(ast: &Data, name: &Ident) -> (proc_macro2::TokenStream, Vec) { enum DataType { Struct, Tuple, } let (body, fields) = match *ast { Data::Struct(DataStruct { fields: Fields::Named(FieldsNamed { named: ref x, .. }), .. }) => (DataType::Struct, x), Data::Struct(DataStruct { fields: Fields::Unnamed(FieldsUnnamed { unnamed: ref x, .. }), .. }) => (DataType::Tuple, x), _ => panic!("Enums are not supported"), }; let tys = collect_field_types(fields); let fetch_return = match body { DataType::Struct => { let identifiers = gen_identifiers(fields); quote! { #name { #( #identifiers: async_ecs::system::SystemData::fetch(world) ),* } } } DataType::Tuple => { let count = tys.len(); let fetch = vec![quote! { async_ecs::system::SystemData::fetch(world) }; count]; quote! { #name ( #( #fetch ),* ) } } }; (fetch_return, tys) }