Game stats that reset every frame, inspired by immediate mode GUI.
gamedev bevy stats
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

Moved logic for enum/struct derive into their own modules.

+120 -110
+73
immediate_stats_macros/src/derive_enum.rs
··· 1 + use crate::FieldOptions; 2 + use proc_macro2::{Span, TokenStream}; 3 + use quote::quote; 4 + use syn::{DataEnum, Ident, Variant}; 5 + 6 + /// Returns a match statement that can be used to reset an enum. 7 + pub fn reset_enum(body: &DataEnum) -> TokenStream { 8 + body.variants 9 + .iter() 10 + .map(|variant| { 11 + let cases = reset_variant(&variant); 12 + quote! { 13 + match self { 14 + #cases 15 + _ => {} 16 + } 17 + } 18 + }) 19 + .flatten() 20 + .collect() 21 + } 22 + 23 + /// Returns a case that can be used to reset the variant. 24 + /// If there are no stat fields, the result will be empty. 25 + fn reset_variant(variant: &Variant) -> TokenStream { 26 + // List of all identifiers that need to be reset. 27 + let names: Vec<Ident> = variant 28 + .fields 29 + .iter() 30 + .enumerate() 31 + .filter_map(|(index, field)| { 32 + let options = FieldOptions::from_field(field); 33 + 34 + if options.is_stat() { 35 + return Some(match options.ident { 36 + Some(ident) => ident, 37 + None => get_ident_from_index(index), 38 + }); 39 + } 40 + 41 + None 42 + }) 43 + .collect(); 44 + 45 + if names.is_empty() { 46 + return TokenStream::new(); 47 + } 48 + 49 + let is_named = variant.fields.iter().next().unwrap().ident.is_some(); 50 + let ident = &variant.ident; 51 + 52 + if is_named { 53 + quote! { 54 + Self::#ident { #(#names,)* .. } => { 55 + #(#names.reset_modifiers();)* 56 + }, 57 + } 58 + } else { 59 + quote! { 60 + Self::#ident ( #(#names,)* .. ) => { 61 + #(#names.reset_modifiers();)* 62 + }, 63 + } 64 + } 65 + } 66 + 67 + /// Generates an alphabetic identifier from an index. 68 + fn get_ident_from_index(index: usize) -> Ident { 69 + Ident::new( 70 + format!("{}", ('a' as u8 + index as u8) as char).as_str(), 71 + Span::call_site(), 72 + ) 73 + }
+35
immediate_stats_macros/src/derive_struct.rs
··· 1 + use crate::FieldOptions; 2 + use proc_macro2::TokenStream; 3 + use quote::quote; 4 + use syn::{DataStruct, Field, Index}; 5 + 6 + /// Returns the code that can be used to reset a struct's stat fields. 7 + pub fn reset_struct(body: &DataStruct) -> TokenStream { 8 + body.fields 9 + .iter() 10 + .enumerate() 11 + .map(|(index, field)| reset_struct_field(field, index)) 12 + .flatten() 13 + .collect() 14 + } 15 + 16 + /// Returns the method call that can be used to reset a stat field. 17 + /// If the field is not a stat, the result will be empty. 18 + /// The `index` is used for tuple/unnamed fields. 19 + fn reset_struct_field(field: &Field, index: usize) -> TokenStream { 20 + let options = FieldOptions::from_field(&field); 21 + 22 + if options.is_stat() { 23 + return match options.ident { 24 + Some(ident) => { 25 + quote! { self.#ident.reset_modifiers(); } 26 + } 27 + None => { 28 + let index = Index::from(index); 29 + quote! { self.#index.reset_modifiers(); } 30 + } 31 + }; 32 + } 33 + 34 + TokenStream::new() 35 + }
+12 -110
immediate_stats_macros/src/lib.rs
··· 1 1 #[cfg(feature = "bevy_butler")] 2 2 mod bevy_butler; 3 + mod derive_enum; 4 + mod derive_struct; 3 5 4 - use darling::{Error, FromField}; 6 + use darling::Error; 5 7 use proc_macro_error::{emit_call_site_error, emit_warning, proc_macro_error}; 6 - use proc_macro2::{Span, TokenStream}; 7 8 use quote::{ToTokens, quote}; 8 9 use syn::spanned::Spanned; 9 - use syn::{Data, DeriveInput, Field, Fields, Ident, Index, Variant, parse_macro_input}; 10 + use syn::{Data, DeriveInput, Field, Ident, parse_macro_input}; 10 11 11 12 #[proc_macro_derive(StatContainer, attributes(stat, stat_ignore, add_component))] 12 13 #[proc_macro_error] ··· 15 16 let ident = &tree.ident; 16 17 17 18 let method_contents = match tree.data.clone() { 18 - Data::Struct(s) => reset_struct(&s.fields), 19 - Data::Enum(e) => reset_variants(e.variants.iter()), 19 + Data::Struct(s) => derive_struct::reset_struct(&s), 20 + Data::Enum(e) => derive_enum::reset_enum(&e), 20 21 Data::Union(_) => { 21 22 emit_call_site_error!("This trait cannot be derived from unions."); 22 23 return proc_macro::TokenStream::new(); ··· 41 42 method.into() 42 43 } 43 44 45 + /// Represents the options that a field could have. 44 46 #[derive(Default)] 45 47 struct FieldOptions { 46 48 ident: Option<Ident>, 49 + /// True if the field's type contains the word "Stat". 47 50 stat_type: bool, 51 + /// True if the field has the `#[stat]` attribute. 48 52 include: bool, 53 + /// True if the field has the `#[stat_ignore]` attribute. 49 54 exclude: bool, 50 55 } 51 56 ··· 62 67 63 68 (self.include || self.stat_type) && !self.exclude 64 69 } 65 - } 66 70 67 - impl FromField for FieldOptions { 68 - fn from_field(field: &Field) -> darling::Result<Self> { 71 + fn from_field(field: &Field) -> Self { 69 72 let mut options = FieldOptions { 70 73 ident: field.ident.clone(), 71 74 ..Self::default() ··· 84 87 } 85 88 } 86 89 87 - Ok(options) 88 - } 89 - } 90 - 91 - /// Returns the code that can be used to reset a struct's stat fields. 92 - fn reset_struct(fields: &Fields) -> TokenStream { 93 - fields 94 - .iter() 95 - .enumerate() 96 - .map(|(index, field)| reset_struct_field(field, index)) 97 - .flatten() 98 - .collect() 99 - } 100 - 101 - /// Returns the method call that can be used to reset a stat field. 102 - /// If the field is not a stat, the result will be empty. 103 - /// The `index` is used for tuple/unnamed fields. 104 - fn reset_struct_field(field: &Field, index: usize) -> TokenStream { 105 - let options = FieldOptions::from_field(&field).unwrap(); 106 - 107 - if options.is_stat() { 108 - return match options.ident { 109 - Some(ident) => { 110 - quote! { self.#ident.reset_modifiers(); } 111 - } 112 - None => { 113 - let index = Index::from(index); 114 - quote! { self.#index.reset_modifiers(); } 115 - } 116 - }; 117 - } 118 - 119 - TokenStream::new() 120 - } 121 - 122 - /// Returns a match statement that can be used to reset an enum. 123 - fn reset_variants<'a, T>(variants: T) -> TokenStream 124 - where 125 - T: Iterator<Item = &'a Variant>, 126 - { 127 - variants 128 - .map(|variant| { 129 - let cases = reset_variant(&variant); 130 - quote! { 131 - match self { 132 - #cases 133 - _ => {} 134 - } 135 - } 136 - }) 137 - .flatten() 138 - .collect() 139 - } 140 - 141 - /// Returns a case that can be used to reset the variant. 142 - /// If there are no stat fields, the result will be empty. 143 - fn reset_variant(variant: &Variant) -> TokenStream { 144 - let names: Vec<Ident> = variant 145 - .fields 146 - .iter() 147 - .enumerate() 148 - .filter_map(|(index, field)| { 149 - let options = FieldOptions::from_field(field).unwrap(); 150 - 151 - if options.is_stat() { 152 - return Some(match options.ident { 153 - Some(ident) => ident, 154 - None => get_ident_from_index(index), 155 - }); 156 - } 157 - 158 - None 159 - }) 160 - .collect(); 161 - 162 - if names.is_empty() { 163 - return TokenStream::new(); 90 + options 164 91 } 165 - 166 - let is_named = variant.fields.iter().next().unwrap().ident.is_some(); 167 - let ident = &variant.ident; 168 - 169 - if is_named { 170 - quote! { 171 - Self::#ident { #(#names,)* .. } => { 172 - #(#names.reset_modifiers();)* 173 - }, 174 - } 175 - } else { 176 - quote! { 177 - Self::#ident ( #(#names,)* .. ) => { 178 - #(#names.reset_modifiers();)* 179 - }, 180 - } 181 - } 182 - } 183 - 184 - /// Generates an alphabetic identifier from an index. 185 - fn get_ident_from_index(index: usize) -> Ident { 186 - Ident::new( 187 - format!("{}", ('a' as u8 + index as u8) as char).as_str(), 188 - Span::call_site(), 189 - ) 190 92 }