1use proc_macro::TokenStream;
5
6use quote::{format_ident, quote};
7use syn::punctuated::Punctuated;
8use syn::{
9 Attribute, Data, DeriveInput, Expr, Fields, Ident, LitStr, Path, Token, parse_macro_input,
10 parse_quote,
11};
12
13const BLOCK: usize = 16;
16
17const PART: Named = Named {
19 trait_name: "Part",
20 what: "part",
21};
22
23const CLIP: Named = Named {
25 trait_name: "Clip",
26 what: "clip",
27};
28
29#[proc_macro_derive(Catalog, attributes(catalog))]
41pub fn derive_catalog(input: TokenStream) -> TokenStream {
42 let input = parse_macro_input!(input as DeriveInput);
43 match catalog_impl(&input) {
44 Ok(implementation) => implementation,
45 Err(error) => error.to_compile_error().into(),
46 }
47}
48
49fn catalog_impl(input: &DeriveInput) -> syn::Result<TokenStream> {
50 let name = &input.ident;
51 let values = match &input.data {
52 Data::Enum(data) => data
53 .variants
54 .iter()
55 .map(|variant| {
56 let variant_name = &variant.ident;
57 cataloged(
58 &parse_quote!(#name::#variant_name),
59 &variant.fields,
60 &variant.attrs,
61 variant_name,
62 )
63 })
64 .collect::<syn::Result<Vec<_>>>()?
65 .concat(),
66 Data::Struct(data) => cataloged(&parse_quote!(#name), &data.fields, &input.attrs, name)?,
67 Data::Union(_) => {
68 return Err(syn::Error::new_spanned(
69 name,
70 "Catalog covers enums and structs; a union needs the impl written by hand",
71 ));
72 }
73 };
74
75 let (impl_generics, type_generics, where_clause) = input.generics.split_for_impl();
76 Ok(quote! {
77 impl #impl_generics ::mirage_engine::Catalog for #name #type_generics #where_clause {
78 fn catalog() -> ::std::vec::Vec<Self> {
79 ::std::vec![#(#values),*]
80 }
81 }
82 }
83 .into())
84}
85
86fn cataloged(
89 path: &Path,
90 fields: &Fields,
91 attributes: &[Attribute],
92 name: &Ident,
93) -> syn::Result<Vec<Expr>> {
94 let mut declared = attributes
95 .iter()
96 .filter(|attribute| attribute.path().is_ident("catalog"));
97 let Some(attribute) = declared.next() else {
98 return match fields {
99 Fields::Unit => Ok(vec![parse_quote!(#path)]),
100 _ => Err(syn::Error::new_spanned(
101 name,
102 format!(
103 "`{name}` has fields, so each value of it is a mesh of its own: name a value \
104 of the type in `#[catalog({}, …)]`",
105 representative(path, fields)
106 ),
107 )),
108 };
109 };
110 if let Some(extra) = declared.next() {
111 return Err(syn::Error::new_spanned(
112 extra,
113 format!(
114 "`{name}` names its values in one `#[catalog(…)]`; drop the attribute past the \
115 first"
116 ),
117 ));
118 }
119 if matches!(fields, Fields::Unit) {
120 return Err(syn::Error::new_spanned(
121 attribute,
122 format!("`{name}` has no fields, so it catalogs itself; drop the attribute"),
123 ));
124 }
125
126 let values: Vec<Expr> = attribute
127 .parse_args_with(Punctuated::<Expr, Token![,]>::parse_terminated)?
128 .into_iter()
129 .collect();
130 match values.is_empty() {
131 true => Err(syn::Error::new_spanned(
132 attribute,
133 format!(
134 "`{name}` names no value; give the attribute a value of the type, `{}`",
135 representative(path, fields)
136 ),
137 )),
138 false => Ok(values),
139 }
140}
141
142fn representative(path: &Path, fields: &Fields) -> String {
145 let spelled = path
146 .segments
147 .iter()
148 .map(|segment| segment.ident.to_string())
149 .collect::<Vec<_>>()
150 .join("::");
151 let each: Vec<String> = fields
152 .iter()
153 .map(|field| match &field.ident {
154 Some(field) => format!("{field}: …"),
155 None => "…".to_owned(),
156 })
157 .collect();
158 match fields {
159 Fields::Named(_) => format!("{spelled} {{ {} }}", each.join(", ")),
160 _ => format!("{spelled}({})", each.join(", ")),
161 }
162}
163
164#[proc_macro_derive(Part, attributes(part))]
172pub fn derive_part(input: TokenStream) -> TokenStream {
173 let input = parse_macro_input!(input as DeriveInput);
174 match named_impl(&input, &PART) {
175 Ok(implementation) => implementation,
176 Err(error) => error.to_compile_error().into(),
177 }
178}
179
180#[proc_macro_derive(Clip, attributes(clip))]
188pub fn derive_clip(input: TokenStream) -> TokenStream {
189 let input = parse_macro_input!(input as DeriveInput);
190 match named_impl(&input, &CLIP) {
191 Ok(implementation) => implementation,
192 Err(error) => error.to_compile_error().into(),
193 }
194}
195
196struct Named {
199 trait_name: &'static str,
200 what: &'static str,
201}
202
203fn named_impl(input: &DeriveInput, named: &Named) -> syn::Result<TokenStream> {
204 let name = &input.ident;
205 let parts = match &input.data {
206 Data::Enum(data) => data
207 .variants
208 .iter()
209 .map(|variant| {
210 let variant_name = &variant.ident;
211 spellings(
212 &parse_quote!(#name::#variant_name),
213 &variant.fields,
214 &variant.attrs,
215 variant_name,
216 named,
217 )
218 })
219 .collect::<syn::Result<Vec<_>>>()?,
220 Data::Struct(data) => vec![spellings(
221 &parse_quote!(#name),
222 &data.fields,
223 &input.attrs,
224 name,
225 named,
226 )?],
227 Data::Union(_) => {
228 let trait_name = named.trait_name;
229 return Err(syn::Error::new_spanned(
230 name,
231 format!(
232 "{trait_name} covers enums and unit structs; a union needs the impl written \
233 by hand"
234 ),
235 ));
236 }
237 };
238
239 let every = parts.iter().map(|(key, _)| key);
240 let indices = 0u32..parts.len() as u32;
241 let indexed = parts.iter().map(|(key, _)| key);
242 let (modelled, keys): (Vec<&LitStr>, Vec<&Expr>) = parts
243 .iter()
244 .flat_map(|(key, names)| names.iter().map(move |spelling| (spelling, key)))
245 .unzip();
246
247 let trait_name = format_ident!("{}", named.trait_name);
248 let (impl_generics, type_generics, where_clause) = input.generics.split_for_impl();
249 Ok(quote! {
250 impl #impl_generics ::mirage_engine::#trait_name for #name #type_generics #where_clause {
251 fn from_name(name: &str) -> ::core::option::Option<Self> {
252 match name {
253 #(#modelled => ::core::option::Option::Some(#keys),)*
254 _ => ::core::option::Option::None,
255 }
256 }
257
258 fn all() -> ::std::vec::Vec<Self> {
259 ::std::vec![#(#every),*]
260 }
261
262 fn index(&self) -> u32 {
263 match self {
264 #(#indexed => #indices,)*
265 }
266 }
267 }
268 }
269 .into())
270}
271
272fn spellings(
276 path: &Path,
277 fields: &Fields,
278 attributes: &[Attribute],
279 name: &Ident,
280 named: &Named,
281) -> syn::Result<(Expr, Vec<LitStr>)> {
282 let what = named.what;
283 if !matches!(fields, Fields::Unit) {
284 return Err(syn::Error::new_spanned(
285 name,
286 format!("`{name}` has fields, but a {what} is a plain name; give it none"),
287 ));
288 }
289
290 let declared: Vec<LitStr> = attributes
291 .iter()
292 .filter(|attribute| attribute.path().is_ident(what))
293 .map(Attribute::parse_args)
294 .collect::<syn::Result<_>>()?;
295
296 let names = match declared.is_empty() {
297 true => vec![LitStr::new(&name.to_string(), name.span())],
298 false => declared,
299 };
300 Ok((parse_quote!(#path), names))
301}
302
303#[proc_macro_derive(InputButtonAction)]
311pub fn derive_input_button_action(input: TokenStream) -> TokenStream {
312 let input = parse_macro_input!(input as DeriveInput);
313 emit(
314 &input,
315 &parse_quote!(::mirage_engine::ButtonBinding),
316 &parse_quote!(::mirage_engine::InputButtonAction),
317 "NoInputButtons",
318 )
319}
320
321#[proc_macro_derive(InputAxisAction)]
325pub fn derive_input_axis_action(input: TokenStream) -> TokenStream {
326 let input = parse_macro_input!(input as DeriveInput);
327 emit(
328 &input,
329 &parse_quote!(::mirage_engine::AxisBinding),
330 &parse_quote!(::mirage_engine::InputAxisAction),
331 "NoInputAxes",
332 )
333}
334
335#[proc_macro_derive(InputAxis2Action)]
340pub fn derive_input_axis2_action(input: TokenStream) -> TokenStream {
341 let input = parse_macro_input!(input as DeriveInput);
342 emit(
343 &input,
344 &parse_quote!(::mirage_engine::Axis2Binding),
345 &parse_quote!(::mirage_engine::InputAxis2Action),
346 "NoInputAxes2",
347 )
348}
349
350fn emit(input: &DeriveInput, binding: &Path, kind: &Path, empty: &str) -> TokenStream {
351 match actions(input, binding, kind, empty) {
352 Ok(implementation) => implementation,
353 Err(error) => error.to_compile_error().into(),
354 }
355}
356
357fn actions(
358 input: &DeriveInput,
359 binding: &Path,
360 kind: &Path,
361 empty: &str,
362) -> syn::Result<TokenStream> {
363 let name = &input.ident;
364 let verbs: Vec<(Path, &Ident)> = match &input.data {
365 Data::Enum(data) => data
366 .variants
367 .iter()
368 .map(|variant| {
369 let variant_name = &variant.ident;
370 verb(
371 parse_quote!(#name::#variant_name),
372 &variant.fields,
373 variant_name,
374 )
375 })
376 .collect::<syn::Result<_>>()?,
377 Data::Struct(data) => vec![verb(parse_quote!(#name), &data.fields, name)?],
378 Data::Union(_) => {
379 return Err(syn::Error::new_spanned(
380 name,
381 "an action vocabulary is an enum or a unit struct; a union needs the impl \
382 written by hand",
383 ));
384 }
385 };
386 if verbs.is_empty() {
387 return Err(syn::Error::new_spanned(
388 name,
389 format!("`{name}` names no action; the vocabulary of none is `mirage_engine::{empty}`"),
390 ));
391 }
392
393 let (paths, idents): (Vec<&Path>, Vec<&Ident>) =
394 verbs.iter().map(|(path, ident)| (path, *ident)).unzip();
395 let (impl_generics, type_generics, where_clause) = input.generics.split_for_impl();
396 Ok(quote! {
397 impl #impl_generics ::mirage_engine::InputAction for #name #type_generics #where_clause {
398 type Binding = #binding;
399
400 fn defaults(&self) -> ::std::vec::Vec<#binding> {
401 <Self as #kind>::bindings(self)
402 }
403
404 fn all() -> ::std::vec::Vec<Self> {
405 ::std::vec![#(#paths),*]
406 }
407
408 fn name(&self) -> &'static str {
409 match self {
410 #(#paths => ::core::stringify!(#idents),)*
411 }
412 }
413
414 fn from_name(name: &str) -> ::core::option::Option<Self> {
415 match name {
416 #(::core::stringify!(#idents) => ::core::option::Option::Some(#paths),)*
417 _ => ::core::option::Option::None,
418 }
419 }
420 }
421 }
422 .into())
423}
424
425fn verb<'a>(path: Path, fields: &Fields, name: &'a Ident) -> syn::Result<(Path, &'a Ident)> {
427 fieldless(
428 fields,
429 name,
430 "an action is a plain verb; move what varies into the game's own state",
431 )?;
432 Ok((path, name))
433}
434
435#[proc_macro_derive(Saves)]
443pub fn derive_saves(input: TokenStream) -> TokenStream {
444 let input = parse_macro_input!(input as DeriveInput);
445 match saves(&input) {
446 Ok(implementation) => implementation,
447 Err(error) => error.to_compile_error().into(),
448 }
449}
450
451fn saves(input: &DeriveInput) -> syn::Result<TokenStream> {
452 let name = &input.ident;
453 let keys: Vec<(Path, LitStr)> = match &input.data {
454 Data::Enum(data) => data
455 .variants
456 .iter()
457 .map(|variant| {
458 let variant_name = &variant.ident;
459 kept(
460 parse_quote!(#name::#variant_name),
461 &variant.fields,
462 variant_name,
463 &format!("{name}.{variant_name}"),
464 )
465 })
466 .collect::<syn::Result<_>>()?,
467 Data::Struct(data) => vec![kept(
468 parse_quote!(#name),
469 &data.fields,
470 name,
471 &name.to_string(),
472 )?],
473 Data::Union(_) => {
474 return Err(syn::Error::new_spanned(
475 name,
476 "a save vocabulary is an enum or a unit struct; a union needs the impl written \
477 by hand",
478 ));
479 }
480 };
481
482 let (paths, names): (Vec<&Path>, Vec<&LitStr>) =
483 keys.iter().map(|(path, name)| (path, name)).unzip();
484 let (impl_generics, type_generics, where_clause) = input.generics.split_for_impl();
485 Ok(quote! {
486 impl #impl_generics ::mirage_engine::Saves for #name #type_generics #where_clause {
487 fn name(&self) -> &'static str {
488 match *self {
489 #(#paths => #names,)*
490 }
491 }
492 }
493 }
494 .into())
495}
496
497fn kept(path: Path, fields: &Fields, name: &Ident, under: &str) -> syn::Result<(Path, LitStr)> {
499 fieldless(
500 fields,
501 name,
502 "a save key is a plain name; move what varies into what it keeps",
503 )?;
504 Ok((path, LitStr::new(under, name.span())))
505}
506
507fn fieldless(fields: &Fields, name: &Ident, complaint: &str) -> syn::Result<()> {
509 match fields {
510 Fields::Unit => Ok(()),
511 _ => Err(syn::Error::new_spanned(
512 name,
513 format!("`{name}` has fields, but {complaint}"),
514 )),
515 }
516}
517
518#[proc_macro_derive(ShaderValues)]
537pub fn derive_shader_values(input: TokenStream) -> TokenStream {
538 let input = parse_macro_input!(input as DeriveInput);
539 match shader_values(&input) {
540 Ok(implementation) => implementation,
541 Err(error) => error.to_compile_error().into(),
542 }
543}
544
545fn shader_values(input: &DeriveInput) -> syn::Result<TokenStream> {
546 let name = &input.ident;
547 let Data::Struct(data) = &input.data else {
548 return Err(syn::Error::new_spanned(
549 name,
550 "values a shader reads are a struct of the fields it reads",
551 ));
552 };
553 let read = match &data.fields {
554 Fields::Named(fields) => fields.named.iter().collect(),
555 Fields::Unit => Vec::new(),
556 Fields::Unnamed(_) => {
557 return Err(syn::Error::new_spanned(
558 name,
559 "values a shader reads are named, so that it reads them by name",
560 ));
561 }
562 };
563
564 let layout = Layout::of(name, &read)?;
565 let declaration = &layout.declaration;
566 let size = layout.size;
567 let written = layout
568 .placed
569 .iter()
570 .map(|placed| placed.lane.written(&placed.field, placed.offset));
571 let (impl_generics, type_generics, where_clause) = input.generics.split_for_impl();
572 Ok(quote! {
573 impl #impl_generics ::mirage_engine::Sealed for #name #type_generics #where_clause {}
574
575 impl #impl_generics ::mirage_engine::ShaderValues for #name #type_generics #where_clause {
576 const TYPE: &'static str = ::core::stringify!(#name);
577 const DECLARATION: &'static str = #declaration;
578
579 fn write(&self, into: &mut ::std::vec::Vec<u8>) {
580 let start = into.len();
581 #(#written)*
582 into.resize(start + #size, 0);
583 }
584 }
585 }
586 .into())
587}
588
589struct Layout {
593 declaration: String,
594 placed: Vec<Placed>,
595 size: usize,
596}
597
598impl Layout {
599 fn of(name: &Ident, read: &[&syn::Field]) -> syn::Result<Self> {
601 if read.is_empty() {
602 return Ok(Self {
603 declaration: String::new(),
604 placed: Vec::new(),
605 size: 0,
606 });
607 }
608
609 let mut declaration = format!("struct {name} {{\n");
610 let mut placed = Vec::new();
611 let mut offset = 0usize;
612 for read in read {
613 let Some(field) = read.ident.clone() else {
614 continue;
615 };
616 let lane = Lane::of(&read.ty)?;
617 offset = lane.aligned(offset);
618 declaration.push_str(&format!(" {field}: {},\n", lane.wgsl()));
619 placed.push(Placed {
620 lane,
621 field,
622 offset,
623 });
624 offset += lane.size();
625 }
626 declaration.push('}');
627 Ok(Self {
628 declaration,
629 placed,
630 size: offset.next_multiple_of(BLOCK),
631 })
632 }
633}
634
635struct Placed {
638 lane: Lane,
639 field: Ident,
640 offset: usize,
641}
642
643#[derive(Clone, Copy)]
645enum Lane {
646 Number,
647 Count,
648 Vec2,
649 Vec3,
650 Vec4,
651 Mat4,
652 Color,
653}
654
655impl Lane {
656 fn of(ty: &syn::Type) -> syn::Result<Self> {
659 let syn::Type::Path(path) = ty else {
660 return Err(Self::unread(ty));
661 };
662 match path.path.segments.last() {
663 Some(segment) => match segment.ident.to_string().as_str() {
664 "f32" => Ok(Self::Number),
665 "u32" => Ok(Self::Count),
666 "Vec2" => Ok(Self::Vec2),
667 "Vec3" => Ok(Self::Vec3),
668 "Vec4" => Ok(Self::Vec4),
669 "Mat4" => Ok(Self::Mat4),
670 "Color" => Ok(Self::Color),
671 _ => Err(Self::unread(ty)),
672 },
673 None => Err(Self::unread(ty)),
674 }
675 }
676
677 fn unread(ty: &syn::Type) -> syn::Error {
678 syn::Error::new_spanned(
679 ty,
680 "a shader reads `f32`, `u32`, `Vec2`, `Vec3`, `Vec4`, `Mat4` and `Color`, and \
681 nothing else",
682 )
683 }
684
685 fn wgsl(self) -> &'static str {
687 match self {
688 Self::Number => "f32",
689 Self::Count => "u32",
690 Self::Vec2 => "vec2<f32>",
691 Self::Vec3 => "vec3<f32>",
692 Self::Vec4 | Self::Color => "vec4<f32>",
693 Self::Mat4 => "mat4x4<f32>",
694 }
695 }
696
697 fn size(self) -> usize {
699 match self {
700 Self::Number | Self::Count => 4,
701 Self::Vec2 => 8,
702 Self::Vec3 => 12,
703 Self::Vec4 | Self::Color => 16,
704 Self::Mat4 => 64,
705 }
706 }
707
708 fn aligned(self, offset: usize) -> usize {
710 let align = match self {
711 Self::Number | Self::Count => 4,
712 Self::Vec2 => 8,
713 Self::Vec3 | Self::Vec4 | Self::Color | Self::Mat4 => BLOCK,
714 };
715 offset.next_multiple_of(align)
716 }
717
718 fn written(self, field: &Ident, offset: usize) -> impl quote::ToTokens {
720 let numbers = match self {
721 Self::Number | Self::Count => vec![quote!(self.#field)],
722 Self::Vec2 => vec![quote!(self.#field.x), quote!(self.#field.y)],
723 Self::Vec3 => vec![
724 quote!(self.#field.x),
725 quote!(self.#field.y),
726 quote!(self.#field.z),
727 ],
728 Self::Vec4 => vec![
729 quote!(self.#field.x),
730 quote!(self.#field.y),
731 quote!(self.#field.z),
732 quote!(self.#field.w),
733 ],
734 Self::Color => vec![
735 quote!(self.#field.red),
736 quote!(self.#field.green),
737 quote!(self.#field.blue),
738 quote!(self.#field.alpha),
739 ],
740 Self::Mat4 => {
741 return quote! {
742 into.resize(start + #offset, 0);
743 for number in self.#field.to_cols_array() {
744 into.extend_from_slice(&number.to_le_bytes());
745 }
746 };
747 }
748 };
749
750 quote! {
751 into.resize(start + #offset, 0);
752 #(into.extend_from_slice(&#numbers.to_le_bytes());)*
753 }
754 }
755}