An experimental, status effects-as-entities system for Bevy.
0
fork

Configure Feed

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

Overhauled macro code using darling.

This allow the alternate `plugin(...)` syntax as apposed to `plugin = ...`.

Most of this code was adapted from my other crate, `immediate_stats`.

+196 -69
+55 -1
Cargo.lock
··· 293 293 name = "bevy_status_effects_macros" 294 294 version = "0.1.0" 295 295 dependencies = [ 296 + "darling", 296 297 "proc-macro-error", 297 298 "proc-macro2", 298 299 "quote", ··· 510 511 ] 511 512 512 513 [[package]] 514 + name = "darling" 515 + version = "0.20.11" 516 + source = "registry+https://github.com/rust-lang/crates.io-index" 517 + checksum = "fc7f46116c46ff9ab3eb1597a45688b6715c6e628b5c133e288e709a29bcb4ee" 518 + dependencies = [ 519 + "darling_core", 520 + "darling_macro", 521 + ] 522 + 523 + [[package]] 524 + name = "darling_core" 525 + version = "0.20.11" 526 + source = "registry+https://github.com/rust-lang/crates.io-index" 527 + checksum = "0d00b9596d185e565c2207a0b01f8bd1a135483d02d9b7b0a54b11da8d53412e" 528 + dependencies = [ 529 + "fnv", 530 + "ident_case", 531 + "proc-macro2", 532 + "quote", 533 + "strsim 0.11.1", 534 + "syn 2.0.100", 535 + ] 536 + 537 + [[package]] 538 + name = "darling_macro" 539 + version = "0.20.11" 540 + source = "registry+https://github.com/rust-lang/crates.io-index" 541 + checksum = "fc34b93ccb385b40dc71c6fceac4b2ad23662c7eeb248cf10d529b7e055b6ead" 542 + dependencies = [ 543 + "darling_core", 544 + "quote", 545 + "syn 2.0.100", 546 + ] 547 + 548 + [[package]] 513 549 name = "deluxe" 514 550 version = "0.5.0" 515 551 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 531 567 "arrayvec", 532 568 "proc-macro2", 533 569 "quote", 534 - "strsim", 570 + "strsim 0.10.0", 535 571 "syn 2.0.100", 536 572 ] 537 573 ··· 628 664 checksum = "1d674e81391d1e1ab681a28d99df07927c6d4aa5b027d7da16ba32d1d21ecd99" 629 665 630 666 [[package]] 667 + name = "fnv" 668 + version = "1.0.7" 669 + source = "registry+https://github.com/rust-lang/crates.io-index" 670 + checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" 671 + 672 + [[package]] 631 673 name = "foldhash" 632 674 version = "0.1.5" 633 675 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 758 800 version = "0.4.3" 759 801 source = "registry+https://github.com/rust-lang/crates.io-index" 760 802 checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" 803 + 804 + [[package]] 805 + name = "ident_case" 806 + version = "1.0.1" 807 + source = "registry+https://github.com/rust-lang/crates.io-index" 808 + checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" 761 809 762 810 [[package]] 763 811 name = "if_chain" ··· 1243 1291 version = "0.10.0" 1244 1292 source = "registry+https://github.com/rust-lang/crates.io-index" 1245 1293 checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" 1294 + 1295 + [[package]] 1296 + name = "strsim" 1297 + version = "0.11.1" 1298 + source = "registry+https://github.com/rust-lang/crates.io-index" 1299 + checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" 1246 1300 1247 1301 [[package]] 1248 1302 name = "syn"
+31 -8
bevy_status_effects/tests/bevy_butler.rs
··· 14 14 #[add_component(plugin = ButlerPlugin)] 15 15 struct Derive; 16 16 17 + #[test] 18 + fn derive_refresh() { 19 + let mut app = App::new(); 20 + app.add_plugins(ButlerPlugin); 21 + app.update(); 22 + 23 + let target = app.world_mut().spawn_empty().id(); 24 + let first = app 25 + .world_mut() 26 + .spawn((Derive, Effecting(target), EffectMode::Replace)) 27 + .id(); 28 + let second = app 29 + .world_mut() 30 + .spawn((Derive, Effecting(target), EffectMode::Replace)) 31 + .id(); 32 + 33 + app.world_mut().flush(); 34 + 35 + assert_eq!(app.world().get::<Derive>(first), None); 36 + assert_eq!(app.world().get::<Derive>(second), Some(&Derive)); 37 + } 38 + 39 + /// Same as above, but with `plugin(...)` syntax as apposed to `plugin = ...` 17 40 #[derive(StatusEffect, Component, Debug, Eq, PartialEq, Default)] 18 - #[add_component(plugin = ButlerPlugin)] 19 - struct DeriveRefresh; 41 + #[add_component(plugin(ButlerPlugin))] 42 + struct AlternateSyntax; 20 43 21 44 #[test] 22 - fn derive_refresh() { 45 + fn derive_refresh_alternate_syntax() { 23 46 let mut app = App::new(); 24 47 app.add_plugins(ButlerPlugin); 25 48 app.update(); ··· 27 50 let target = app.world_mut().spawn_empty().id(); 28 51 let first = app 29 52 .world_mut() 30 - .spawn((DeriveRefresh, Effecting(target), EffectMode::Replace)) 53 + .spawn((AlternateSyntax, Effecting(target), EffectMode::Replace)) 31 54 .id(); 32 55 let second = app 33 56 .world_mut() 34 - .spawn((DeriveRefresh, Effecting(target), EffectMode::Replace)) 57 + .spawn((AlternateSyntax, Effecting(target), EffectMode::Replace)) 35 58 .id(); 36 59 37 60 app.world_mut().flush(); 38 61 39 - assert_eq!(app.world().get::<DeriveRefresh>(first), None); 62 + assert_eq!(app.world().get::<AlternateSyntax>(first), None); 40 63 assert_eq!( 41 - app.world().get::<DeriveRefresh>(second), 42 - Some(&DeriveRefresh) 64 + app.world().get::<AlternateSyntax>(second), 65 + Some(&AlternateSyntax) 43 66 ); 44 67 }
+1
bevy_status_effects_macros/Cargo.toml
··· 20 20 bevy_butler = [] 21 21 22 22 [dependencies] 23 + darling = "0.20.11" 23 24 proc-macro2 = "1.0" 24 25 proc-macro-error = "1.0" 25 26 quote = "1.0"
+95
bevy_status_effects_macros/src/bevy_butler.rs
··· 1 + use darling::ast::NestedMeta; 2 + use darling::{Error, FromMeta}; 3 + use proc_macro2::{Ident, TokenStream}; 4 + use quote::{ToTokens, format_ident, quote}; 5 + use syn::{DeriveInput, Expr, Meta, Path}; 6 + 7 + /// Returns code that will register the effect hook using Bevy Butler. 8 + pub fn register_systems(input: DeriveInput) -> darling::Result<TokenStream> { 9 + let struct_name = &input.ident; 10 + 11 + let mut butler_attributes = ButlerAttribute::new(struct_name); 12 + 13 + for attr in input.attrs { 14 + if attr.path().is_ident("add_component") { 15 + let plugin = PluginPath::from_meta(&attr.meta)?; 16 + butler_attributes.plugin = Some(plugin); 17 + } 18 + } 19 + 20 + Ok(butler_attributes.into_token_stream()) 21 + } 22 + 23 + struct ButlerAttribute<'a> { 24 + ident: &'a Ident, 25 + plugin: Option<PluginPath>, 26 + } 27 + 28 + impl<'a> ButlerAttribute<'a> { 29 + pub fn new(ident: &'a Ident) -> Self { 30 + Self { 31 + ident, 32 + plugin: None, 33 + } 34 + } 35 + } 36 + 37 + impl<'a> ToTokens for ButlerAttribute<'a> { 38 + fn to_tokens(&self, tokens: &mut TokenStream) { 39 + if let Some(plugin_path) = &self.plugin { 40 + let ident = &self.ident; 41 + let plugin = &plugin_path.0; 42 + let use_as = format_ident!("__{ident}_component"); 43 + 44 + // Due to some strange import scoping issues, we cannot use the plugins. 45 + // Instead, we can just recreate the plugin's functionality. 46 + tokens.extend(quote! { 47 + #[bevy_butler::add_system( 48 + generics = <#ident>, 49 + plugin = #plugin, schedule = 50 + bevy_status_effects::Startup 51 + )] 52 + use bevy_status_effects::init_effect_hook as #use_as; 53 + }); 54 + } 55 + } 56 + } 57 + 58 + /// Represents a `plugin(PATH)` or `plugin = PATH` attribute meta. 59 + pub struct PluginPath(pub Path); 60 + 61 + impl FromMeta for PluginPath { 62 + fn from_list(items: &[NestedMeta]) -> darling::Result<Self> { 63 + for item in items { 64 + return match item { 65 + NestedMeta::Meta(meta) => match meta { 66 + Meta::Path(_) => Err(Error::custom("Expected a value for `plugin`")), 67 + Meta::List(list) => { 68 + if list.path.require_ident()? != "plugin" { 69 + continue; 70 + } 71 + 72 + let mut path = None; 73 + 74 + list.parse_nested_meta(|value_meta| { 75 + path = Some(value_meta.path); 76 + Ok(()) 77 + })?; 78 + 79 + match path { 80 + None => Err(Error::custom("Expected `plugin` attribute")), 81 + Some(path) => Ok(PluginPath(path)), 82 + } 83 + } 84 + Meta::NameValue(name_value) => match &name_value.value { 85 + Expr::Path(p) => Ok(PluginPath(p.path.clone())), 86 + _ => Err(Error::custom("Expected a path to a butler plugin")), 87 + }, 88 + }, 89 + NestedMeta::Lit(_) => Err(Error::custom("Expected `plugin` attribute")), 90 + }; 91 + } 92 + 93 + Err(Error::custom("Expected `plugin` attribute")) 94 + } 95 + }
+14 -60
bevy_status_effects_macros/src/lib.rs
··· 1 + #[cfg(feature = "bevy_butler")] 2 + mod bevy_butler; 3 + 1 4 use proc_macro_error::proc_macro_error; 2 5 use quote::quote; 3 - use syn::DeriveInput; 4 - 5 - #[cfg(feature = "bevy_butler")] 6 - use proc_macro2::{Ident, Span, TokenStream}; 7 - #[cfg(feature = "bevy_butler")] 8 - use syn::{Attribute, Token}; 6 + use syn::{DeriveInput, parse_macro_input}; 9 7 10 8 #[proc_macro_derive(StatusEffect, attributes(add_component))] 11 9 #[proc_macro_error] 12 10 pub fn stat_container_derive(item: proc_macro::TokenStream) -> proc_macro::TokenStream { 13 - let tree: DeriveInput = syn::parse(item).expect("TokenStream must be valid."); 11 + let tree: DeriveInput = parse_macro_input!(item as DeriveInput); 12 + let ident = &tree.ident; 14 13 15 - #[cfg(feature = "bevy_butler")] 16 - let mut systems = Vec::new(); 17 - 18 - let struct_name = &tree.ident; 19 - 20 - for attr in &tree.attrs { 21 - let path = attr.meta.path(); 22 - 23 - let Some(ident) = path.get_ident() else { 24 - continue; 25 - }; 26 - 27 - match ident.to_string().as_str() { 28 - #[cfg(feature = "bevy_butler")] 29 - "add_component" => parse_add_component(attr, struct_name, &mut systems), 30 - _ => continue, 31 - }; 32 - } 14 + let trait_impl = quote! { 15 + impl StatusEffect for #ident {} 16 + }; 33 17 34 18 #[cfg(feature = "bevy_butler")] 35 - return quote! { 36 - impl bevy_status_effects::StatusEffect for #struct_name {} 37 - #(#systems)* 19 + { 20 + let systems = 21 + bevy_butler::register_systems(tree).unwrap_or_else(darling::Error::write_errors); 22 + quote! { #trait_impl #systems }.into() 38 23 } 39 - .into(); 40 24 41 25 #[cfg(not(feature = "bevy_butler"))] 42 - quote! { 43 - impl bevy_status_effects::StatusEffect for #struct_name {} 44 - } 45 - .into() 46 - } 47 - 48 - #[cfg(feature = "bevy_butler")] 49 - fn parse_add_component(attr: &Attribute, struct_name: &Ident, systems: &mut Vec<TokenStream>) { 50 - attr.parse_nested_meta(|meta| { 51 - let Some(var_name) = meta.path.segments.first() else { 52 - return Ok(()) 53 - }; 54 - 55 - if var_name.ident.to_string() != "plugin" { 56 - return Ok(()) 57 - }; 58 - 59 - let input = &meta.input; 60 - 61 - input.parse::<Token![=]>().expect("An equals sign."); 62 - let input = input.parse::<Ident>().expect("An identifier."); 63 - 64 - let use_as = Ident::new(&format!("__{struct_name}_init_effect_hook"), Span::call_site()); 65 - 66 - systems.push(quote! { 67 - #[bevy_butler::add_system(generics = <#struct_name>, plugin = #input, schedule = bevy_status_effects::Startup)] 68 - use bevy_status_effects::init_effect_hook as #use_as; 69 - }); 70 - 71 - Ok(()) 72 - }).unwrap(); 26 + trait_impl.into() 73 27 }