Skip to main content

wasserxr_macros/
lib.rs

1//! Procedural macros that generate WasserXR's C ABI plugin bindings.
2//!
3//! These macros let plugin authors write normal Rust systems, components, and
4//! asset types while emitting the `wxr_*` functions the runtime resolves from
5//! loaded plugins.
6
7mod asset;
8mod component;
9mod system;
10
11use proc_macro::TokenStream;
12use syn::{Error, ItemFn, ItemStruct, parse_macro_input};
13
14/// Turns a Rust system attach function into the C ABI function WasserXR needs.
15///
16/// Use it on a function with this shape:
17///
18/// ```ignore
19/// #[attacher(my_system)]
20/// fn attach_my_system(scene: &mut wasserxr::scene::Scene) {
21///     // ...
22/// }
23/// ```
24///
25/// The macro exports `wxr_attach_<system>`.
26#[proc_macro_attribute]
27pub fn attacher(args: TokenStream, item: TokenStream) -> TokenStream {
28    let args = parse_macro_input!(args as system::LifecycleArgs);
29    let item = parse_macro_input!(item as ItemFn);
30
31    system::expand_attacher(args, item)
32        .unwrap_or_else(Error::into_compile_error)
33        .into()
34}
35
36/// Turns a Rust system detach function into the C ABI function WasserXR needs.
37///
38/// Use it on a function with this shape:
39///
40/// ```ignore
41/// #[detacher(my_system)]
42/// fn detach_my_system(scene: &mut wasserxr::scene::Scene) {
43///     // ...
44/// }
45/// ```
46///
47/// The macro exports `wxr_detach_<system>`.
48#[proc_macro_attribute]
49pub fn detacher(args: TokenStream, item: TokenStream) -> TokenStream {
50    let args = parse_macro_input!(args as system::LifecycleArgs);
51    let item = parse_macro_input!(item as ItemFn);
52
53    system::expand_detacher(args, item)
54        .unwrap_or_else(Error::into_compile_error)
55        .into()
56}
57
58/// Turns a Rust system function into the C ABI functions WasserXR needs.
59///
60/// Use it on a function with this shape:
61///
62/// ```ignore
63/// #[system(entities = [["Transform", "Mesh"], ["Camera"]])]
64/// fn render(scene: &mut wasserxr::scene::Scene, entities: Vec<Vec<uuid::Uuid>>) {
65///     // ...
66/// }
67/// ```
68///
69/// The macro exports `WXR_GROUPS_<SYSTEM>`, `wxr_select_<system>`, and
70/// `wxr_system_<system>`. The first matching entity group wins.
71#[proc_macro_attribute]
72pub fn system(args: TokenStream, item: TokenStream) -> TokenStream {
73    let args = parse_macro_input!(args as system::Args);
74    let item = parse_macro_input!(item as ItemFn);
75
76    system::expand(args, item)
77        .unwrap_or_else(Error::into_compile_error)
78        .into()
79}
80
81/// Turns a Rust component struct into the C ABI functions WasserXR needs.
82///
83/// Use it on a named-field struct:
84///
85/// ```ignore
86/// #[component(no_schema)]
87/// #[virtual_field(x: f32, getter = custom_x_getter, mutable)]
88/// #[derive(Default)]
89/// struct MyComponent {
90///     value: i32,
91///     #[getter(custom_name_getter)]
92///     #[mutable]
93///     name: String,
94///     #[none]
95///     internal: i32,
96/// }
97/// ```
98///
99/// The macro exports destroy and schema functions for the component.
100/// Use `#[component(no_schema)]` to skip schema generation and provide a custom
101/// `wxr_schema_<Component>` function yourself.
102/// Fields without field function attributes get generated getter, serializer,
103/// and deserializer functions by default. Use `#[mutable]` to allow mutable
104/// references through `Scene::query_mut`. If at least one field function
105/// attribute is present, only the requested functions are generated. Field
106/// function attributes can also take a custom function path, for example
107/// `#[getter(my_getter)]`. Use `#[none]` to register a field without generated
108/// field functions. Generated serializers for complex fields use serde through
109/// bincode, so those field types must implement serde's serialize and
110/// deserialize traits. Use `#[virtual_field(name: Type, getter = my_getter)]`
111/// to register a queryable field that is not stored directly in the struct.
112#[proc_macro_attribute]
113pub fn component(args: TokenStream, item: TokenStream) -> TokenStream {
114    let args = parse_macro_input!(args as component::Args);
115    let item = parse_macro_input!(item as ItemStruct);
116
117    component::expand(args, item)
118        .unwrap_or_else(Error::into_compile_error)
119        .into()
120}
121
122/// Wraps `fn create(scene: &mut Scene) -> Option<Component>` as a component creator.
123///
124/// The macro exports `wxr_create_<Component>` and maps `None` to a null pointer.
125#[proc_macro_attribute]
126pub fn component_creator(args: TokenStream, item: TokenStream) -> TokenStream {
127    let args = parse_macro_input!(args as component::CreatorArgs);
128    let item = parse_macro_input!(item as ItemFn);
129
130    component::expand_component_creator(args, item)
131        .unwrap_or_else(Error::into_compile_error)
132        .into()
133}
134
135/// Turns a Rust asset struct into the C ABI functions WasserXR needs.
136///
137/// The macro exports destroy, schema, and getter functions for the asset type.
138/// Every named field is queryable unless it has `#[none]`.
139#[proc_macro_attribute]
140pub fn asset_type(args: TokenStream, item: TokenStream) -> TokenStream {
141    if !args.is_empty() {
142        return Error::new(
143            proc_macro2::Span::call_site(),
144            "`asset_type` does not support arguments",
145        )
146        .into_compile_error()
147        .into();
148    }
149
150    let item = parse_macro_input!(item as ItemStruct);
151
152    asset::expand_asset_type(item)
153        .unwrap_or_else(Error::into_compile_error)
154        .into()
155}
156
157/// Wraps `fn create(scene: &mut Scene, data: &str) -> Option<AssetType>` as an asset creator.
158///
159/// The macro exports `wxr_asset_create_<AssetType>` and maps `None` or invalid
160/// C strings to a null pointer.
161#[proc_macro_attribute]
162pub fn asset_type_creator(args: TokenStream, item: TokenStream) -> TokenStream {
163    let args = parse_macro_input!(args as asset::CreatorArgs);
164    let item = parse_macro_input!(item as ItemFn);
165
166    asset::expand_asset_type_creator(args, item)
167        .unwrap_or_else(Error::into_compile_error)
168        .into()
169}