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}