1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
extern crate proc_macro;
use quote::quote;

const MAX_TYPES: usize = 10;

#[allow(clippy::or_fun_call)]
#[proc_macro_attribute]
pub fn system(
    attr: proc_macro::TokenStream,
    item: proc_macro::TokenStream,
) -> proc_macro::TokenStream {
    let name = syn::parse_macro_input!(attr as syn::Ident);
    let mut run = syn::parse_macro_input!(item as syn::ItemFn);

    assert!(run.sig.ident == "run", "Systems have only one method: run.");
    assert!(
        run.sig.generics.params.is_empty() && run.sig.generics.where_clause.is_none(),
        "run should not take generic arguments nor where clause."
    );

    // checks if run returns a type other than ()
    match run.sig.output {
        syn::ReturnType::Type(_, type_info) => {
            if let syn::Type::Tuple(tuple) = &*type_info {
                if !tuple.elems.is_empty() {
                    panic!("run should not return anything.")
                }
            } else {
                panic!("run should not return anything.")
            }
        }
        syn::ReturnType::Default => {}
    }

    let body = &*run.block;

    let mut data = Vec::with_capacity(run.sig.inputs.len());
    let mut binding = Vec::with_capacity(run.sig.inputs.len());

    run.sig.inputs.iter_mut().for_each(|arg| {
        if let syn::FnArg::Typed(syn::PatType { pat, ty, .. }) = arg {
            match **ty {
                syn::Type::Reference(ref mut reference) => {
                    // references are added a 'a lifetime if they don't have one
                    if let syn::Type::Path(path) = &*reference.elem {
                        // transform &Entities into Entites and &mut Entities into EntitiesMut
                        if path.path.segments.last().unwrap().ident == "Entities" {
                            if reference.mutability.is_none() {
                                **ty = quote!(::shipyard::Entities).into();
                            } else {
                                **ty = quote!(::shipyard::EntitiesMut).into();
                            }
                        } else {
                            reference.lifetime = reference.lifetime.clone().or(Some(syn::Lifetime::new(
                                "'a",
                                proc_macro::Span::call_site().into(),
                            )));
                        }
                    } else {
                        reference.lifetime = reference.lifetime.clone().or(Some(syn::Lifetime::new(
                            "'a",
                            proc_macro::Span::call_site().into(),
                        )));
                    }
                }
                syn::Type::Path(ref mut path) => {
                    let last = path.path.segments.last_mut().unwrap();
                    // Not has to be handled separately since its lifetime is inside the type
                    if last.ident == "Not" {
                        if let syn::PathArguments::AngleBracketed(inner_type) = &mut last.arguments
                        {
                            assert!(inner_type.args.len() == 1, "Not will only accept one type and nothing else.");
                            let arg = inner_type.args.iter_mut().next().unwrap();
                            if let syn::GenericArgument::Type(inner_type) = arg {
                                if let syn::Type::Reference(reference) = inner_type {
                                    reference.lifetime = reference.lifetime.clone().or(Some(syn::Lifetime::new(
                                        "'a",
                                        proc_macro::Span::call_site().into(),
                                    )));
                                } else {
                                    panic!("Not will only work with component storages refered by &T or &mut T.")
                                }
                            } else {
                                unreachable!()
                            }
                        }
                    }
                }
                _ => {
                    panic!(
                        "A system will only accept a type of this list:\n\n\
                            \t\t\t&T for an immutable reference to T storage\n\
                            \t\t\t&mut T for a mutable reference to T storage\n\
                            \t\t\t&Entities for an immutable reference to the entity storage\n\
                            \t\t\t&mut EntitiesMut for a mutable reference to the entity storage\n\
                            \t\t\tAllStorages for a mutable reference to the storage of all components\n\
                            \t\t\tThreadPool for an immutable reference to the rayon::ThreadPool used by the World"
                    );
                }
            }

            data.push((*ty).clone());
            binding.push((**pat).clone());
        } else {
            unreachable!()
        }
    });

    // make tuples MAX_TYPES len maximum to allow users to pass an infinite number of types
    while data.len() > MAX_TYPES {
        for i in 0..(data.len() / MAX_TYPES) {
            let ten = &data[(i * MAX_TYPES)..((i + 1) * MAX_TYPES)];
            *data[i] = quote!((#(#ten,)*)).into();
            data.drain((i + 1)..((i + 1) * MAX_TYPES));

            let ten = &binding[i..(i + 10)];
            binding[i] = quote!((#(#ten,)*)).into();
            binding.drain((i + 1)..((i + 1) * MAX_TYPES));
        }
    }

    (quote! {
        struct #name;
        impl<'a> ::shipyard::System<'a> for #name {
            type Data = (#(#data,)*);
            fn run(&self, (#(#binding,)*): <Self::Data as ::shipyard::SystemData<'a>>::View) #body
        }
    })
    .into()
}