/ macros / src / derive_vertex.rs
derive_vertex.rs
  1  use crate::bail;
  2  use proc_macro2::{Span, TokenStream};
  3  use quote::quote;
  4  use syn::{
  5      Data, DataStruct, Fields, Ident, LitStr, Result, Token,
  6      parse::{Parse, ParseStream},
  7      punctuated::Punctuated,
  8  };
  9  
 10  pub fn derive_vertex(crate_ident: &Ident, ast: syn::DeriveInput) -> Result<TokenStream> {
 11      let struct_name = &ast.ident;
 12  
 13      let fields = match &ast.data {
 14          Data::Struct(DataStruct {
 15              fields: Fields::Named(fields),
 16              ..
 17          }) => &fields.named,
 18          _ => bail!("expected a struct with named fields"),
 19      };
 20  
 21      let mut members = quote! {
 22          let mut offset = 0;
 23          let mut members = ::std::collections::HashMap::default();
 24      };
 25  
 26      for field in fields.iter() {
 27          let field_name = field.ident.to_owned().unwrap();
 28          let field_name_lit = LitStr::new(&field_name.to_string(), Span::call_site());
 29          let field_ty = &field.ty;
 30          let mut names = vec![field_name_lit.clone()];
 31          let mut format = quote! {};
 32          for attr in &field.attrs {
 33              let attr_ident = if let Some(ident) = attr.path().get_ident() {
 34                  ident
 35              } else {
 36                  continue;
 37              };
 38              if attr_ident == "name" {
 39                  let meta = attr.parse_args_with(NameMeta::parse)?;
 40                  names = meta.lit_str_list.into_iter().collect();
 41              } else if attr_ident == "format" {
 42                  let format_ident = attr.parse_args_with(Ident::parse)?;
 43                  format = quote! {
 44                      ::#crate_ident::resources::Format::#format_ident;
 45                  };
 46              }
 47          }
 48          if format.is_empty() {
 49              bail!(
 50                  field_name,
 51                  "expected `#[format(...)]`-attribute with valid `let_engine_core::resources::Format`",
 52              );
 53          }
 54          for name in &names {
 55              members = quote! {
 56                  #members
 57  
 58                  {
 59                      let field_align = ::std::mem::align_of::<#field_ty>();
 60                      offset = (offset + field_align - 1) & !(field_align - 1);
 61  
 62                      let field_size = ::std::mem::size_of::<#field_ty>();
 63                      let format = #format;
 64                      let format_size = usize::try_from(format.block_size()).unwrap();
 65                      let num_elements = field_size / format_size;
 66                      let remainder = field_size % format_size;
 67                      ::std::assert!(
 68                          remainder == 0,
 69                          "struct field `{}` size does not fit multiple of format size",
 70                          #field_name_lit,
 71                      );
 72  
 73                      members.insert(
 74                          #name.to_string(),
 75                          ::#crate_ident::resources::model::VertexMemberInfo {
 76                              offset: offset.try_into().unwrap(),
 77                              format,
 78                              num_elements: num_elements.try_into().unwrap(),
 79                              stride: format_size.try_into().unwrap(),
 80                          },
 81                      );
 82  
 83                      offset += field_size;
 84                  }
 85              };
 86          }
 87      }
 88  
 89      let function_body = quote! {
 90          #members
 91  
 92          ::#crate_ident::resources::model::VertexBufferDescription {
 93              members,
 94              stride: ::std::mem::size_of::<#struct_name>() as u32,
 95          }
 96      };
 97  
 98      Ok(quote! {
 99          #[allow(unsafe_code)]
100          unsafe impl ::#crate_ident::resources::model::Vertex for #struct_name {
101              #[inline(always)]
102              fn description() -> ::#crate_ident::resources::model::VertexBufferDescription {
103                  #function_body
104              }
105  
106          }
107      })
108  }
109  
110  struct NameMeta {
111      lit_str_list: Punctuated<LitStr, Token![,]>,
112  }
113  
114  impl Parse for NameMeta {
115      fn parse(input: ParseStream) -> Result<Self> {
116          Ok(Self {
117              lit_str_list: input.parse_terminated(<LitStr as Parse>::parse, Token![,])?,
118          })
119      }
120  }