extern crate proc_macro;
use itertools::Itertools;
use proc_macro::TokenStream;
use quote::{quote, ToTokens};
use syn::{parse_macro_input, Attribute, ImplItem, ItemImpl, Meta};
macro_rules! fake_helpers{
($len:literal, $(($name:ident, $val:expr)),+) => {
$(///system parameter marker attribute that generates\
///`.add_system(
#[doc=stringify!(system_name.$val)]
#[proc_macro_attribute]
pub fn $name(_attr: TokenStream, input: TokenStream) -> TokenStream {
input
})
+
const PARAM_HELPERS: [(&'static str, &'static str ); $len]= [
$((stringify!($name), stringify!($val))),+
];
}
}
fake_helpers!(
34,
(sysparam, arg),
(startup, on_startup()),
(run_if, run_if(arg)),
(run_once, run_if(run_once())),
(before, before(arg)),
(after, after(arg)),
(pipe, pipe(arg)),
(dbg, pipe(system_adapter::dbg)),
(error, pipe(system_adapter::error)),
(ignore, pipe(system_adapter::ignore)),
(info, pipe(system_adapter::info)),
(unwrap, pipe(system_adapter::unwrap)),
(warn, pipe(system_adapter::warn)),
(not, run_if(not(arg))),
(any_with_component, run_if(any_with_component::<arg>())),
(resource_added, run_if(resource_added::<arg>())),
(resource_changed, run_if(resource_changed::<arg>())),
(
resource_changed_or_removed,
run_if(resource_changed_or_removed::<arg>())
),
(resource_equals, run_if(resource_equals(arg))),
(resource_exists, run_if(resource_exists::<arg>())),
(
resource_exists_and_changed,
run_if(resource_exists_and_changed::<arg>())
),
(
resource_exists_and_equals,
run_if(resource_exists_and_equals(arg))
),
(resource_removed, run_if(resource_removed::<arg>())),
(on_event, run_if(on_event::<arg>())),
(on_enter, in_schedule(OnEnter(arg))),
(on_exit, in_schedule(OnExit(arg))),
(on_update, in_set(OnUpdate(arg))),
(in_set, in_set(arg)),
(in_base_set, in_base_set(arg)),
(in_schedule, in_schedule(arg)),
(in_state, run_if(in_state(arg))),
(state_changed, run_if(state_changed::<arg>())),
(state_exists, run_if(state_exists::<arg>())),
(
state_exists_and_equals,
run_if(state_exists_and_equals(arg))
)
);
#[proc_macro_attribute]
pub fn do_not_add(_attr: TokenStream, input: TokenStream) -> TokenStream {
input
}
fn get_fake_helper_name(attr: &Attribute) -> String {
attr.meta
.path()
.segments
.last()
.map(|a| a.ident.clone())
.unwrap()
.to_string()
}
#[proc_macro_attribute]
pub fn bevy_plugin(_attr: TokenStream, input: TokenStream) -> TokenStream {
let mut input = parse_macro_input!(input as ItemImpl);
let ty = input.self_ty.clone();
let mut custom_build = quote! {};
input.items = input
.items
.iter()
.cloned()
.filter(|item| {
if let ImplItem::Fn(func) = item {
let ident = func.sig.ident.clone();
if ident.to_string() == "build" {
custom_build = func.block.clone().into_token_stream();
return false;
}
}
true
})
.collect_vec();
let systems = input
.items
.iter()
.filter_map(|item| {
if let ImplItem::Fn(func) = item {
if func
.attrs
.iter()
.any(|attr| get_fake_helper_name(attr) == "do_not_add")
{
return None;
}
Some(func)
} else {
None
}
})
.map(|system| {
let ident = system.sig.ident.clone();
let extensions = &system
.attrs
.iter()
.filter_map(|attr| {
let path = get_fake_helper_name(attr);
let params = if let Meta::List(meta_list) = attr.meta.clone() {
meta_list.tokens
} else {
Default::default()
};
PARAM_HELPERS
.iter()
.find(|(name, _)| path.as_str() == *name)
.map(|(_, val)| val.replace("arg", params.to_string().as_str()))
})
.join(".");
if extensions.len() > 0 {
let extensions =
syn::parse_str::<proc_macro2::TokenStream>(extensions.as_str()).unwrap();
quote! { .add_system(#ty::#ident.#extensions)}
} else if system.attrs.len() == 0 {
quote!(.add_system(#ty::#ident))
} else {
Default::default()
}
})
.collect_vec();
let add_systems = if systems.len() > 0 {
quote!(app #(#systems)*;)
} else {
Default::default()
};
let output = quote! {
#input
impl Plugin for #ty {
fn build(&self, app: &mut App) {
#add_systems
#custom_build
}
}
};
TokenStream::from(output)
}