use proc_macro::TokenStream; use quote::{format_ident, quote}; use std::fmt::Write; #[proc_macro_attribute] pub fn unity_authoring(_: TokenStream, item: TokenStream) -> TokenStream { let input = proc_macro2::TokenStream::from(item); quote! { #[derive(bevy::prelude::Component, Clone, Copy, Debug)] #[repr(C)] #input } .into() } #[proc_macro_attribute] pub fn bevy_state(_: TokenStream, item: TokenStream) -> TokenStream { let input = proc_macro2::TokenStream::from(item); quote! { #[repr(u8)] #[derive(Clone, Eq, PartialEq, Debug, Hash, Default, unrust::bevy::prelude::States)] #input } .into() } #[proc_macro] pub fn generate_inbuilt(item: TokenStream) -> TokenStream { let config = syn::parse2::(item.into()).expect("expecting tuples"); let enum_types = config.elems.iter().filter_map(|p| { let syn::Expr::Path(p) = p else { return None; }; let p = p.path.get_ident()?; Some(p) }); let custom_types = enum_types.clone().enumerate().map(|(index, ident)| { let index = index as u8; quote! { #ident= #index } }); let component_types = enum_types.clone().map(|ident| { quote! { pub #ident: #ident } }); let ingest_types = enum_types.clone().map(|ident| { let fn_name = format_ident!("{}_ingest_component", ident); quote! { InbuiltTypes::#ident => #fn_name(entity, &ele.value.#ident) } }); let unity_enums = enum_types .clone() .enumerate() .fold(String::default(), |mut acc, (index, ident)| { write!(acc, "{} = {},\n", ident, index).unwrap(); acc }); let union_types = enum_types .clone() .fold(String::default(), |mut acc, ident| { write!( acc, r#" [FieldOffset(0)] public {} {}; "#, ident, ident ) .unwrap(); acc }); let count = enum_types.clone().count() as i32; let csharp_fns = enum_types.clone().map(|ident| { let fn_name = format_ident!("{}_CSHARP_TOKEN", ident); quote! { #fn_name() } }); quote! { #[repr(u8)] pub enum InbuiltTypes { #(#custom_types,)* } #[allow(non_snake_case)] pub union InbuiltComponents { #(#component_types,)* } #[repr(C)] pub struct InbuiltData { pub ty: InbuiltTypes, pub value: InbuiltComponents, } #[repr(C)] pub struct InbuiltEntityData { pub entity: UnityEntity, pub data: *const InbuiltData, pub len: usize, } pub unsafe fn ingest_component(entity: &mut EntityMut, components: &[InbuiltData]) { for ele in components { match ele.ty { #(#ingest_types,)* } } } const UNITY_TYPES: &str = #unity_enums; const UNITY_UNION: &str = #union_types; const UNITY_COUNT: i32 = #count; fn get_inbuilt_csharp_tokens() -> Vec { vec![#(#csharp_fns,)*] } } .into() } #[proc_macro_attribute] pub fn unity_prefab(_: TokenStream, item: TokenStream) -> TokenStream { let input = proc_macro2::TokenStream::from(item.clone()); let parsed_enum = syn::parse_macro_input!(item as syn::ItemEnum); let enum_name = parsed_enum.ident; let res_name = format_ident!("{}Resource", enum_name); let variants = parsed_enum.variants.iter().enumerate().map(|(index, v)| { let id = &v.ident; quote! { self.vals.insert(#enum_name::#id, unrust::InstantiateEntity { entity: vals[#index].clone() }); } }); quote! { #[derive(PartialEq,Eq, Hash)] #input #[derive(unrust::bevy::prelude::Resource, Default)] pub struct #res_name { pub vals: std::collections::HashMap<#enum_name,unrust::InstantiateEntity> } impl #res_name { pub fn get_unity_prefab(&self, val: &#enum_name) -> Option<&unrust::InstantiateEntity> { self.vals.get(val) } pub fn insert_prefabs(&mut self, vals: &[unrust::UnityEntity]) { #(#variants)* } } } .into() } #[proc_macro_attribute] pub fn unrust_setup(attr: TokenStream, item: TokenStream) -> TokenStream { let input = proc_macro2::TokenStream::from(item.clone()); let parsed = syn::parse_macro_input!(item as syn::ItemFn); let ident = parsed.sig.ident; let custom_incoming = handle_custom_components(attr.clone()); let state_incoming = handle_custom_states(attr.clone()); let states = custom_states(attr.clone()); let prefabs = prefab_resources(attr.clone()); let register = register_prefabs(attr.clone()); quote! { #input #[derive(Default,Copy,Clone)] #[repr(C)] pub struct Game; impl GamePlugin for Game { fn initialize(&self, app: &mut App) { #states #prefabs #ident(app); } fn register(&self, world: &mut World, prefabs: unrust::PrefabData) { #register } #[allow(clippy::missing_safety_doc)] unsafe fn spawn_custom( &self, entity: &mut unrust::bevy::ecs::world::EntityMut, custom: *const u8, custom_len: usize, custom_state: *const u8, custom_state_len: usize, ) { unsafe { handle_custom_components(entity, custom, custom_len) }; unsafe { handle_custom_states(entity, custom_state, custom_state_len); } } } #custom_incoming #state_incoming #[no_mangle] pub extern "C" fn create_game() { let game = Box::new(Game); unsafe { unrust::setup_game(game) }; } } .into() } fn get_nth_tuple(item: TokenStream, n: usize) -> Option { let tokens = proc_macro2::TokenStream::from(item); let config = syn::parse2::(tokens).expect("expecting a tuple of tuples"); let Some(config) = config.elems.iter().nth(n) else { panic!("expected at least n tuples") }; let syn::Expr::Tuple(types) = config else { panic!("expected tuple for custom incoming types!"); }; Some(types.clone()) } fn handle_custom_components(item: TokenStream) -> proc_macro2::TokenStream { let Some(types) = get_nth_tuple(item, 0) else { return quote! { fn handle_custom_components(entity: &mut unrust::bevy::ecs::world::EntityMut, custom: *const u8, len: usize) {} }; }; let filtered = types.elems.iter().filter_map(|expr| { let syn::Expr::Path(expr) = expr else { return None; }; let last = expr.path.segments.last()?; Some((last, &expr.path)) }); let custom_types = filtered.clone().enumerate().map(|(index, (exp, _))| { let index = index as u8; quote! { #exp = #index } }); let component_types = filtered.clone().map(|(exp, rest)| { quote! { pub #exp: #rest } }); let match_types = filtered.clone().map(|(exp, _)| { quote! { CustomTypes::#exp => entity.insert(ele.value.#exp) } }); if filtered.count() > 0 { quote! { #[repr(u8)] pub enum CustomTypes { #(#custom_types,)* } #[allow(non_snake_case)] union CustomComponents { #(#component_types,)* } #[repr(C)] struct CustomData { pub ty: CustomTypes, pub value: CustomComponents, } unsafe fn handle_custom_components(entity: &mut unrust::bevy::ecs::world::EntityMut, custom: *const u8, len: usize) { let components = unsafe { std::slice::from_raw_parts(custom as *const CustomData, len) }; for ele in components { match ele.ty { #(#match_types,)* }; }; } } } else { quote! { fn handle_custom_components(entity: &mut unrust::bevy::ecs::world::EntityMut, custom: *const u8, len: usize) {} } } } fn handle_custom_states(item: TokenStream) -> proc_macro2::TokenStream { let Some(types) = get_nth_tuple(item, 1) else { return quote! { fn handle_custom_components(entity: &mut unrust::bevy::ecs::world::EntityMut, custom: *const u8, len: usize) {} }; }; let filtered = types.elems.iter().filter_map(|expr| { let syn::Expr::Path(expr) = expr else { return None; }; let last = expr.path.segments.last()?; Some((last, &expr.path)) }); let custom_types = filtered.clone().enumerate().map(|(index, (exp, _))| { let index = index as u8; quote! { #exp = #index } }); let match_types = filtered.clone().map(|(exp, _)| { let ident = &exp.ident; let comp_name = format_ident!("Custom{ident}"); quote! { CustomStates::#exp => { entity.insert(#comp_name { val: ele.value }); } } }); let custom_components = filtered.clone().map(|(exp, p)| { let ident = &exp.ident; let comp_name = format_ident!("Custom{ident}"); let fn_name = format_ident!("update_{ident}"); quote! { #[derive(unrust::bevy::prelude::Component,Debug, Clone, PartialEq)] pub struct #comp_name { pub val: u8, } #[allow(non_snake_case)] fn #fn_name( mut next_state: ResMut>, entities: Query<&#comp_name, Changed<#comp_name>>, ) { let Ok(state) = entities.get_single() else { return; }; unrust::tracing::trace!("switching to {}", state.val); unsafe { next_state.set(std::mem::transmute(state.val)); } } } }); if filtered.count() > 0 { quote! { #(#custom_components)* #[repr(u8)] #[derive(Clone, Debug, PartialEq)] pub enum CustomStates { #(#custom_types,)* } #[repr(C)] struct CustomStateData { pub ty: CustomStates, pub value: u8, } unsafe fn handle_custom_states(entity: &mut unrust::bevy::ecs::world::EntityMut, custom: *const u8, len: usize) { let components = unsafe { std::slice::from_raw_parts(custom as *const CustomStateData, len) }; for ele in components { match ele.ty { #(#match_types,)* }; }; } } } else { quote! { unsafe fn handle_custom_states(entity: &mut unrust::bevy::ecs::world::EntityMut, custom: *const u8, len: usize) {} } } } fn custom_states(item: TokenStream) -> proc_macro2::TokenStream { let Some(states) = get_nth_tuple(item, 1) else { return quote! {}; }; let filtered = states.elems.iter().filter_map(|expr| { let syn::Expr::Path(expr) = expr else { return None; }; let last = expr.path.segments.last()?; let comp_name = &last.ident; let fn_name = format_ident!("update_{comp_name}"); Some(quote! { app.add_state::<#expr>(); app.add_systems(PostUpdate, #fn_name); }) }); quote! { #(#filtered)* } } fn prefab_resources(item: TokenStream) -> proc_macro2::TokenStream { let Some(states) = get_nth_tuple(item, 2) else { return quote! {}; }; let filtered = states.elems.iter().filter_map(|expr| { let syn::Expr::Path(expr) = expr else { return None; }; let last = expr.path.segments.last()?; let comp_name = &last.ident; let res_name = format_ident!("{comp_name}Resource"); Some(quote! { app.insert_resource(#res_name::default()); }) }); quote! { #(#filtered)* } } fn register_prefabs(item: TokenStream) -> proc_macro2::TokenStream { let Some(states) = get_nth_tuple(item, 2) else { return quote! {}; }; let filtered = states .elems .iter() .filter_map(|expr| { let syn::Expr::Path(expr) = expr else { return None; }; Some(expr) }) .enumerate() .map(|(index, ident)| { let count = index as i32; let ident = quote! { #ident }; let ident: syn::Expr = syn::parse_str(&format!("{}Resource", ident.to_string().replace(' ', ""))).unwrap(); Some(quote! { #count => { let Some(mut res) = world.get_resource_mut::<#ident>() else { return; }; res.insert_prefabs(guids); } }) }); quote! { let guids = unsafe { std::slice::from_raw_parts(prefabs.guids, prefabs.len) }; match prefabs.ref_id { #(#filtered)* _ => {} } } }