mirror of
https://github.com/bevyengine/bevy
synced 2024-12-22 11:03:06 +00:00
eb3c81374a
# Objective - Provide an expressive way to register dynamic behavior in response to ECS changes that is consistent with existing bevy types and traits as to provide a smooth user experience. - Provide a mechanism for immediate changes in response to events during command application in order to facilitate improved query caching on the path to relations. ## Solution - A new fundamental ECS construct, the `Observer`; inspired by flec's observers but adapted to better fit bevy's access patterns and rust's type system. --- ## Examples There are 3 main ways to register observers. The first is a "component observer" that looks like this: ```rust world.observe(|trigger: Trigger<OnAdd, Transform>, query: Query<&Transform>| { let transform = query.get(trigger.entity()).unwrap(); }); ``` The above code will spawn a new entity representing the observer that will run it's callback whenever the `Transform` component is added to an entity. This is a system-like function that supports dependency injection for all the standard bevy types: `Query`, `Res`, `Commands` etc. It also has a `Trigger` parameter that provides information about the trigger such as the target entity, and the event being triggered. Importantly these systems run during command application which is key for their future use to keep ECS internals up to date. There are similar events for `OnInsert` and `OnRemove`, and this will be expanded with things such as `ArchetypeCreated`, `TableEmpty` etc. in follow up PRs. Another way to register an observer is an "entity observer" that looks like this: ```rust world.entity_mut(entity).observe(|trigger: Trigger<Resize>| { // ... }); ``` Entity observers run whenever an event of their type is triggered targeting that specific entity. This type of observer will de-spawn itself if the entity (or entities) it is observing is ever de-spawned so as to not leave dangling observers. Entity observers can also be spawned from deferred contexts such as other observers, systems, or hooks using commands: ```rust commands.entity(entity).observe(|trigger: Trigger<Resize>| { // ... }); ``` Observers are not limited to in built event types, they can be used with any type that implements `Event` (which has been extended to implement Component). This means events can also carry data: ```rust #[derive(Event)] struct Resize { x: u32, y: u32 } commands.entity(entity).observe(|trigger: Trigger<Resize>, query: Query<&mut Size>| { let event = trigger.event(); // ... }); // Will trigger the observer when commands are applied. commands.trigger_targets(Resize { x: 10, y: 10 }, entity); ``` You can also trigger events that target more than one entity at a time: ```rust commands.trigger_targets(Resize { x: 10, y: 10 }, [e1, e2]); ``` Additionally, Observers don't _need_ entity targets: ```rust app.observe(|trigger: Trigger<Quit>| { }) commands.trigger(Quit); ``` In these cases, `trigger.entity()` will be a placeholder. Observers are actually just normal entities with an `ObserverState` and `Observer` component! The `observe()` functions above are just shorthand for: ```rust world.spawn(Observer::new(|trigger: Trigger<Resize>| {}); ``` This will spawn the `Observer` system and use an `on_add` hook to add the `ObserverState` component. Dynamic components and trigger types are also fully supported allowing for runtime defined trigger types. ## Possible Follow-ups 1. Deprecate `RemovedComponents`, observers should fulfill all use cases while being more flexible and performant. 2. Queries as entities: Swap queries to entities and begin using observers listening to archetype creation triggers to keep their caches in sync, this allows unification of `ObserverState` and `QueryState` as well as unlocking several API improvements for `Query` and the management of `QueryState`. 3. Trigger bubbling: For some UI use cases in particular users are likely to want some form of bubbling for entity observers, this is trivial to implement naively but ideally this includes an acceleration structure to cache hierarchy traversals. 4. All kinds of other in-built trigger types. 5. Optimization; in order to not bloat the complexity of the PR I have kept the implementation straightforward, there are several areas where performance can be improved. The focus for this PR is to get the behavior implemented and not incur a performance cost for users who don't use observers. I am leaving each of these to follow up PR's in order to keep each of them reviewable as this already includes significant changes. --------- Co-authored-by: Alice Cecile <alice.i.cecile@gmail.com> Co-authored-by: MiniaczQ <xnetroidpl@gmail.com> Co-authored-by: Carter Anderson <mcanders1@gmail.com>
543 lines
21 KiB
Rust
543 lines
21 KiB
Rust
// FIXME(3492): remove once docs are ready
|
|
#![allow(missing_docs)]
|
|
#![cfg_attr(docsrs, feature(doc_auto_cfg))]
|
|
|
|
extern crate proc_macro;
|
|
|
|
mod component;
|
|
mod query_data;
|
|
mod query_filter;
|
|
mod states;
|
|
mod world_query;
|
|
|
|
use crate::{query_data::derive_query_data_impl, query_filter::derive_query_filter_impl};
|
|
use bevy_macro_utils::{derive_label, ensure_no_collision, get_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_struct_fields(&ast.data) {
|
|
Ok(fields) => fields,
|
|
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())
|
|
.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_component_ids = Vec::new();
|
|
let mut field_get_components = Vec::new();
|
|
let mut field_from_components = Vec::new();
|
|
for (((i, field_type), field_kind), field) in field_type
|
|
.iter()
|
|
.enumerate()
|
|
.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_component_ids.push(quote! {
|
|
<#field_type as #ecs_path::bundle::Bundle>::get_component_ids(components, &mut *ids);
|
|
});
|
|
match field {
|
|
Some(field) => {
|
|
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),
|
|
});
|
|
}
|
|
None => {
|
|
let index = syn::Index::from(i);
|
|
field_get_components.push(quote! {
|
|
self.#index.get_components(&mut *func);
|
|
});
|
|
field_from_components.push(quote! {
|
|
#index: <#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)*
|
|
}
|
|
|
|
fn get_component_ids(
|
|
components: &#ecs_path::component::Components,
|
|
ids: &mut impl FnMut(Option<#ecs_path::component::ComponentId>)
|
|
){
|
|
#(#field_get_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,)*)>;
|
|
|
|
// Note: We allow non snake case so the compiler don't complain about the creation of non_snake_case variables
|
|
#[allow(non_snake_case)]
|
|
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);
|
|
// The variable is being defined with non_snake_case here
|
|
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,)*)
|
|
}
|
|
|
|
unsafe fn new_archetype(state: &mut Self::State, archetype: &Archetype, system_meta: &mut SystemMeta) {
|
|
// SAFETY: The caller ensures that `archetype` is from the World the state was initialized from in `init_state`.
|
|
unsafe { <(#(#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),
|
|
}
|
|
}
|
|
|
|
unsafe fn new_archetype(state: &mut Self::State, archetype: &#path::archetype::Archetype, system_meta: &mut #path::system::SystemMeta) {
|
|
// SAFETY: The caller ensures that `archetype` is from the World the state was initialized from in `init_state`.
|
|
unsafe { <#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);
|
|
}
|
|
|
|
fn queue(state: &mut Self::State, system_meta: &#path::system::SystemMeta, world: #path::world::DeferredWorld) {
|
|
<#fields_alias::<'_, '_, #punctuated_generic_idents> as #path::system::SystemParam>::queue(&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 `QueryData` to use a struct as a data parameter in a query
|
|
#[proc_macro_derive(QueryData, attributes(query_data))]
|
|
pub fn derive_query_data(input: TokenStream) -> TokenStream {
|
|
derive_query_data_impl(input)
|
|
}
|
|
|
|
/// Implement `QueryFilter` to use a struct as a filter parameter in a query
|
|
#[proc_macro_derive(QueryFilter, attributes(query_filter))]
|
|
pub fn derive_query_filter(input: TokenStream) -> TokenStream {
|
|
derive_query_filter_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, "ScheduleLabel", &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)
|
|
}
|
|
|
|
#[proc_macro_derive(SubStates, attributes(source))]
|
|
pub fn derive_substates(input: TokenStream) -> TokenStream {
|
|
states::derive_substates(input)
|
|
}
|