Skip to main content

godot_bevy_macros/
lib.rs

1mod bevy_bundle;
2mod godot_node;
3mod node_tree_view;
4
5use crate::godot_node::derive_godot_node;
6use proc_macro::TokenStream;
7use quote::quote;
8use syn::parse::Parser;
9use syn::{DeriveInput, Error, parse_macro_input};
10
11#[proc_macro_attribute]
12pub fn bevy_app(attr: TokenStream, item: TokenStream) -> TokenStream {
13    let input_fn = parse_macro_input!(item as syn::ItemFn);
14    let name = &input_fn.sig.ident;
15
16    // Parse attribute for configuration options
17    let config = if !attr.is_empty() {
18        match parse_bevy_app_config(attr) {
19            Ok(cfg) => cfg,
20            Err(err) => return err.into_compile_error().into(),
21        }
22    } else {
23        BevyAppConfig::default()
24    };
25
26    let scene_tree_auto_despawn_children = config.scene_tree_auto_despawn_children;
27
28    let expanded = quote! {
29        struct BevyExtensionLibrary;
30
31        #[gdextension]
32        unsafe impl ExtensionLibrary for BevyExtensionLibrary {
33            fn on_level_init(level: godot::prelude::InitLevel) {
34                if level == godot::prelude::InitLevel::Core {
35
36                    // Store the scene tree configuration
37                    let _ = godot_bevy::app::BEVY_APP_CONFIG.set(godot_bevy::app::BevyAppConfig {
38                        scene_tree_auto_despawn_children: #scene_tree_auto_despawn_children,
39                    });
40
41                    // Stores the client's entrypoint, which we'll call shortly when our `BevyApp`
42                    // Godot Node has its `ready()` invoked
43                    let _ = godot_bevy::app::BEVY_INIT_FUNC.get_or_init(|| Box::new(#name));
44
45                    // Initialize profiling (Tracy or other backends)
46                    // This function handles all feature gating internally
47                    godot_bevy::profiling::init_profiler();
48                }
49            }
50
51
52            fn on_level_deinit(_level: godot::prelude::InitLevel) {
53                if _level == godot::prelude::InitLevel::Core {
54                    // Shutdown profiling cleanly
55                    // This function handles all feature gating internally
56                    godot_bevy::profiling::shutdown_profiler();
57                }
58            }
59        }
60
61        #input_fn
62    };
63
64    expanded.into()
65}
66
67struct BevyAppConfig {
68    scene_tree_auto_despawn_children: bool,
69}
70
71impl Default for BevyAppConfig {
72    fn default() -> Self {
73        Self {
74            scene_tree_auto_despawn_children: true,
75        }
76    }
77}
78
79fn parse_bevy_app_config(attr: TokenStream) -> Result<BevyAppConfig, Error> {
80    let mut config = BevyAppConfig::default();
81    let parser = syn::meta::parser(|meta| {
82        if meta.path.is_ident("scene_tree_auto_despawn_children") {
83            config.scene_tree_auto_despawn_children = meta.value()?.parse::<syn::LitBool>()?.value;
84            Ok(())
85        } else if meta.path.is_ident("scene_tree_add_child_relationship") {
86            Err(meta.error(
87                "scene_tree_add_child_relationship was removed; use scene_tree_auto_despawn_children",
88            ))
89        } else {
90            Err(meta.error("unsupported bevy_app attribute"))
91        }
92    });
93
94    parser.parse(attr)?;
95    Ok(config)
96}
97
98/// Derive this macro on a struct for easy access to a scene's nodes.
99///
100/// Example:
101/// ```ignore
102/// #[derive(NodeTreeView)]
103/// pub struct MenuUi {
104///     #[node("/root/Main/HUD/Message")]
105///     pub message_label: GodotNodeHandle,
106/// }
107/// ```
108/// Node paths can be specified with patterns:
109/// - `/root/*/HUD/CurrentLevel` - matches any single node name where * appears
110/// - `/root/Level*/HUD/CurrentLevel` - matches node names starting with "Level"
111/// - `*/HUD/CurrentLevel` - matches relative to the base node
112///
113/// See `godot_bevy::node_tree_view::find_node_by_pattern` for details on how nodes are found.
114///
115/// Supported field types are:
116/// - `GodotNodeHandle`: `from_node()` returns `NodeTreeViewError` if the node is not found.
117/// - `Option<GodotNodeHandle>`: Filled with `None` if the node is not found.
118///
119/// For each field annotated with `#[node(<path>)]`, a companion string constant is generated
120/// containing that path. The constant name is `<UPPERCASE_FIELD_NAME>_PATH`, and it is defined
121/// in the struct that derives `NodeTreeView`.
122///
123/// Example:
124/// ```ignore
125/// #[derive(NodeTreeView)]
126/// pub struct MobNodes {
127///     #[node("AnimatedSprite2D")]
128///     animated_sprite: GodotNodeHandle,
129///
130///     #[node("Node2D/*/VisibleOnScreenNotifier2D")]
131///     visibility_notifier: GodotNodeHandle,
132/// }
133/// /// Generated companion string constants:
134/// impl MobNodes {
135///     pub const ANIMATED_SPRITE_PATH: &'static str = "AnimatedSprite2D";
136///     pub const VISIBILITY_NOTIFIER_PATH: &'static str = "Node2D/*/VisibleOnScreenNotifier2D";
137/// }
138/// ```
139#[proc_macro_derive(NodeTreeView, attributes(node))]
140pub fn derive_node_tree_view(item: TokenStream) -> TokenStream {
141    let view = parse_macro_input!(item as DeriveInput);
142
143    let expanded = node_tree_view::node_tree_view(view).unwrap_or_else(Error::into_compile_error);
144
145    TokenStream::from(expanded)
146}
147
148#[proc_macro_derive(BevyBundle, attributes(bevy_bundle))]
149pub fn derive_bevy_bundle(item: TokenStream) -> TokenStream {
150    let input = parse_macro_input!(item as DeriveInput);
151
152    let expanded = bevy_bundle::bevy_bundle(input).unwrap_or_else(Error::into_compile_error);
153
154    TokenStream::from(expanded)
155}
156
157/// # Generates a Godot Node from a Bevy Component or Bevy Bundle
158///
159/// A struct level attribute can be used to specify the Godot class to extend, and the class name:
160///
161/// ```ignore
162/// #[godot_node(base(<godot_node_type>), class_name(<custom_class_name>))]
163/// ```
164///
165/// - `base` (Default: `Node`) Godot node to extend.
166/// - `class_name` (Default: `<struct_name>BevyComponent`) Name of generated Godot class.
167///
168/// ## Annotating structs that derive `Bundle`
169///
170/// Bundle component fields can be annotated with `#[export_fields(...)]` to expose them to Godot.
171/// The `export_fields` attribute takes a list of component field entries:
172/// - Struct component fields: `field_name(export_type(Type), transform_with(path::to::fn), default(expr))`
173/// - Tuple/newtype components: `value(export_type(Type), transform_with(path::to::fn), default(expr))`
174///
175/// Each entry can take optional parameters to configure how it will be exported. See
176/// the [export configuration attributes](#export-configuration-attributes) for details.
177///
178/// Example syntax:
179///
180/// ```ignore
181/// #[export_fields(
182///     <field1_name>(
183///         export_type(<godot_type>),
184///         transform_with(<conversion_function>),
185///         default(<value>)
186///     ),
187///     <field2_name>(...),
188///     ...
189/// )]
190/// ```
191///
192/// ## Annotating structs that derive `Component`
193///
194/// Component fields can be exposed to Godot as node properties using the `#[godot_export]` attribute.
195/// The attribute syntax is:
196///
197/// ```ignore
198/// #[godot_export(export_type(<godot_type>), transform_with(<conversion_function>), default(<value>))]
199/// ```
200///
201/// See the [export configuration attributes for](#export-configuration-attributes)
202/// for export parameter details.
203///
204/// ## Export configuration attributes
205///
206/// For fields with types incompatible with Godot-Rust's `#[export]` macro:
207/// - Use `export_type` to specify an alternate Godot-compatible type
208/// - Use `transform_with` to provide a conversion function from the Godot type to the field type
209/// - Use `default` to provide an initial value to the exported Godot field.
210#[proc_macro_derive(GodotNode, attributes(godot_export, godot_node, export_fields))]
211pub fn component_as_godot_node(input: TokenStream) -> TokenStream {
212    let parsed: DeriveInput = parse_macro_input!(input as DeriveInput);
213    derive_godot_node(parsed)
214        .unwrap_or_else(Error::into_compile_error)
215        .into()
216}