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