Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
bevyengine
GitHub Repository: bevyengine/bevy
Path: blob/main/crates/bevy_ecs/macro_logic/src/map_entities.rs
30636 views
1
use proc_macro2::TokenStream;
2
use quote::{format_ident, quote, ToTokens};
3
use syn::{
4
parse::Parse, spanned::Spanned, Data, DataEnum, DataStruct, Expr, ExprPath, Ident, Member,
5
Path, Token,
6
};
7
8
use crate::component::relationship_field;
9
10
const ENTITIES: &str = "entities";
11
12
/// Implements `MapEntities`
13
pub fn map_entities(
14
data: &Data,
15
bevy_ecs: &Path,
16
self_ident: Ident,
17
is_relationship: bool,
18
is_relationship_target: bool,
19
map_entities_attr: Option<MapEntitiesAttributeKind>,
20
) -> Option<TokenStream> {
21
if let Some(map_entities_override) = map_entities_attr {
22
let map_entities_tokens = map_entities_override.to_token_stream(bevy_ecs);
23
return Some(quote!(
24
#map_entities_tokens(#self_ident, mapper)
25
));
26
}
27
28
match data {
29
Data::Struct(DataStruct { fields, .. }) => {
30
let mut map = Vec::with_capacity(fields.len());
31
32
let relationship = if is_relationship || is_relationship_target {
33
relationship_field(fields, "MapEntities", fields.span()).ok()
34
} else {
35
None
36
};
37
fields
38
.iter()
39
.enumerate()
40
.filter(|(_, field)| {
41
field.attrs.iter().any(|a| a.path().is_ident(ENTITIES))
42
|| relationship.is_some_and(|relationship| relationship == *field)
43
})
44
.for_each(|(index, field)| {
45
let field_member = field
46
.ident
47
.clone()
48
.map_or(Member::from(index), Member::Named);
49
50
map.push(quote!(#self_ident.#field_member.map_entities(mapper);));
51
});
52
if map.is_empty() {
53
return None;
54
};
55
Some(quote!(
56
#(#map)*
57
))
58
}
59
Data::Enum(DataEnum { variants, .. }) => {
60
let mut map = Vec::with_capacity(variants.len());
61
62
for variant in variants.iter() {
63
let field_members = variant
64
.fields
65
.iter()
66
.enumerate()
67
.filter(|(_, field)| field.attrs.iter().any(|a| a.path().is_ident(ENTITIES)))
68
.map(|(index, field)| {
69
field
70
.ident
71
.clone()
72
.map_or(Member::from(index), Member::Named)
73
})
74
.collect::<Vec<_>>();
75
76
let ident = &variant.ident;
77
let field_idents = field_members
78
.iter()
79
.map(|member| format_ident!("__self{}", member))
80
.collect::<Vec<_>>();
81
82
map.push(
83
quote!(Self::#ident {#(#field_members: #field_idents,)* ..} => {
84
#(#field_idents.map_entities(mapper);)*
85
}),
86
);
87
}
88
89
if map.is_empty() {
90
return None;
91
};
92
93
Some(quote!(
94
match #self_ident {
95
#(#map,)*
96
_ => {}
97
}
98
))
99
}
100
Data::Union(_) => None,
101
}
102
}
103
104
/// The type of `MapEntities` attribute.
105
#[derive(Debug)]
106
pub enum MapEntitiesAttributeKind {
107
/// expressions like function or struct names
108
///
109
/// structs will throw compile errors on the code generation so this is safe
110
Path(ExprPath),
111
/// When no value is specified
112
Default,
113
}
114
115
impl MapEntitiesAttributeKind {
116
fn from_expr(value: Expr) -> syn::Result<Self> {
117
match value {
118
Expr::Path(path) => Ok(Self::Path(path)),
119
// throw meaningful error on all other expressions
120
_ => Err(syn::Error::new(
121
value.span(),
122
[
123
"Not supported in this position, please use one of the following:",
124
"- path to function",
125
"- nothing to default to MapEntities implementation",
126
]
127
.join("\n"),
128
)),
129
}
130
}
131
132
fn to_token_stream(&self, bevy_ecs_path: &Path) -> TokenStream {
133
match self {
134
MapEntitiesAttributeKind::Path(path) => path.to_token_stream(),
135
MapEntitiesAttributeKind::Default => {
136
quote!(
137
<Self as #bevy_ecs_path::entity::MapEntities>::map_entities
138
)
139
}
140
}
141
}
142
}
143
144
impl Parse for MapEntitiesAttributeKind {
145
fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
146
if input.peek(Token![=]) {
147
input.parse::<Token![=]>()?;
148
input.parse::<Expr>().and_then(Self::from_expr)
149
} else {
150
Ok(Self::Default)
151
}
152
}
153
}
154
155