bevy_yoetz_macros/
lib.rs

1mod suggestion;
2mod util;
3
4use syn::parse_macro_input;
5
6/// Generate implementation `YoetzSuggestion` together with the companion types required for it.
7///
8/// The generated companion types are:
9///
10/// * The key `enum` - with its name being the suggestion type's name concatenated with the "Key"
11///   suffix. An `enum` containing each variant of the suggestion enum, but with only the fields
12///   marked as `#[yoetz(key)]` included.
13///
14/// * A strategy `struct` for each variant - with their names being the suggestion type's name
15///   concatenated with the variant's name. These structs act as Bevy `Component`s which will be
16///   added to the entity when the suggested variant is chosen, and can be used by action systems
17///   to enact the behaviors they represent.
18///
19/// * For internal usage only - an omni-query `struct`.
20///
21/// This macro must decorate an `enum`, and each variant of the `enum` must be either a unit
22/// variant or a struct variant (tuple variants are not allowed). Each field of a struct variant
23/// must be annotated with a `#[yoetz(...)]` attribute that specifies its role:
24///
25/// * Key fields (annotated with `#[yoetz(key)]`) can discern between different suggestions. If the
26///   same variant is suggested but with a difference in the key fields, it will be considered as a
27///   different suggestion, which means the `consistency_bonus` will not be applied and that
28///   components will be re-created.
29///
30///   Key fields **must** be [`Clone`] and [`PartialEq`], because they get into the key enum.
31///
32/// * Input fields (annotated with `#[yoetz(input)]`) always get updated from the suggestion, even
33///   if the suggestion itself (and therefore the components) do not change.
34///
35/// * State fields (annotated with `#[yoetz(state)]`) only get initialized from the suggestion when
36///   the suggestion itself changes. When it doesn't (the variant and the key fields remain the
37///   same) the state fields from the suggestion are discarded, which means that the action systems
38///   can use them to maintain their own state.
39///
40/// The `enum` itself may be annotated with its own `#[yoetz(...)] attribute:
41///
42/// - `#[yoetz(key_enum(...))]` - for customizing the generated key `enum`.
43///
44/// - `#[yoetz(strategy_structs(...))]` - for customizing the generated strategy `struct`s.
45///
46/// Attributes that customize generated types support the following settings:
47///
48/// - `#[yoetz(...(derive(...)))]` - for applying derive macros on the generated structs.
49///
50/// ```no_run
51/// # use bevy::prelude::*;
52/// # use bevy_yoetz::prelude::*;
53/// #[derive(YoetzSuggestion)]
54/// #[yoetz(
55///     // We want to be able to print both the key enum `AiBehaviorKey` and the strategy structs
56///     // `AiBehaviorDoNothing` and `AiBehaviorAttack`, so we make them all Debug.
57///     key_enum(derive(Debug)),
58///     strategy_structs(derive(Debug),
59/// ))]
60/// enum AiBehavior {
61///     // Unit variants are allowed.
62///     DoNothing,
63///     // Struct variants are allowed. But not tuple variants.
64///     Attack {
65///         // This is a key field, because attacking a different target should be considered a
66///         // different suggestion.
67///         #[yoetz(key)]
68///         target_to_attack: Entity,
69///
70///         // This is an input field so that the system that suggests the Attack can also tell the
71///         // attacker AI where the target is, since it already has that information.
72///         #[yoetz(input)]
73///         target_position: Vec3,
74///
75///         // This is a state field because the system that suggests the Attack will create the
76///         // timer, but once the attack is started we want to advance the timer and not just
77///         // set it again over and over each frame.
78///         #[yoetz(state)]
79///         time_left_for_the_attack: Timer,
80///     },
81/// }
82/// ```
83#[proc_macro_derive(YoetzSuggestion, attributes(yoetz))]
84pub fn derive_suggestion(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
85    let input = parse_macro_input!(input as syn::DeriveInput);
86    match suggestion::impl_suggestion(&input) {
87        Ok(output) => output.into(),
88        Err(error) => error.to_compile_error().into(),
89    }
90}