diff options
Diffstat (limited to 'engine-macros/src/lib.rs')
| -rw-r--r-- | engine-macros/src/lib.rs | 142 |
1 files changed, 142 insertions, 0 deletions
diff --git a/engine-macros/src/lib.rs b/engine-macros/src/lib.rs new file mode 100644 index 0000000..75ff209 --- /dev/null +++ b/engine-macros/src/lib.rs @@ -0,0 +1,142 @@ +#![deny(clippy::all, clippy::pedantic)] + +use proc_macro::TokenStream; +use quote::{ToTokens, format_ident, quote}; +use syn::{Item, LitStr, Path as SynPath, parse}; + +macro_rules! syn_path { + ($first_segment: ident $(::$segment: ident)*) => { + ::syn::Path { + leading_colon: None, + segments: ::syn::punctuated::Punctuated::from_iter([ + syn_path_segment!($first_segment), + $(syn_path_segment!($segment),)* + ]) + } + }; +} + +macro_rules! syn_path_segment { + ($segment: ident) => { + ::syn::PathSegment { + ident: ::proc_macro2::Ident::new( + stringify!($segment), + ::proc_macro2::Span::call_site(), + ), + arguments: ::syn::PathArguments::None, + } + }; +} + +#[proc_macro_derive(Reflection)] +pub fn reflection_derive(input: TokenStream) -> TokenStream +{ + let input = parse::<Item>(input).unwrap(); + + let input = match input { + Item::Struct(input) => input, + Item::Enum(_) => unimplemented!(), + _ => panic!("Invalid input"), + }; + + let engine_crate_path = find_engine_crate_path().unwrap(); + + let input_ident = input.ident; + + let (impl_generics, type_generics, where_clause) = input.generics.split_for_impl(); + + let fields = input.fields.into_iter().enumerate().map(|(index, field)| { + let field_ident = field.ident.unwrap_or_else(|| format_ident!("{index}")); + + let field_type = &field.ty; + + let field_name = LitStr::new(&field_ident.to_string(), field_ident.span()); + + // since std::any::type_name as const is not stable yet + let field_type_name = field_type.to_token_stream().to_string(); + + quote! { + #engine_crate_path::reflection::StructField { + name: #field_name, + index: #index, + layout: std::alloc::Layout::new::<#field_type>(), + byte_offset: std::mem::offset_of!(Self, #field_ident), + type_id: std::any::TypeId::of::<#field_type>(), + type_name: #field_type_name, + get_type: #engine_crate_path::reflection::FnWithDebug::new(|| { + struct SpecializationTarget<Field>(std::marker::PhantomData<Field>); + + trait FieldHasReflection + { + fn field_type_reflection(&self) + -> Option<&'static #engine_crate_path::reflection::Type>; + } + + trait FieldDoesNotHaveReflection + { + fn field_type_reflection(&self) + -> Option<&'static #engine_crate_path::reflection::Type>; + } + + impl<Field> FieldDoesNotHaveReflection for &SpecializationTarget<Field> + { + fn field_type_reflection(&self) + -> Option<&'static #engine_crate_path::reflection::Type> + { + None + } + } + + impl<Field> FieldHasReflection for SpecializationTarget<Field> + where + Field: #engine_crate_path::reflection::Reflection + { + fn field_type_reflection(&self) + -> Option<&'static #engine_crate_path::reflection::Type> + { + Some(Field::type_reflection()) + } + } + + (&SpecializationTarget::<#field_type>(std::marker::PhantomData)) + .field_type_reflection() + }) + } + } + }); + + quote! { + unsafe impl #impl_generics #engine_crate_path::reflection::Reflection for + #input_ident #type_generics #where_clause + { + const TYPE_REFLECTION: &#engine_crate_path::reflection::Type = + &const { + #engine_crate_path::reflection::Type::Struct( + #engine_crate_path::reflection::Struct { + fields: &[ + #(#fields),* + ] + } + ) + }; + } + } + .into() +} + +fn find_engine_crate_path() -> Option<SynPath> +{ + let cargo_crate_name = std::env::var("CARGO_CRATE_NAME").ok()?; + let cargo_pkg_name = std::env::var("CARGO_PKG_NAME").ok()?; + + if cargo_pkg_name == "engine" && cargo_crate_name != "engine" { + // Macro is used by a crate example/test/benchmark + return Some(syn_path!(engine)); + } + + if cargo_crate_name == "engine" { + return Some(syn_path!(crate)); + } + + Some(syn_path!(engine)) +} |
