moecs_macros 0.1.0

Macros for the MOECS ECS engine.
Documentation
extern crate proc_macro;

use proc_macro::TokenStream;
use quote::quote;
use syn::{parse_macro_input, DeriveInput};

#[proc_macro_derive(Component)]
pub fn derive_component(input: TokenStream) -> TokenStream {
    let input_clone = input.clone();

    let ast = parse_macro_input!(input_clone as DeriveInput);
    let name_literal = &ast.ident;
    let (impl_generics, ty_generics, where_clause) = ast.generics.split_for_impl();
    let component_impl = TokenStream::from(quote! {
        impl #impl_generics ::moecs::component::Component for #name_literal #ty_generics #where_clause {}
    });

    let property_id_impl = derive_property_id(input.clone());

    TokenStream::from_iter(vec![component_impl, property_id_impl])
}

#[proc_macro_derive(System)]
pub fn derive_system(input: TokenStream) -> TokenStream {
    derive_property_id(input)
}

#[proc_macro_derive(SystemParam)]
pub fn derive_system_param(input: TokenStream) -> TokenStream {
    let input_clone = input.clone();

    let ast = parse_macro_input!(input_clone as DeriveInput);
    let name_literal = &ast.ident;
    let (impl_generics, ty_generics, where_clause) = ast.generics.split_for_impl();
    let system_param_impl = TokenStream::from(quote! {
        impl #impl_generics ::moecs::system::SystemParam for #name_literal #ty_generics #where_clause {}
    });

    let property_id_impl = derive_property_id(input.clone());

    TokenStream::from_iter(vec![system_param_impl, property_id_impl])
}

fn derive_property_id(input: TokenStream) -> TokenStream {
    let ast = parse_macro_input!(input as DeriveInput);

    let name_literal = &ast.ident;
    let name_string = &ast.ident.to_string();
    let (impl_generics, ty_generics, where_clause) = ast.generics.split_for_impl();

    TokenStream::from(quote! {
        impl #impl_generics ::moecs::util::PropertyId for #name_literal #ty_generics #where_clause {
            fn property_string() -> &'static str
            where Self: Sized {
                static PROPERTY_STRING: &'static str = concat!(module_path!(), "::", #name_string);
                PROPERTY_STRING
            }

            fn property_id() -> u64
            where Self: Sized {
                use ::std::collections::hash_map::DefaultHasher;
                use ::std::hash::{Hash, Hasher};
                use ::std::sync::OnceLock;

                static HASH: OnceLock<u64> = OnceLock::new();
                *HASH.get_or_init(|| {
                    let key = #name_literal::property_string();
                    let mut hasher = DefaultHasher::new();
                    key.hash(&mut hasher);
                    hasher.finish()
                })
            }

            fn self_property_id(&self) -> u64 {
                #name_literal::property_id()
            }

            fn self_property_string(&self) -> &'static str {
                #name_literal::property_string()
            }
        }
    })
}