bevy_tnua_macros/lib.rs
1use proc_macro2::TokenStream;
2use syn::{DeriveInput, parse::Error, parse_macro_input, spanned::Spanned};
3
4use self::action_slots_derive::codegen::generate_action_slots_derive;
5use self::action_slots_derive::parsed::ParsedActionSlots;
6use self::scheme_derive::codegen::generate_scheme_derive;
7use self::scheme_derive::parsed::ParsedScheme;
8
9mod action_slots_derive;
10mod scheme_derive;
11#[allow(unused)]
12mod util;
13
14/// Make an enum a control scheme for a Tnua character controller.
15///
16/// This implements the `TnuaScheme` trait for the enum, and also generates the following structs
17/// required for implementing it (replace `{name}` with the name of the control scheme enum):
18///
19/// * `{name}Config` - a struct with the configuration of the basis and all the actions.
20/// * `{name}ActionDiscriminant` - an enum mirroring the control scheme, except all the variants
21/// are units.
22/// * `{name}ActionState` - an enum mirroring the control scheme, except instead of just the input
23/// types each variant contains a `TnuaActionState` which holds the input, configuration and
24/// memory of the action.
25///
26/// The enum itself **must** have a `#[scheme(basis = ...)]` attribute that specifies the basis of
27/// the control scheme (typically `TnuaBuiltinWalk`). The following additional parameters are
28/// allowed on that `scheme` attribute on the enum:
29///
30/// * `#[scheme(serde)]` - derive Serialize and Deserialize on the generated action state enum.
31/// * This is mostly useful with (and will probably fail without) the `serialize` feature enabled
32/// on the bevy-tnua crate.
33/// * The control scheme enum itself will not get these derives automatically - that derive will
34/// need to be added manually.
35/// * With these, and with the `serialize` feature enabled, the `TnuaController` and
36/// `TnuaGhostOverwrites` of the control scheme will also be serializable and deserializable -
37/// allowing networking libraries to synchronize them between machines.
38/// * Even without this setting and without the `serialize` feature on the bevy-tnua crate, the
39/// generated configuration struct and the action discriminant enum will still get these
40/// derives.
41/// * `#[scheme(config_ext = ...)]` - add an extension field to the configuration struct generated
42/// for the control scheme. The field will have the name `ext` and the type specified by this
43/// parameter. This allows adding user-defined settings that the user control systems can utilize
44/// for character control related decisions (e.g. - max number of air actions allowed), and load
45/// these settings from the same asset.
46///
47/// Each variant **must** be a tuple variant, where the first element of the tuple is the action,
48/// followed by zero or more payloads.
49///
50/// Payloads are ignored by Tnua itself - they are for the user systems to keep track of data
51/// related to the actions - except when they are annotated by `#[scheme(modify_basis_config)]`.
52/// Such payloads will modify the configuration when the action they are part of is in effect.
53///
54/// Example:
55///
56/// ```ignore
57/// #[derive(TnuaScheme)]
58/// #[scheme(basis = TnuaBuiltinWalk)]
59/// pub enum ControlScheme {
60/// Jump(TnuaBuiltinJump),
61/// Crouch(
62/// TnuaBuiltinCrouch,
63/// // While this action is in effect, `SlowDownWhileCrouching` will change the
64/// // `TnuaBuiltinWalkConfig` to reduce character speed.
65/// #[scheme(modify_basis_config)] SlowDownWhileCrouching,
66/// ),
67/// WallSlide(
68/// TnuaBuiltinWallSlide,
69/// // This payload has is ignored by Tnua, but user code can use it to tell which wall
70/// // the character is sliding on.
71/// Entity,
72/// ),
73/// }
74/// ```
75///
76#[proc_macro_derive(TnuaScheme, attributes(scheme))]
77pub fn derive_tnua_scheme(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
78 let input = parse_macro_input!(input as DeriveInput);
79 match impl_derive_tnua_scheme(&input) {
80 Ok(output) => output.into(),
81 Err(error) => error.to_compile_error().into(),
82 }
83}
84
85fn impl_derive_tnua_scheme(ast: &syn::DeriveInput) -> Result<TokenStream, Error> {
86 Ok(match &ast.data {
87 syn::Data::Struct(_) => {
88 return Err(Error::new(
89 ast.span(),
90 "TnuaScheme is not supported for structs - only for enums",
91 ));
92 }
93 syn::Data::Enum(data_enum) => {
94 let parsed = ParsedScheme::new(ast, data_enum)?;
95 generate_scheme_derive(&parsed)?
96 }
97 syn::Data::Union(_) => {
98 return Err(Error::new(
99 ast.span(),
100 "TnuaScheme is not supported for unions - only for enums",
101 ));
102 }
103 })
104}
105
106/// Define the behavior of action that can be performed a limited amount of times during certain
107/// durations (e.g. air actions)
108///
109/// This macro must be defined on a struct with a `#[slots(scheme = ...)]` attribute on the struct
110/// itself, pointing to a [`TnuaScheme`] that the slots belong to.
111///
112/// Each field of the struct must have the type [`usize`], and have a `#[slots(...)]` attribute on
113/// it listing the actions (variants of the scheme enum) belonging to that slot.
114///
115/// Not all actions need to be assigned to slots, but every slot needs at least one action assigned
116/// to it.
117///
118/// A single action must not be assigned to more than one slot, but a single slot is allowed to
119/// have multiple actions (`#[slots(Action1, Action2, ...)]`)
120///
121/// The main attribute on the struct can also have a `#[slots(ending(...))]` parameter, listing
122/// actions that end the counting. This is used to signal that the counting should start anew after
123/// these actions, even if the regular conditions for terminating and re-starting the counting
124/// don't occur. For example - when counting air actions, a wall slide should end the counting so
125/// that after jumping from it'd be a new air duration and the player could air-jump again even if
126/// they've exhausted all the air jumps before the wall slide.
127///
128/// Example:
129///
130/// ```ignore
131/// #[derive(Debug, TnuaActionSlots)]
132/// #[slots(scheme = ControlScheme, ending(WallSlide))]
133/// pub struct DemoControlSchemeAirActions {
134/// #[slots(Jump)]
135/// jump: usize,
136/// #[slots(Dash)]
137/// dash: usize,
138/// // Other actions, like `Crouch`
139/// }
140/// ```
141#[proc_macro_derive(TnuaActionSlots, attributes(slots))]
142pub fn derive_tnua_action_slots(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
143 let input = parse_macro_input!(input as DeriveInput);
144 match impl_derive_tnua_action_slots(&input) {
145 Ok(output) => output.into(),
146 Err(error) => error.to_compile_error().into(),
147 }
148}
149
150fn impl_derive_tnua_action_slots(ast: &syn::DeriveInput) -> Result<TokenStream, Error> {
151 generate_action_slots_derive(&ParsedActionSlots::new(ast)?)
152}