1use proc_macro::TokenStream;
18use quote::{format_ident, quote};
19use syn::{parse_macro_input, Data, DeriveInput, Fields};
20
21#[proc_macro_derive(Component, attributes(component))]
43pub fn derive_component(input: TokenStream) -> TokenStream {
44 let input = parse_macro_input!(input as DeriveInput);
45 let name = &input.ident;
46 let vis = &input.vis;
47 let serializable_name = format_ident!("Serializable{}", name);
48 let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl();
49
50 let component_impl = quote! {
52 impl #impl_generics crate::ecs::component::Component for #name #ty_generics #where_clause {}
53 };
54
55 let fields = match &input.data {
57 Data::Struct(data) => &data.fields,
58 _ => {
59 return TokenStream::from(quote! {
60 #component_impl
61 compile_error!("Component derive only supports structs");
62 });
63 }
64 };
65
66 let layout_soa = input.attrs.iter().any(|attr| {
74 if !attr.path().is_ident("component") {
75 return false;
76 }
77 let mut soa = false;
78 let _ = attr.parse_nested_meta(|meta| {
79 if meta.path.is_ident("layout") {
80 if let Ok(value) = meta.value() {
81 if let Ok(s) = value.parse::<syn::LitStr>() {
82 soa = s.value() == "soa";
83 }
84 }
85 }
86 Ok(())
87 });
88 soa
89 });
90
91 let (component_impl, soa_layout_impl) = if layout_soa {
92 let named = match fields {
93 Fields::Named(n) => n,
94 _ => {
95 return TokenStream::from(quote! {
96 #component_impl
97 compile_error!("#[component(layout = \"soa\")] requires a struct with named fields");
98 });
99 }
100 };
101 let mut field_idents = Vec::new();
102 for f in &named.named {
103 let is_f32 = matches!(&f.ty, syn::Type::Path(p) if p.path.is_ident("f32"));
104 if !is_f32 {
105 return TokenStream::from(quote! {
106 #component_impl
107 compile_error!("#[component(layout = \"soa\")] (v1) supports only `f32` fields");
108 });
109 }
110 field_idents.push(f.ident.clone().unwrap());
111 }
112 let field_count = field_idents.len();
113 let field_names: Vec<String> = field_idents.iter().map(|i| i.to_string()).collect();
114 let field_idx: Vec<usize> = (0..field_count).collect();
115
116 let soa_layout = quote! {
117 impl #impl_generics crate::ecs::soa::SoaLayout for #name #ty_generics #where_clause {
118 const FIELD_COUNT: usize = #field_count;
119 const FIELD_NAMES: &'static [&'static str] = &[#(#field_names),*];
120 fn scatter_push(&self, fields: &mut [Vec<f32>]) {
121 #(fields[#field_idx].push(self.#field_idents);)*
122 }
123 fn scatter_set(&self, fields: &mut [Vec<f32>], row: usize) {
124 #(fields[#field_idx][row] = self.#field_idents;)*
125 }
126 fn gather(fields: &[Vec<f32>], row: usize) -> Self {
127 Self { #(#field_idents: fields[#field_idx][row]),* }
128 }
129 }
130 };
131 let comp_impl = quote! {
132 impl #impl_generics crate::ecs::component::Component for #name #ty_generics #where_clause {
133 fn make_column() -> Box<dyn crate::ecs::page::AnyVec> {
134 Box::new(crate::ecs::soa::FieldSoaColumn::<#name>::new())
135 }
136 fn push_into_column(self, column: &mut dyn crate::ecs::page::AnyVec) {
137 column
138 .as_any_mut()
139 .downcast_mut::<crate::ecs::soa::FieldSoaColumn<#name>>()
140 .expect("SoA column type mismatch")
141 .push(self);
142 }
143 fn copy_row_between(
144 src: &dyn crate::ecs::page::AnyVec,
145 src_row: usize,
146 dst: &mut dyn crate::ecs::page::AnyVec,
147 ) {
148 let value = src
149 .as_any()
150 .downcast_ref::<crate::ecs::soa::FieldSoaColumn<#name>>()
151 .expect("SoA column type mismatch")
152 .get(src_row);
153 dst.as_any_mut()
154 .downcast_mut::<crate::ecs::soa::FieldSoaColumn<#name>>()
155 .expect("SoA column type mismatch")
156 .push(value);
157 }
158 fn clone_from_column(column: &dyn crate::ecs::page::AnyVec, row: usize) -> Self {
159 column
160 .as_any()
161 .downcast_ref::<crate::ecs::soa::FieldSoaColumn<#name>>()
162 .expect("SoA column type mismatch")
163 .get(row)
164 }
165 fn set_in_column(self, column: &mut dyn crate::ecs::page::AnyVec, row: usize) {
166 column
167 .as_any_mut()
168 .downcast_mut::<crate::ecs::soa::FieldSoaColumn<#name>>()
169 .expect("SoA column type mismatch")
170 .set(row, self);
171 }
172 }
173 };
174 (comp_impl, soa_layout)
175 } else {
176 (component_impl, quote! {})
177 };
178
179 let no_serializable = input.attrs.iter().any(|attr| {
186 if !attr.path().is_ident("component") {
187 return false;
188 }
189 let mut no = false;
190 let _ = attr.parse_nested_meta(|meta| {
191 if meta.path.is_ident("no_serializable") {
192 no = true;
193 } else if meta.input.peek(syn::Token![=]) {
194 let _ = meta.value()?.parse::<syn::Expr>()?;
195 }
196 Ok(())
197 });
198 no
199 });
200
201 let (domain_ident, provenance_ident): (Option<syn::Ident>, syn::Ident) = {
218 let mut domain = None;
219 let mut provenance = None;
220 for attr in &input.attrs {
221 if !attr.path().is_ident("component") {
222 continue;
223 }
224 let _ = attr.parse_nested_meta(|meta| {
225 if meta.path.is_ident("domain") {
226 domain = Some(meta.value()?.parse::<syn::Ident>()?);
227 } else if meta.path.is_ident("provenance") {
228 provenance = Some(meta.value()?.parse::<syn::Ident>()?);
229 } else if meta.input.peek(syn::Token![=]) {
230 let _ = meta.value()?.parse::<syn::Expr>()?;
231 }
232 Ok(())
233 });
234 }
235 let provenance = provenance
236 .unwrap_or_else(|| syn::Ident::new("Authored", proc_macro::Span::call_site().into()));
237 (domain, provenance)
238 };
239
240 let domain_registration = match &domain_ident {
241 Some(domain) => quote! {
242 inventory::submit! {
243 crate::ecs::ComponentDomainRegistration {
244 register: |world: &mut crate::ecs::World| {
245 world.register_component::<#name>(crate::ecs::SemanticDomain::#domain);
246 }
247 }
248 }
249 },
250 None => quote! {},
251 };
252
253 if no_serializable {
254 return TokenStream::from(quote! {
255 #component_impl
256 #domain_registration
257 #soa_layout_impl
258 });
259 }
260
261 let mut included_fields = Vec::new();
263 let mut skipped_fields = Vec::new();
264
265 for field in fields.iter() {
266 let is_skip = field.attrs.iter().any(|attr| {
267 if !attr.path().is_ident("component") {
268 return false;
269 }
270 let mut skip = false;
271 let _ = attr.parse_nested_meta(|meta| {
272 if meta.path.is_ident("skip") {
273 skip = true;
274 }
275 Ok(())
276 });
277 skip
278 });
279
280 if is_skip {
281 skipped_fields.push(field);
282 } else {
283 included_fields.push(field);
284 }
285 }
286
287 let serializable_field_defs: Vec<_> = included_fields
289 .iter()
290 .map(|f| {
291 let fvis = &f.vis;
292 let fname = &f.ident;
293 let ftype = &f.ty;
294 if let Some(fname) = fname {
295 quote! { #fvis #fname: #ftype }
296 } else {
297 quote! { #fvis #ftype }
298 }
299 })
300 .collect();
301
302 let to_serializable_assigns: Vec<_> = included_fields
304 .iter()
305 .map(|f| {
306 let fname = &f.ident;
307 if fname.is_some() {
308 quote! { #fname: value.#fname }
309 } else {
310 quote! { value.#fname }
311 }
312 })
313 .collect();
314
315 let from_serializable_included: Vec<_> = included_fields
318 .iter()
319 .map(|f| {
320 let fname = &f.ident;
321 if fname.is_some() {
322 quote! { #fname: serializable.#fname }
323 } else {
324 quote! { serializable.#fname }
325 }
326 })
327 .collect();
328
329 let from_serializable_skipped: Vec<_> = skipped_fields
330 .iter()
331 .map(|f| {
332 let fname = &f.ident;
333 if fname.is_some() {
334 quote! { #fname: Default::default() }
335 } else {
336 quote! { Default::default() }
337 }
338 })
339 .collect();
340
341 let all_from_fields: Vec<_> = from_serializable_included
342 .into_iter()
343 .chain(from_serializable_skipped)
344 .collect();
345
346 let serializable_struct = match fields {
355 Fields::Named(_) if serializable_field_defs.is_empty() => {
356 quote! {
358 #[derive(Debug, Clone, serde::Serialize, serde::Deserialize, bincode::Encode, bincode::Decode)]
359 #[allow(missing_docs)]
360 #[doc(hidden)]
361 #vis struct #serializable_name;
362 }
363 }
364 Fields::Named(_) => {
365 quote! {
366 #[derive(Debug, Clone, serde::Serialize, serde::Deserialize, bincode::Encode, bincode::Decode)]
367 #[allow(missing_docs)]
368 #[doc(hidden)]
369 #vis struct #serializable_name {
370 #(#serializable_field_defs),*
371 }
372 }
373 }
374 Fields::Unnamed(_) => {
375 let field_types: Vec<_> = included_fields.iter().map(|f| &f.ty).collect();
376 let field_vis: Vec<_> = included_fields.iter().map(|f| &f.vis).collect();
377 quote! {
378 #[derive(Debug, Clone, serde::Serialize, serde::Deserialize, bincode::Encode, bincode::Decode)]
379 #[allow(missing_docs)]
380 #[doc(hidden)]
381 #vis struct #serializable_name(#(#field_vis #field_types),*);
382 }
383 }
384 Fields::Unit => {
385 quote! {
386 #[derive(Debug, Clone, serde::Serialize, serde::Deserialize, bincode::Encode, bincode::Decode)]
387 #[allow(missing_docs)]
388 #[doc(hidden)]
389 #vis struct #serializable_name;
390 }
391 }
392 };
393
394 let from_original_to_serializable = if matches!(fields, Fields::Named(_))
396 && !serializable_field_defs.is_empty()
397 {
398 quote! {
399 impl From<#name> for #serializable_name {
400 fn from(value: #name) -> Self {
401 Self {
402 #(#to_serializable_assigns),*
403 }
404 }
405 }
406 }
407 } else if matches!(fields, Fields::Unnamed(_)) {
408 let indices: Vec<syn::Index> = (0..included_fields.len()).map(syn::Index::from).collect();
409 quote! {
410 impl From<#name> for #serializable_name {
411 fn from(value: #name) -> Self {
412 Self(#(value.#indices),*)
413 }
414 }
415 }
416 } else {
417 quote! {}
418 };
419
420 let from_serializable_to_original = if matches!(fields, Fields::Named(_))
421 && !serializable_field_defs.is_empty()
422 {
423 quote! {
424 impl From<#serializable_name> for #name {
425 fn from(serializable: #serializable_name) -> Self {
426 Self {
427 #(#all_from_fields),*
428 }
429 }
430 }
431 }
432 } else if matches!(fields, Fields::Unnamed(_)) {
433 let indices: Vec<syn::Index> = (0..included_fields.len()).map(syn::Index::from).collect();
434 quote! {
435 impl From<#serializable_name> for #name {
436 fn from(serializable: #serializable_name) -> Self {
437 Self(#(serializable.#indices),*)
438 }
439 }
440 }
441 } else {
442 quote! {}
443 };
444
445 let expanded = quote! {
446 #component_impl
447 #domain_registration
448 #soa_layout_impl
449 #serializable_struct
450 #from_original_to_serializable
451 #from_serializable_to_original
452
453 inventory::submit! {
459 crate::scene::ComponentRegistration {
460 type_id: std::any::TypeId::of::<#name>(),
461 type_name: stringify!(#name),
462 provenance: crate::ecs::ComponentProvenance::#provenance_ident,
463 serialize_recipe: |world, entity| {
464 world.clone_component::<#name>(entity).map(|c| {
466 bincode::encode_to_vec(&<#serializable_name>::from(c), bincode::config::standard())
467 .unwrap_or_default()
468 })
469 },
470 deserialize_recipe: |world, entity, data| {
471 let (s, _): (#serializable_name, _) = bincode::decode_from_slice_with_context(
472 data, bincode::config::standard(), ()
473 ).map_err(|e| e.to_string())?;
474 world.add_component(entity, <#name>::from(s)).ok();
475 Ok(())
476 },
477 create_default: |world, entity| {
478 world.add_component(entity, <#name>::default()).ok();
479 Ok(())
480 },
481 to_json: |world, entity| {
482 world.clone_component::<#name>(entity).and_then(|c| {
483 serde_json::to_value(<#serializable_name>::from(c)).ok()
484 })
485 },
486 from_json: |world, entity, value| {
487 let s: #serializable_name = serde_json::from_value(value.clone())
488 .map_err(|e| e.to_string())?;
489 let new_value = <#name>::from(s);
490 if !world.set_component(entity, new_value.clone()) {
492 world.add_component(entity, new_value)
493 .map_err(|e| format!("{:?}", e))?;
494 }
495 Ok(())
496 },
497 remove: |world, entity| {
498 match world.remove_component::<#name>(entity) {
501 Ok(_) => Ok(()),
502 Err(e) => Err(format!("{:?}", e)),
503 }
504 },
505 }
506 }
507 };
508
509 TokenStream::from(expanded)
510}