Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
bevyengine
GitHub Repository: bevyengine/bevy
Path: blob/main/crates/bevy_ecs/macros/src/event.rs
6849 views
1
use proc_macro::TokenStream;
2
use quote::quote;
3
use syn::{
4
parse_macro_input, parse_quote, spanned::Spanned, Data, DataStruct, DeriveInput, Fields, Index,
5
Member, Path, Result, Token, Type,
6
};
7
8
pub const EVENT: &str = "event";
9
pub const ENTITY_EVENT: &str = "entity_event";
10
pub const PROPAGATE: &str = "propagate";
11
#[deprecated(since = "0.17.0", note = "This has been renamed to `propagate`.")]
12
pub const TRAVERSAL: &str = "traversal";
13
pub const AUTO_PROPAGATE: &str = "auto_propagate";
14
pub const TRIGGER: &str = "trigger";
15
pub const EVENT_TARGET: &str = "event_target";
16
17
pub fn derive_event(input: TokenStream) -> TokenStream {
18
let mut ast = parse_macro_input!(input as DeriveInput);
19
let bevy_ecs_path: Path = crate::bevy_ecs_path();
20
21
ast.generics
22
.make_where_clause()
23
.predicates
24
.push(parse_quote! { Self: Send + Sync + 'static });
25
26
let mut processed_attrs = Vec::new();
27
let mut trigger: Option<Type> = None;
28
29
for attr in ast.attrs.iter().filter(|attr| attr.path().is_ident(EVENT)) {
30
if let Err(e) = attr.parse_nested_meta(|meta| match meta.path.get_ident() {
31
Some(ident) if processed_attrs.iter().any(|i| ident == i) => {
32
Err(meta.error(format!("duplicate attribute: {ident}")))
33
}
34
Some(ident) if ident == TRIGGER => {
35
trigger = Some(meta.value()?.parse()?);
36
processed_attrs.push(TRIGGER);
37
Ok(())
38
}
39
Some(ident) => Err(meta.error(format!("unsupported attribute: {ident}"))),
40
None => Err(meta.error("expected identifier")),
41
}) {
42
return e.to_compile_error().into();
43
}
44
}
45
46
let trigger = if let Some(trigger) = trigger {
47
quote! {#trigger}
48
} else {
49
quote! {#bevy_ecs_path::event::GlobalTrigger}
50
};
51
52
let struct_name = &ast.ident;
53
let (impl_generics, type_generics, where_clause) = &ast.generics.split_for_impl();
54
55
TokenStream::from(quote! {
56
impl #impl_generics #bevy_ecs_path::event::Event for #struct_name #type_generics #where_clause {
57
type Trigger<'a> = #trigger;
58
}
59
})
60
}
61
62
pub fn derive_entity_event(input: TokenStream) -> TokenStream {
63
let mut ast = parse_macro_input!(input as DeriveInput);
64
65
ast.generics
66
.make_where_clause()
67
.predicates
68
.push(parse_quote! { Self: Send + Sync + 'static });
69
70
let mut auto_propagate = false;
71
let mut propagate = false;
72
let mut traversal: Option<Type> = None;
73
let mut trigger: Option<Type> = None;
74
let bevy_ecs_path: Path = crate::bevy_ecs_path();
75
76
let mut processed_attrs = Vec::new();
77
78
for attr in ast
79
.attrs
80
.iter()
81
.filter(|attr| attr.path().is_ident(ENTITY_EVENT))
82
{
83
if let Err(e) = attr.parse_nested_meta(|meta| match meta.path.get_ident() {
84
Some(ident) if processed_attrs.iter().any(|i| ident == i) => {
85
Err(meta.error(format!("duplicate attribute: {ident}")))
86
}
87
Some(ident) if ident == AUTO_PROPAGATE => {
88
propagate = true;
89
auto_propagate = true;
90
processed_attrs.push(AUTO_PROPAGATE);
91
Ok(())
92
}
93
#[expect(deprecated, reason = "we want to continue supporting this for a release")]
94
Some(ident) if ident == TRAVERSAL => {
95
Err(meta.error(
96
"`traversal` has been renamed to `propagate`, use that instead. If you were writing `traversal = &'static ChildOf`, you can now just write `propagate`, which defaults to the `ChildOf` traversal."
97
))
98
}
99
Some(ident) if ident == PROPAGATE => {
100
propagate = true;
101
if meta.input.peek(Token![=]) {
102
traversal = Some(meta.value()?.parse()?);
103
}
104
processed_attrs.push(PROPAGATE);
105
Ok(())
106
}
107
Some(ident) if ident == TRIGGER => {
108
trigger = Some(meta.value()?.parse()?);
109
processed_attrs.push(TRIGGER);
110
Ok(())
111
}
112
Some(ident) => Err(meta.error(format!("unsupported attribute: {ident}"))),
113
None => Err(meta.error("expected identifier")),
114
}) {
115
return e.to_compile_error().into();
116
}
117
}
118
119
if trigger.is_some() && propagate {
120
return syn::Error::new(
121
ast.span(),
122
"Cannot define both #[entity_event(trigger)] and #[entity_event(propagate)]",
123
)
124
.into_compile_error()
125
.into();
126
}
127
128
let entity_field = match get_event_target_field(&ast) {
129
Ok(value) => value,
130
Err(err) => return err.into_compile_error().into(),
131
};
132
133
let struct_name = &ast.ident;
134
let (impl_generics, type_generics, where_clause) = &ast.generics.split_for_impl();
135
136
let trigger = if let Some(trigger) = trigger {
137
quote! {#trigger}
138
} else if propagate {
139
let traversal = traversal
140
.unwrap_or_else(|| parse_quote! { &'static #bevy_ecs_path::hierarchy::ChildOf});
141
quote! {#bevy_ecs_path::event::PropagateEntityTrigger<#auto_propagate, Self, #traversal>}
142
} else {
143
quote! {#bevy_ecs_path::event::EntityTrigger}
144
};
145
TokenStream::from(quote! {
146
impl #impl_generics #bevy_ecs_path::event::Event for #struct_name #type_generics #where_clause {
147
type Trigger<'a> = #trigger;
148
}
149
150
impl #impl_generics #bevy_ecs_path::event::EntityEvent for #struct_name #type_generics #where_clause {
151
fn event_target(&self) -> #bevy_ecs_path::entity::Entity {
152
self.#entity_field
153
}
154
155
fn event_target_mut(&mut self) -> &mut #bevy_ecs_path::entity::Entity {
156
&mut self.#entity_field
157
}
158
}
159
160
})
161
}
162
163
/// Returns the field with the `#[event_target]` attribute, the only field if unnamed,
164
/// or the field with the name "entity".
165
fn get_event_target_field(ast: &DeriveInput) -> Result<Member> {
166
let Data::Struct(DataStruct { fields, .. }) = &ast.data else {
167
return Err(syn::Error::new(
168
ast.span(),
169
"EntityEvent can only be derived for structs.",
170
));
171
};
172
match fields {
173
Fields::Named(fields) => fields.named.iter().find_map(|field| {
174
if field.ident.as_ref().is_some_and(|i| i == "entity") || field
175
.attrs
176
.iter()
177
.any(|attr| attr.path().is_ident(EVENT_TARGET)) {
178
Some(Member::Named(field.ident.clone()?))
179
} else {
180
None
181
}
182
}).ok_or(syn::Error::new(
183
fields.span(),
184
"EntityEvent derive expected a field name 'entity' or a field annotated with #[event_target]."
185
)),
186
Fields::Unnamed(fields) if fields.unnamed.len() == 1 => Ok(Member::Unnamed(Index::from(0))),
187
Fields::Unnamed(fields) => fields.unnamed.iter().enumerate().find_map(|(index, field)| {
188
if field
189
.attrs
190
.iter()
191
.any(|attr| attr.path().is_ident(EVENT_TARGET)) {
192
Some(Member::Unnamed(Index::from(index)))
193
} else {
194
None
195
}
196
})
197
.ok_or(syn::Error::new(
198
fields.span(),
199
"EntityEvent derive expected unnamed structs with one field or with a field annotated with #[event_target].",
200
)),
201
Fields::Unit => Err(syn::Error::new(
202
fields.span(),
203
"EntityEvent derive does not work on unit structs. Your type must have a field to store the `Entity` target, such as `Attack(Entity)` or `Attack { entity: Entity }`.",
204
)),
205
}
206
}
207
208