mirror of
https://github.com/bevyengine/bevy
synced 2024-12-23 03:23:20 +00:00
a830530be4
# Objective First of all, this PR took heavy inspiration from #7760 and #5715. It intends to also fix #5569, but with a slightly different approach. This also fixes #9335 by reexporting `DynEq`. ## Solution The advantage of this API is that we can intern a value without allocating for zero-sized-types and for enum variants that have no fields. This PR does this automatically in the `SystemSet` and `ScheduleLabel` derive macros for unit structs and fieldless enum variants. So this should cover many internal and external use cases of `SystemSet` and `ScheduleLabel`. In these optimal use cases, no memory will be allocated. - The interning returns a `Interned<dyn SystemSet>`, which is just a wrapper around a `&'static dyn SystemSet`. - `Hash` and `Eq` are implemented in terms of the pointer value of the reference, similar to my first approach of anonymous system sets in #7676. - Therefore, `Interned<T>` does not implement `Borrow<T>`, only `Deref`. - The debug output of `Interned<T>` is the same as the interned value. Edit: - `AppLabel` is now also interned and the old `derive_label`/`define_label` macros were replaced with the new interning implementation. - Anonymous set ids are reused for different `Schedule`s, reducing the amount of leaked memory. ### Pros - `InternedSystemSet` and `InternedScheduleLabel` behave very similar to the current `BoxedSystemSet` and `BoxedScheduleLabel`, but can be copied without an allocation. - Many use cases don't allocate at all. - Very fast lookups and comparisons when using `InternedSystemSet` and `InternedScheduleLabel`. - The `intern` module might be usable in other areas. - `Interned{ScheduleLabel, SystemSet, AppLabel}` does implement `{ScheduleLabel, SystemSet, AppLabel}`, increasing ergonomics. ### Cons - Implementors of `SystemSet` and `ScheduleLabel` still need to implement `Hash` and `Eq` (and `Clone`) for it to work. ## Changelog ### Added - Added `intern` module to `bevy_utils`. - Added reexports of `DynEq` to `bevy_ecs` and `bevy_app`. ### Changed - Replaced `BoxedSystemSet` and `BoxedScheduleLabel` with `InternedSystemSet` and `InternedScheduleLabel`. - Replaced `impl AsRef<dyn ScheduleLabel>` with `impl ScheduleLabel`. - Replaced `AppLabelId` with `InternedAppLabel`. - Changed `AppLabel` to use `Debug` for error messages. - Changed `AppLabel` to use interning. - Changed `define_label`/`derive_label` to use interning. - Replaced `define_boxed_label`/`derive_boxed_label` with `define_label`/`derive_label`. - Changed anonymous set ids to be only unique inside a schedule, not globally. - Made interned label types implement their label trait. ### Removed - Removed `define_boxed_label` and `derive_boxed_label`. ## Migration guide - Replace `BoxedScheduleLabel` and `Box<dyn ScheduleLabel>` with `InternedScheduleLabel` or `Interned<dyn ScheduleLabel>`. - Replace `BoxedSystemSet` and `Box<dyn SystemSet>` with `InternedSystemSet` or `Interned<dyn SystemSet>`. - Replace `AppLabelId` with `InternedAppLabel` or `Interned<dyn AppLabel>`. - Types manually implementing `ScheduleLabel`, `AppLabel` or `SystemSet` need to implement: - `dyn_hash` directly instead of implementing `DynHash` - `as_dyn_eq` - Pass labels to `World::try_schedule_scope`, `World::schedule_scope`, `World::try_run_schedule`. `World::run_schedule`, `Schedules::remove`, `Schedules::remove_entry`, `Schedules::contains`, `Schedules::get` and `Schedules::get_mut` by value instead of by reference. --------- Co-authored-by: Joseph <21144246+JoJoJet@users.noreply.github.com> Co-authored-by: Carter Anderson <mcanders1@gmail.com>
489 lines
19 KiB
Rust
489 lines
19 KiB
Rust
extern crate proc_macro;
|
|
|
|
mod component;
|
|
mod fetch;
|
|
mod states;
|
|
|
|
use crate::fetch::derive_world_query_impl;
|
|
use bevy_macro_utils::{derive_label, ensure_no_collision, get_named_struct_fields, BevyManifest};
|
|
use proc_macro::TokenStream;
|
|
use proc_macro2::Span;
|
|
use quote::{format_ident, quote};
|
|
use syn::{
|
|
parse_macro_input, parse_quote, punctuated::Punctuated, spanned::Spanned, token::Comma,
|
|
ConstParam, DeriveInput, GenericParam, Ident, Index, TypeParam,
|
|
};
|
|
|
|
enum BundleFieldKind {
|
|
Component,
|
|
Ignore,
|
|
}
|
|
|
|
const BUNDLE_ATTRIBUTE_NAME: &str = "bundle";
|
|
const BUNDLE_ATTRIBUTE_IGNORE_NAME: &str = "ignore";
|
|
|
|
#[proc_macro_derive(Bundle, attributes(bundle))]
|
|
pub fn derive_bundle(input: TokenStream) -> TokenStream {
|
|
let ast = parse_macro_input!(input as DeriveInput);
|
|
let ecs_path = bevy_ecs_path();
|
|
|
|
let named_fields = match get_named_struct_fields(&ast.data) {
|
|
Ok(fields) => &fields.named,
|
|
Err(e) => return e.into_compile_error().into(),
|
|
};
|
|
|
|
let mut field_kind = Vec::with_capacity(named_fields.len());
|
|
|
|
for field in named_fields {
|
|
for attr in field
|
|
.attrs
|
|
.iter()
|
|
.filter(|a| a.path().is_ident(BUNDLE_ATTRIBUTE_NAME))
|
|
{
|
|
if let Err(error) = attr.parse_nested_meta(|meta| {
|
|
if meta.path.is_ident(BUNDLE_ATTRIBUTE_IGNORE_NAME) {
|
|
field_kind.push(BundleFieldKind::Ignore);
|
|
Ok(())
|
|
} else {
|
|
Err(meta.error(format!(
|
|
"Invalid bundle attribute. Use `{BUNDLE_ATTRIBUTE_IGNORE_NAME}`"
|
|
)))
|
|
}
|
|
}) {
|
|
return error.into_compile_error().into();
|
|
}
|
|
}
|
|
|
|
field_kind.push(BundleFieldKind::Component);
|
|
}
|
|
|
|
let field = named_fields
|
|
.iter()
|
|
.map(|field| field.ident.as_ref().unwrap())
|
|
.collect::<Vec<_>>();
|
|
let field_type = named_fields
|
|
.iter()
|
|
.map(|field| &field.ty)
|
|
.collect::<Vec<_>>();
|
|
|
|
let mut field_component_ids = Vec::new();
|
|
let mut field_get_components = Vec::new();
|
|
let mut field_from_components = Vec::new();
|
|
for ((field_type, field_kind), field) in
|
|
field_type.iter().zip(field_kind.iter()).zip(field.iter())
|
|
{
|
|
match field_kind {
|
|
BundleFieldKind::Component => {
|
|
field_component_ids.push(quote! {
|
|
<#field_type as #ecs_path::bundle::Bundle>::component_ids(components, storages, &mut *ids);
|
|
});
|
|
field_get_components.push(quote! {
|
|
self.#field.get_components(&mut *func);
|
|
});
|
|
field_from_components.push(quote! {
|
|
#field: <#field_type as #ecs_path::bundle::Bundle>::from_components(ctx, &mut *func),
|
|
});
|
|
}
|
|
|
|
BundleFieldKind::Ignore => {
|
|
field_from_components.push(quote! {
|
|
#field: ::std::default::Default::default(),
|
|
});
|
|
}
|
|
}
|
|
}
|
|
let generics = ast.generics;
|
|
let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
|
|
let struct_name = &ast.ident;
|
|
|
|
TokenStream::from(quote! {
|
|
// SAFETY:
|
|
// - ComponentId is returned in field-definition-order. [from_components] and [get_components] use field-definition-order
|
|
// - `Bundle::get_components` is exactly once for each member. Rely's on the Component -> Bundle implementation to properly pass
|
|
// the correct `StorageType` into the callback.
|
|
unsafe impl #impl_generics #ecs_path::bundle::Bundle for #struct_name #ty_generics #where_clause {
|
|
fn component_ids(
|
|
components: &mut #ecs_path::component::Components,
|
|
storages: &mut #ecs_path::storage::Storages,
|
|
ids: &mut impl FnMut(#ecs_path::component::ComponentId)
|
|
){
|
|
#(#field_component_ids)*
|
|
}
|
|
|
|
#[allow(unused_variables, non_snake_case)]
|
|
unsafe fn from_components<__T, __F>(ctx: &mut __T, func: &mut __F) -> Self
|
|
where
|
|
__F: FnMut(&mut __T) -> #ecs_path::ptr::OwningPtr<'_>
|
|
{
|
|
Self {
|
|
#(#field_from_components)*
|
|
}
|
|
}
|
|
}
|
|
|
|
impl #impl_generics #ecs_path::bundle::DynamicBundle for #struct_name #ty_generics #where_clause {
|
|
#[allow(unused_variables)]
|
|
#[inline]
|
|
fn get_components(
|
|
self,
|
|
func: &mut impl FnMut(#ecs_path::component::StorageType, #ecs_path::ptr::OwningPtr<'_>)
|
|
) {
|
|
#(#field_get_components)*
|
|
}
|
|
}
|
|
})
|
|
}
|
|
|
|
fn get_idents(fmt_string: fn(usize) -> String, count: usize) -> Vec<Ident> {
|
|
(0..count)
|
|
.map(|i| Ident::new(&fmt_string(i), Span::call_site()))
|
|
.collect::<Vec<Ident>>()
|
|
}
|
|
|
|
#[proc_macro]
|
|
pub fn impl_param_set(_input: TokenStream) -> TokenStream {
|
|
let mut tokens = TokenStream::new();
|
|
let max_params = 8;
|
|
let params = get_idents(|i| format!("P{i}"), max_params);
|
|
let metas = get_idents(|i| format!("m{i}"), max_params);
|
|
let mut param_fn_muts = Vec::new();
|
|
for (i, param) in params.iter().enumerate() {
|
|
let fn_name = Ident::new(&format!("p{i}"), Span::call_site());
|
|
let index = Index::from(i);
|
|
let ordinal = match i {
|
|
1 => "1st".to_owned(),
|
|
2 => "2nd".to_owned(),
|
|
3 => "3rd".to_owned(),
|
|
x => format!("{x}th"),
|
|
};
|
|
let comment =
|
|
format!("Gets exclusive access to the {ordinal} parameter in this [`ParamSet`].");
|
|
param_fn_muts.push(quote! {
|
|
#[doc = #comment]
|
|
/// No other parameters may be accessed while this one is active.
|
|
pub fn #fn_name<'a>(&'a mut self) -> SystemParamItem<'a, 'a, #param> {
|
|
// SAFETY: systems run without conflicts with other systems.
|
|
// Conflicting params in ParamSet are not accessible at the same time
|
|
// ParamSets are guaranteed to not conflict with other SystemParams
|
|
unsafe {
|
|
#param::get_param(&mut self.param_states.#index, &self.system_meta, self.world, self.change_tick)
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
for param_count in 1..=max_params {
|
|
let param = ¶ms[0..param_count];
|
|
let meta = &metas[0..param_count];
|
|
let param_fn_mut = ¶m_fn_muts[0..param_count];
|
|
tokens.extend(TokenStream::from(quote! {
|
|
// SAFETY: All parameters are constrained to ReadOnlySystemParam, so World is only read
|
|
unsafe impl<'w, 's, #(#param,)*> ReadOnlySystemParam for ParamSet<'w, 's, (#(#param,)*)>
|
|
where #(#param: ReadOnlySystemParam,)*
|
|
{ }
|
|
|
|
// SAFETY: Relevant parameter ComponentId and ArchetypeComponentId access is applied to SystemMeta. If any ParamState conflicts
|
|
// with any prior access, a panic will occur.
|
|
unsafe impl<'_w, '_s, #(#param: SystemParam,)*> SystemParam for ParamSet<'_w, '_s, (#(#param,)*)>
|
|
{
|
|
type State = (#(#param::State,)*);
|
|
type Item<'w, 's> = ParamSet<'w, 's, (#(#param,)*)>;
|
|
|
|
fn init_state(world: &mut World, system_meta: &mut SystemMeta) -> Self::State {
|
|
#(
|
|
// Pretend to add each param to the system alone, see if it conflicts
|
|
let mut #meta = system_meta.clone();
|
|
#meta.component_access_set.clear();
|
|
#meta.archetype_component_access.clear();
|
|
#param::init_state(world, &mut #meta);
|
|
let #param = #param::init_state(world, &mut system_meta.clone());
|
|
)*
|
|
// Make the ParamSet non-send if any of its parameters are non-send.
|
|
if false #(|| !#meta.is_send())* {
|
|
system_meta.set_non_send();
|
|
}
|
|
#(
|
|
system_meta
|
|
.component_access_set
|
|
.extend(#meta.component_access_set);
|
|
system_meta
|
|
.archetype_component_access
|
|
.extend(&#meta.archetype_component_access);
|
|
)*
|
|
(#(#param,)*)
|
|
}
|
|
|
|
fn new_archetype(state: &mut Self::State, archetype: &Archetype, system_meta: &mut SystemMeta) {
|
|
<(#(#param,)*) as SystemParam>::new_archetype(state, archetype, system_meta);
|
|
}
|
|
|
|
fn apply(state: &mut Self::State, system_meta: &SystemMeta, world: &mut World) {
|
|
<(#(#param,)*) as SystemParam>::apply(state, system_meta, world);
|
|
}
|
|
|
|
#[inline]
|
|
unsafe fn get_param<'w, 's>(
|
|
state: &'s mut Self::State,
|
|
system_meta: &SystemMeta,
|
|
world: UnsafeWorldCell<'w>,
|
|
change_tick: Tick,
|
|
) -> Self::Item<'w, 's> {
|
|
ParamSet {
|
|
param_states: state,
|
|
system_meta: system_meta.clone(),
|
|
world,
|
|
change_tick,
|
|
}
|
|
}
|
|
}
|
|
|
|
impl<'w, 's, #(#param: SystemParam,)*> ParamSet<'w, 's, (#(#param,)*)>
|
|
{
|
|
#(#param_fn_mut)*
|
|
}
|
|
}));
|
|
}
|
|
|
|
tokens
|
|
}
|
|
|
|
/// Implement `SystemParam` to use a struct as a parameter in a system
|
|
#[proc_macro_derive(SystemParam, attributes(system_param))]
|
|
pub fn derive_system_param(input: TokenStream) -> TokenStream {
|
|
let token_stream = input.clone();
|
|
let ast = parse_macro_input!(input as DeriveInput);
|
|
let syn::Data::Struct(syn::DataStruct {
|
|
fields: field_definitions,
|
|
..
|
|
}) = ast.data
|
|
else {
|
|
return syn::Error::new(
|
|
ast.span(),
|
|
"Invalid `SystemParam` type: expected a `struct`",
|
|
)
|
|
.into_compile_error()
|
|
.into();
|
|
};
|
|
let path = bevy_ecs_path();
|
|
|
|
let mut field_locals = Vec::new();
|
|
let mut fields = Vec::new();
|
|
let mut field_types = Vec::new();
|
|
for (i, field) in field_definitions.iter().enumerate() {
|
|
field_locals.push(format_ident!("f{i}"));
|
|
let i = Index::from(i);
|
|
fields.push(
|
|
field
|
|
.ident
|
|
.as_ref()
|
|
.map(|f| quote! { #f })
|
|
.unwrap_or_else(|| quote! { #i }),
|
|
);
|
|
field_types.push(&field.ty);
|
|
}
|
|
|
|
let generics = ast.generics;
|
|
|
|
// Emit an error if there's any unrecognized lifetime names.
|
|
for lt in generics.lifetimes() {
|
|
let ident = <.lifetime.ident;
|
|
let w = format_ident!("w");
|
|
let s = format_ident!("s");
|
|
if ident != &w && ident != &s {
|
|
return syn::Error::new_spanned(
|
|
lt,
|
|
r#"invalid lifetime name: expected `'w` or `'s`
|
|
'w -- refers to data stored in the World.
|
|
's -- refers to data stored in the SystemParam's state.'"#,
|
|
)
|
|
.into_compile_error()
|
|
.into();
|
|
}
|
|
}
|
|
|
|
let (_impl_generics, ty_generics, where_clause) = generics.split_for_impl();
|
|
|
|
let lifetimeless_generics: Vec<_> = generics
|
|
.params
|
|
.iter()
|
|
.filter(|g| !matches!(g, GenericParam::Lifetime(_)))
|
|
.collect();
|
|
|
|
let shadowed_lifetimes: Vec<_> = generics.lifetimes().map(|_| quote!('_)).collect();
|
|
|
|
let mut punctuated_generics = Punctuated::<_, Comma>::new();
|
|
punctuated_generics.extend(lifetimeless_generics.iter().map(|g| match g {
|
|
GenericParam::Type(g) => GenericParam::Type(TypeParam {
|
|
default: None,
|
|
..g.clone()
|
|
}),
|
|
GenericParam::Const(g) => GenericParam::Const(ConstParam {
|
|
default: None,
|
|
..g.clone()
|
|
}),
|
|
_ => unreachable!(),
|
|
}));
|
|
|
|
let mut punctuated_generic_idents = Punctuated::<_, Comma>::new();
|
|
punctuated_generic_idents.extend(lifetimeless_generics.iter().map(|g| match g {
|
|
GenericParam::Type(g) => &g.ident,
|
|
GenericParam::Const(g) => &g.ident,
|
|
_ => unreachable!(),
|
|
}));
|
|
|
|
let punctuated_generics_no_bounds: Punctuated<_, Comma> = lifetimeless_generics
|
|
.iter()
|
|
.map(|&g| match g.clone() {
|
|
GenericParam::Type(mut g) => {
|
|
g.bounds.clear();
|
|
GenericParam::Type(g)
|
|
}
|
|
g => g,
|
|
})
|
|
.collect();
|
|
|
|
let mut tuple_types: Vec<_> = field_types.iter().map(|x| quote! { #x }).collect();
|
|
let mut tuple_patterns: Vec<_> = field_locals.iter().map(|x| quote! { #x }).collect();
|
|
|
|
// If the number of fields exceeds the 16-parameter limit,
|
|
// fold the fields into tuples of tuples until we are below the limit.
|
|
const LIMIT: usize = 16;
|
|
while tuple_types.len() > LIMIT {
|
|
let end = Vec::from_iter(tuple_types.drain(..LIMIT));
|
|
tuple_types.push(parse_quote!( (#(#end,)*) ));
|
|
|
|
let end = Vec::from_iter(tuple_patterns.drain(..LIMIT));
|
|
tuple_patterns.push(parse_quote!( (#(#end,)*) ));
|
|
}
|
|
|
|
// Create a where clause for the `ReadOnlySystemParam` impl.
|
|
// Ensure that each field implements `ReadOnlySystemParam`.
|
|
let mut read_only_generics = generics.clone();
|
|
let read_only_where_clause = read_only_generics.make_where_clause();
|
|
for field_type in &field_types {
|
|
read_only_where_clause
|
|
.predicates
|
|
.push(syn::parse_quote!(#field_type: #path::system::ReadOnlySystemParam));
|
|
}
|
|
|
|
let fields_alias =
|
|
ensure_no_collision(format_ident!("__StructFieldsAlias"), token_stream.clone());
|
|
|
|
let struct_name = &ast.ident;
|
|
let state_struct_visibility = &ast.vis;
|
|
let state_struct_name = ensure_no_collision(format_ident!("FetchState"), token_stream);
|
|
|
|
TokenStream::from(quote! {
|
|
// We define the FetchState struct in an anonymous scope to avoid polluting the user namespace.
|
|
// The struct can still be accessed via SystemParam::State, e.g. EventReaderState can be accessed via
|
|
// <EventReader<'static, 'static, T> as SystemParam>::State
|
|
const _: () = {
|
|
// Allows rebinding the lifetimes of each field type.
|
|
type #fields_alias <'w, 's, #punctuated_generics_no_bounds> = (#(#tuple_types,)*);
|
|
|
|
#[doc(hidden)]
|
|
#state_struct_visibility struct #state_struct_name <#(#lifetimeless_generics,)*>
|
|
#where_clause {
|
|
state: <#fields_alias::<'static, 'static, #punctuated_generic_idents> as #path::system::SystemParam>::State,
|
|
}
|
|
|
|
unsafe impl<#punctuated_generics> #path::system::SystemParam for
|
|
#struct_name <#(#shadowed_lifetimes,)* #punctuated_generic_idents> #where_clause
|
|
{
|
|
type State = #state_struct_name<#punctuated_generic_idents>;
|
|
type Item<'w, 's> = #struct_name #ty_generics;
|
|
|
|
fn init_state(world: &mut #path::world::World, system_meta: &mut #path::system::SystemMeta) -> Self::State {
|
|
#state_struct_name {
|
|
state: <#fields_alias::<'_, '_, #punctuated_generic_idents> as #path::system::SystemParam>::init_state(world, system_meta),
|
|
}
|
|
}
|
|
|
|
fn new_archetype(state: &mut Self::State, archetype: &#path::archetype::Archetype, system_meta: &mut #path::system::SystemMeta) {
|
|
<#fields_alias::<'_, '_, #punctuated_generic_idents> as #path::system::SystemParam>::new_archetype(&mut state.state, archetype, system_meta)
|
|
}
|
|
|
|
fn apply(state: &mut Self::State, system_meta: &#path::system::SystemMeta, world: &mut #path::world::World) {
|
|
<#fields_alias::<'_, '_, #punctuated_generic_idents> as #path::system::SystemParam>::apply(&mut state.state, system_meta, world);
|
|
}
|
|
|
|
unsafe fn get_param<'w, 's>(
|
|
state: &'s mut Self::State,
|
|
system_meta: &#path::system::SystemMeta,
|
|
world: #path::world::unsafe_world_cell::UnsafeWorldCell<'w>,
|
|
change_tick: #path::component::Tick,
|
|
) -> Self::Item<'w, 's> {
|
|
let (#(#tuple_patterns,)*) = <
|
|
(#(#tuple_types,)*) as #path::system::SystemParam
|
|
>::get_param(&mut state.state, system_meta, world, change_tick);
|
|
#struct_name {
|
|
#(#fields: #field_locals,)*
|
|
}
|
|
}
|
|
}
|
|
|
|
// Safety: Each field is `ReadOnlySystemParam`, so this can only read from the `World`
|
|
unsafe impl<'w, 's, #punctuated_generics> #path::system::ReadOnlySystemParam for #struct_name #ty_generics #read_only_where_clause {}
|
|
};
|
|
})
|
|
}
|
|
|
|
/// Implement `WorldQuery` to use a struct as a parameter in a query
|
|
#[proc_macro_derive(WorldQuery, attributes(world_query))]
|
|
pub fn derive_world_query(input: TokenStream) -> TokenStream {
|
|
derive_world_query_impl(input)
|
|
}
|
|
|
|
/// Derive macro generating an impl of the trait `ScheduleLabel`.
|
|
///
|
|
/// This does not work for unions.
|
|
#[proc_macro_derive(ScheduleLabel)]
|
|
pub fn derive_schedule_label(input: TokenStream) -> TokenStream {
|
|
let input = parse_macro_input!(input as DeriveInput);
|
|
let mut trait_path = bevy_ecs_path();
|
|
trait_path.segments.push(format_ident!("schedule").into());
|
|
let mut dyn_eq_path = trait_path.clone();
|
|
trait_path
|
|
.segments
|
|
.push(format_ident!("ScheduleLabel").into());
|
|
dyn_eq_path.segments.push(format_ident!("DynEq").into());
|
|
derive_label(input, "ScheduleName", &trait_path, &dyn_eq_path)
|
|
}
|
|
|
|
/// Derive macro generating an impl of the trait `SystemSet`.
|
|
///
|
|
/// This does not work for unions.
|
|
#[proc_macro_derive(SystemSet)]
|
|
pub fn derive_system_set(input: TokenStream) -> TokenStream {
|
|
let input = parse_macro_input!(input as DeriveInput);
|
|
let mut trait_path = bevy_ecs_path();
|
|
trait_path.segments.push(format_ident!("schedule").into());
|
|
let mut dyn_eq_path = trait_path.clone();
|
|
trait_path.segments.push(format_ident!("SystemSet").into());
|
|
dyn_eq_path.segments.push(format_ident!("DynEq").into());
|
|
derive_label(input, "SystemSet", &trait_path, &dyn_eq_path)
|
|
}
|
|
|
|
pub(crate) fn bevy_ecs_path() -> syn::Path {
|
|
BevyManifest::default().get_path("bevy_ecs")
|
|
}
|
|
|
|
#[proc_macro_derive(Event)]
|
|
pub fn derive_event(input: TokenStream) -> TokenStream {
|
|
component::derive_event(input)
|
|
}
|
|
|
|
#[proc_macro_derive(Resource)]
|
|
pub fn derive_resource(input: TokenStream) -> TokenStream {
|
|
component::derive_resource(input)
|
|
}
|
|
|
|
#[proc_macro_derive(Component, attributes(component))]
|
|
pub fn derive_component(input: TokenStream) -> TokenStream {
|
|
component::derive_component(input)
|
|
}
|
|
|
|
#[proc_macro_derive(States)]
|
|
pub fn derive_states(input: TokenStream) -> TokenStream {
|
|
states::derive_states(input)
|
|
}
|