diff options
| -rw-r--r-- | Cargo.lock | 10 | ||||
| -rw-r--r-- | Cargo.toml | 2 | ||||
| -rw-r--r-- | engine-macros/Cargo.toml | 13 | ||||
| -rw-r--r-- | engine-macros/src/lib.rs | 174 | ||||
| -rw-r--r-- | engine/Cargo.toml | 1 | ||||
| -rw-r--r-- | engine/src/lib.rs | 1 | ||||
| -rw-r--r-- | engine/src/reflection.rs | 147 |
7 files changed, 347 insertions, 1 deletions
@@ -524,6 +524,7 @@ dependencies = [ "cfg_aliases", "crossbeam-channel", "ecs", + "engine-macros", "glutin", "image", "nu-ansi-term", @@ -540,6 +541,15 @@ dependencies = [ ] [[package]] +name = "engine-macros" +version = "0.1.0" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.100", +] + +[[package]] name = "equivalent" version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -4,7 +4,7 @@ version = "0.1.0" edition = "2021" [workspace] -members = ["engine", "ecs", "ecs-macros", "util-macros", "opengl-bindings"] +members = ["engine", "engine-macros", "ecs", "ecs-macros", "util-macros", "opengl-bindings"] [dependencies] engine = { path = "./engine" } diff --git a/engine-macros/Cargo.toml b/engine-macros/Cargo.toml new file mode 100644 index 0000000..7470567 --- /dev/null +++ b/engine-macros/Cargo.toml @@ -0,0 +1,13 @@ +[package] +name = "engine-macros" +version = "0.1.0" +edition = "2024" + +[lib] +proc-macro = true + +[dependencies] +quote = "1.0.35" +syn = { version = "2.0.51", features = ["full"] } +proc-macro2 = "1.0.78" + diff --git a/engine-macros/src/lib.rs b/engine-macros/src/lib.rs new file mode 100644 index 0000000..ad6c15f --- /dev/null +++ b/engine-macros/src/lib.rs @@ -0,0 +1,174 @@ +#![deny(clippy::all, clippy::pedantic)] + +use proc_macro::TokenStream; +use quote::{ToTokens, format_ident, quote}; +use syn::punctuated::Punctuated; +use syn::{ + Item, + LitStr, + Path as SynPath, + PredicateType, + Token, + TraitBound, + TypeParamBound, + WhereClause, + WherePredicate, + 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 mut where_clause = where_clause.cloned().unwrap_or_else(|| WhereClause { + where_token: <Token![where]>::default(), + predicates: Punctuated::new(), + }); + + where_clause + .predicates + .extend(input.fields.iter().map(|field| { + WherePredicate::Type(PredicateType { + lifetimes: None, + bounded_ty: field.ty.clone(), + colon_token: <Token![:]>::default(), + bounds: [TypeParamBound::Trait(TraitBound { + paren_token: None, + modifier: syn::TraitBoundModifier::None, + lifetimes: None, + path: engine_crate_path.join(syn_path!(reflection::With)), + })] + .into_iter() + .collect(), + }) + })); + + 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, + reflection: + #engine_crate_path::reflection::__private::get_type_reflection::< + #field_type + >() + } + } + }); + + quote! { + impl #impl_generics #engine_crate_path::reflection::With for + #input_ident #type_generics #where_clause + { + const REFLECTION: &#engine_crate_path::reflection::Reflection = + &const { + #engine_crate_path::reflection::Reflection::Struct( + #engine_crate_path::reflection::Struct { + fields: &[ + #(#fields),* + ] + } + ) + }; + + fn reflection() -> &'static #engine_crate_path::reflection::Reflection + { + Self::REFLECTION + } + + fn get_reflection(&self) -> &'static #engine_crate_path::reflection::Reflection + { + Self::reflection() + } + } + } + .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)) +} + +trait SynPathExt +{ + fn join(&self, other: Self) -> Self; +} + +impl SynPathExt for SynPath +{ + fn join(&self, other: Self) -> Self + { + Self { + leading_colon: self.leading_colon.clone(), + segments: self + .segments + .iter() + .chain(&other.segments) + .cloned() + .collect(), + } + } +} diff --git a/engine/Cargo.toml b/engine/Cargo.toml index 8e46bef..4e4600d 100644 --- a/engine/Cargo.toml +++ b/engine/Cargo.toml @@ -18,6 +18,7 @@ nu-ansi-term = "0.46.0" ecs = { path = "../ecs" } util-macros = { path = "../util-macros" } opengl-bindings = { path = "../opengl-bindings" } +engine-macros = { path = "../engine-macros" } [dependencies.winit] version = "0.30.11" diff --git a/engine/src/lib.rs b/engine/src/lib.rs index 75bc921..b470cdc 100644 --- a/engine/src/lib.rs +++ b/engine/src/lib.rs @@ -33,6 +33,7 @@ pub mod math; pub mod mesh; pub mod model; pub mod projection; +pub mod reflection; pub mod renderer; pub mod texture; pub mod transform; diff --git a/engine/src/reflection.rs b/engine/src/reflection.rs new file mode 100644 index 0000000..5bd2aef --- /dev/null +++ b/engine/src/reflection.rs @@ -0,0 +1,147 @@ +use std::alloc::Layout; +use std::any::TypeId; + +pub use engine_macros::Reflection; + +pub trait With: 'static +{ + const REFLECTION: &Reflection; + + fn reflection() -> &'static Reflection + where + Self: Sized; + + fn get_reflection(&self) -> &'static Reflection; +} + +#[derive(Debug, Clone)] +#[non_exhaustive] +pub enum Reflection +{ + Struct(Struct), + Array(Array), + Slice(Slice), + Literal, +} + +#[derive(Debug, Clone)] +pub struct Struct +{ + pub fields: &'static [StructField], +} + +#[derive(Debug, Clone)] +pub struct StructField +{ + pub name: &'static str, + pub index: usize, + pub layout: Layout, + pub byte_offset: usize, + pub type_id: TypeId, + pub type_name: &'static str, + pub reflection: &'static Reflection, +} + +#[derive(Debug, Clone)] +pub struct Array +{ + pub item_reflection: &'static Reflection, + pub length: usize, +} + +#[derive(Debug, Clone)] +pub struct Slice +{ + pub item_reflection: &'static Reflection, +} + +macro_rules! impl_with_for_literals { + ($($literal: ty),*) => { + $( + impl With for $literal + { + const REFLECTION: &Reflection = &Reflection::Literal; + + fn reflection() -> &'static Reflection + where + Self: Sized + { + Self::REFLECTION + } + + fn get_reflection(&self) -> &'static Reflection + { + Self::reflection() + } + } + )* + }; +} + +impl_with_for_literals!( + u8, + i8, + u16, + i16, + u32, + i32, + u64, + i64, + u128, + i128, + f32, + f64, + usize, + isize, + &'static str +); + +impl<T: With, const LEN: usize> With for [T; LEN] +{ + const REFLECTION: &Reflection = &Reflection::Array(Array { + item_reflection: T::REFLECTION, + length: LEN, + }); + + fn reflection() -> &'static Reflection + where + Self: Sized, + { + Self::REFLECTION + } + + fn get_reflection(&self) -> &'static Reflection + { + Self::reflection() + } +} + +impl<T: With> With for &'static [T] +{ + const REFLECTION: &Reflection = + &Reflection::Slice(Slice { item_reflection: T::REFLECTION }); + + fn reflection() -> &'static Reflection + where + Self: Sized, + { + Self::REFLECTION + } + + fn get_reflection(&self) -> &'static Reflection + { + Self::reflection() + } +} + +// Used by the Reflection derive macro +#[doc(hidden)] +pub mod __private +{ + pub const fn get_type_reflection<T>() -> &'static super::Reflection + where + T: super::With, + { + T::REFLECTION + } +} |
