ranim_macros/
lib.rs

1mod scene;
2mod utils;
3
4use proc_macro::TokenStream;
5use proc_macro_crate::{FoundCrate, crate_name};
6use proc_macro2::Span;
7use quote::quote;
8use syn::{Data, DeriveInput, Fields, Ident, ItemFn, parse_macro_input};
9
10use crate::scene::parse_scene_attrs;
11
12const RANIM_CRATE_NAME: &str = "ranim";
13
14fn ranim_path() -> proc_macro2::TokenStream {
15    match (
16        crate_name(RANIM_CRATE_NAME),
17        std::env::var("CARGO_CRATE_NAME").as_deref(),
18    ) {
19        (Ok(FoundCrate::Itself), Ok(RANIM_CRATE_NAME)) => quote!(crate),
20        (Ok(FoundCrate::Name(name)), _) => {
21            let ident = Ident::new(&name, Span::call_site());
22            quote!(::#ident)
23        }
24        _ => quote!(::ranim),
25    }
26}
27
28/// 解析单个属性(#[scene(...)] /  / #[output(...)])
29#[derive(Default)]
30struct SceneAttrs {
31    name: Option<String>,        // #[scene(name = "...")]
32    frame_height: Option<f64>,   // #[scene(frame_height = 8.0)]
33    clear_color: Option<String>, // #[scene(clear_color = "#000000")]
34    wasm_demo_doc: bool,         // #[wasm_demo_doc]
35    outputs: Vec<OutputDef>,     // #[output(...)]
36}
37
38/// 一个 #[output(...)] 里的字段
39#[derive(Default)]
40struct OutputDef {
41    width: u32,
42    height: u32,
43    fps: u32,
44    save_frames: bool,
45    dir: String,
46}
47
48// MARK: scene
49#[proc_macro_attribute]
50pub fn scene(args: TokenStream, input: TokenStream) -> TokenStream {
51    let ranim = ranim_path();
52    let input_fn = parse_macro_input!(input as ItemFn);
53    let attrs = parse_scene_attrs(args, input_fn.attrs.as_slice()).unwrap();
54
55    let fn_name = &input_fn.sig.ident;
56    let vis = &input_fn.vis;
57    let fn_body = &input_fn.block;
58    let doc_attrs: Vec<_> = input_fn
59        .attrs
60        .iter()
61        .filter(|attr| attr.path().is_ident("doc"))
62        .collect();
63
64    // 场景名称
65    let scene_name = attrs.name.unwrap_or_else(|| fn_name.to_string());
66
67    // SceneConfig
68    let frame_height = attrs.frame_height.unwrap_or(8.0);
69    let clear_color = attrs.clear_color.unwrap_or("#333333ff".to_string());
70    let scene_config = quote! {
71        #ranim::SceneConfig {
72            frame_height: #frame_height,
73            clear_color: #clear_color,
74        }
75    };
76
77    // Output 列表
78    let mut outputs = Vec::new();
79    for OutputDef {
80        width,
81        height,
82        fps,
83        save_frames,
84        dir,
85    } in attrs.outputs
86    {
87        outputs.push(quote! {
88            #ranim::Output {
89                width: #width,
90                height: #height,
91                fps: #fps,
92                save_frames: #save_frames,
93                dir: #dir,
94            }
95        });
96    }
97    if outputs.is_empty() {
98        outputs.push(quote! {
99            #ranim::Output::DEFAULT
100        });
101    }
102
103    let doc = if attrs.wasm_demo_doc {
104        quote! {
105            #[doc = concat!("<canvas id=\"ranim-app-", stringify!(#fn_name), "\" width=\"1280\" height=\"720\" style=\"width: 100%;\"></canvas>")]
106            #[doc = concat!("<script type=\"module\">")]
107            #[doc = concat!("  const { find_scene, preview_scene } = await ranim_examples;")]
108            #[doc = concat!("  preview_scene(find_scene(\"", stringify!(#fn_name), "\"));")]
109            #[doc = "</script>"]
110        }
111    } else {
112        quote! {}
113    };
114
115    let static_output_name = syn::Ident::new(
116        &format!("__SCENE_{}_OUTPUTS", fn_name.to_string().to_uppercase()),
117        fn_name.span(),
118    );
119    let static_name = syn::Ident::new(
120        &format!("__SCENE_{}", fn_name.to_string().to_uppercase()),
121        fn_name.span(),
122    );
123    let static_scene_name = syn::Ident::new(&format!("{fn_name}_scene"), fn_name.span());
124
125    let output_cnt = outputs.len();
126
127    let scene = quote! {
128        #ranim::Scene {
129            name: #scene_name,
130            constructor: #fn_name,
131            config: #scene_config,
132            outputs: &#static_output_name,
133        }
134    };
135
136    // 构造 Scene 并塞进分布式切片
137    let expanded = quote! {
138        #doc
139        #(#doc_attrs)*
140        #vis fn #fn_name(r: &mut #ranim::RanimScene) #fn_body
141
142        static #static_output_name: [#ranim::Output; #output_cnt] = [#(#outputs),*];
143        #[doc(hidden)]
144        static #static_name: #ranim::Scene = #scene;
145        #ranim::inventory::submit!{
146            #scene
147        }
148
149        #[allow(non_upper_case_globals)]
150        #vis static #static_scene_name: &'static #ranim::Scene = &#static_name;
151    };
152
153    TokenStream::from(expanded)
154}
155
156/// Define a video output.
157///
158/// Default: 1920x1080 60fps, save_frames = false
159///
160/// Available attributes:
161/// - `pixel_size`: (width, height)
162/// - `fps`: frames per second
163/// - `save_frames`: save frames to disk
164/// - `dir`: directory for output
165#[proc_macro_attribute]
166pub fn output(_: TokenStream, _: TokenStream) -> TokenStream {
167    TokenStream::new()
168}
169
170// #[proc_macro_attribute]
171// pub fn preview(_: TokenStream, _: TokenStream) -> TokenStream {
172//     TokenStream::new()
173// }
174
175#[proc_macro_attribute]
176pub fn wasm_demo_doc(_attr: TokenStream, _: TokenStream) -> TokenStream {
177    TokenStream::new()
178}
179
180// MARK: derive Traits
181
182#[proc_macro_derive(Fill)]
183pub fn derive_fill(input: TokenStream) -> TokenStream {
184    impl_derive(
185        input,
186        |ranim| quote! {#ranim::traits::Fill},
187        |ranim, field_positions| {
188            quote! {
189                fn set_fill_opacity(&mut self, opacity: f32) -> &mut Self {
190                    #(
191                        self.#field_positions.set_fill_opacity(opacity);
192                    )*
193                    self
194                }
195                fn fill_color(&self) -> #ranim::color::AlphaColor<#ranim::color::Srgb> {
196                    [#(self.#field_positions.fill_color(), )*].first().cloned().unwrap()
197                }
198                fn set_fill_color(&mut self, color:  #ranim::color::AlphaColor<#ranim::color::Srgb>) -> &mut Self {
199                    #(
200                        self.#field_positions.set_fill_color(color);
201                    )*
202                    self
203                }
204            }
205        },
206    )
207}
208
209#[proc_macro_derive(Stroke)]
210pub fn derive_stroke(input: TokenStream) -> TokenStream {
211    impl_derive(
212        input,
213        |ranim| quote! {#ranim::traits::Stroke},
214        |ranim, field_positions| {
215            quote! {
216                fn stroke_color(&self) -> #ranim::color::AlphaColor<#ranim::color::Srgb> {
217                    [#(self.#field_positions.stroke_color(), )*].first().cloned().unwrap()
218                }
219                fn apply_stroke_func(&mut self, f: impl for<'a> Fn(&'a mut [#ranim::components::width::Width])) -> &mut Self {
220                    #(
221                        self.#field_positions.apply_stroke_func(&f);
222                    )*
223                    self
224                }
225                fn set_stroke_color(&mut self, color: #ranim::color::AlphaColor<#ranim::color::Srgb>) -> &mut Self {
226                    #(
227                        self.#field_positions.set_stroke_color(color);
228                    )*
229                    self
230                }
231                fn set_stroke_opacity(&mut self, opacity: f32) -> &mut Self {
232                    #(
233                        self.#field_positions.set_stroke_opacity(opacity);
234                    )*
235                    self
236                }
237            }
238        },
239    )
240}
241
242#[proc_macro_derive(Partial)]
243pub fn derive_partial(input: TokenStream) -> TokenStream {
244    impl_derive(
245        input,
246        |ranim| quote! {#ranim::traits::Partial},
247        |_ranim, field_positions| {
248            quote! {
249                fn get_partial(&self, range: std::ops::Range<f64>) -> Self {
250                    Self {
251                        #(
252                            #field_positions: self.#field_positions.get_partial(range.clone()),
253                        )*
254                    }
255                }
256                fn get_partial_closed(&self, range: std::ops::Range<f64>) -> Self {
257                    Self {
258                        #(
259                            #field_positions: self.#field_positions.get_partial(range.clone()),
260                        )*
261                    }
262                }
263            }
264        },
265    )
266}
267
268#[proc_macro_derive(Empty)]
269pub fn derive_empty(input: TokenStream) -> TokenStream {
270    let ranim = ranim_path();
271    let input = parse_macro_input!(input as DeriveInput);
272    let name = &input.ident;
273    let generics = &input.generics;
274    let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
275
276    let fields = match &input.data {
277        Data::Struct(data) => &data.fields,
278        _ => panic!("Empty can only be derived for structs"),
279    };
280
281    let field_impls = match fields {
282        Fields::Named(fields) => {
283            let (field_names, field_types): (Vec<_>, Vec<_>) =
284                fields.named.iter().map(|f| (&f.ident, &f.ty)).unzip();
285
286            quote! {
287                Self {
288                    #(
289                        #field_names: #field_types::empty(),
290                    )*
291                }
292            }
293        }
294        Fields::Unnamed(fields) => {
295            let field_types = fields.unnamed.iter().map(|f| &f.ty);
296            quote! {
297                Self (
298                    #(
299                        #field_types::empty(),
300                    )*
301                )
302            }
303        }
304        Fields::Unit => quote! {},
305    };
306
307    let expanded = quote! {
308        impl #impl_generics #ranim::traits::Empty for #name #ty_generics #where_clause {
309            fn empty() -> Self {
310                #field_impls
311            }
312        }
313    };
314
315    TokenStream::from(expanded)
316}
317
318#[proc_macro_derive(Opacity)]
319pub fn derive_opacity(input: TokenStream) -> TokenStream {
320    impl_derive(
321        input,
322        |ranim| quote! {#ranim::traits::Opacity},
323        |_ranim, field_positions| {
324            quote! {
325                fn set_opacity(&mut self, opacity: f32) -> &mut Self {
326                    #(
327                        self.#field_positions.set_opacity(opacity);
328                    )*
329                    self
330                }
331            }
332        },
333    )
334}
335
336#[proc_macro_derive(Alignable)]
337pub fn derive_alignable(input: TokenStream) -> TokenStream {
338    impl_derive(
339        input,
340        |ranim| quote! {#ranim::traits::Alignable},
341        |_ranim, field_positions| {
342            quote! {
343                fn is_aligned(&self, other: &Self) -> bool {
344                    #(
345                        self.#field_positions.is_aligned(&other.#field_positions) &&
346                    )* true
347                }
348                fn align_with(&mut self, other: &mut Self) {
349                    #(
350                        self.#field_positions.align_with(&mut other.#field_positions);
351                    )*
352                }
353            }
354        },
355    )
356}
357
358#[proc_macro_derive(Interpolatable)]
359pub fn derive_interpolatable(input: TokenStream) -> TokenStream {
360    impl_derive(
361        input,
362        |ranim| quote! {#ranim::traits::Interpolatable},
363        |ranim, field_positions| {
364            quote! {
365                fn lerp(&self, other: &Self, t: f64) -> Self {
366                    Self {
367                        #(
368                            #field_positions: #ranim::traits::Interpolatable::lerp(&self.#field_positions, &other.#field_positions, t),
369                        )*
370                    }
371                }
372            }
373        },
374    )
375}
376
377#[proc_macro_derive(BoundingBox)]
378pub fn derive_bounding_box(input: TokenStream) -> TokenStream {
379    let ranim = ranim_path();
380    let input = parse_macro_input!(input as DeriveInput);
381    let name = &input.ident;
382    let generics = &input.generics;
383    let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
384
385    let fields = match &input.data {
386        Data::Struct(data) => &data.fields,
387        _ => panic!("Can only be derived for structs"),
388    };
389
390    let field_positions = get_field_positions(fields)
391        .ok_or("cannot get field from unit struct")
392        .unwrap();
393
394    let expanded = quote! {
395        impl #impl_generics #ranim::traits::BoundingBox for #name #ty_generics #where_clause {
396            fn get_bounding_box(&self) -> [DVec3; 3] {
397                let [min, max] = [#(self.#field_positions.get_bounding_box(), )*]
398                    .into_iter()
399                    .map(|[min, _, max]| [min, max])
400                    .reduce(|[acc_min, acc_max], [min, max]| [acc_min.min(min), acc_max.max(max)])
401                    .unwrap();
402                [min, (min + max) / 2.0, max]
403            }
404        }
405    };
406
407    TokenStream::from(expanded)
408}
409
410#[proc_macro_derive(Position)]
411pub fn derive_position(input: TokenStream) -> TokenStream {
412    impl_derive(
413        input,
414        |ranim| {
415            quote! {#ranim::traits::Position}
416        },
417        |ranim, field_positions| {
418            quote! {
419                fn shift(&mut self, shift: DVec3) -> &mut Self {
420                    #(self.#field_positions.shift(shift);)*
421                    self
422                }
423
424                fn rotate_by_anchor(&mut self, angle: f64, axis: #ranim::glam::DVec3, anchor: #ranim::components::Anchor) -> &mut Self {
425                    #(self.#field_positions.rotate_by_anchor(angle, axis, anchor);)*
426                    self
427                }
428
429                fn scale_by_anchor(&mut self, scale: #ranim::glam::DVec3, anchor: #ranim::components::Anchor) -> &mut Self {
430                    #(self.#field_positions.scale_by_anchor(scale, anchor);)*
431                    self
432                }
433            }
434        },
435    )
436}
437
438#[proc_macro_derive(PointsFunc)]
439pub fn derive_point_func(input: TokenStream) -> TokenStream {
440    impl_derive(
441        input,
442        |ranim| {
443            quote! {#ranim::traits::PointsFunc}
444        },
445        |_ranim, field_positions| {
446            quote! {
447                fn apply_points_func(&mut self, f: impl for<'a> Fn(&'a mut [DVec3])) -> &mut Self {
448                    #(self.#field_positions.apply_points_func(f);)*
449                    self
450                }
451            }
452        },
453    )
454}
455
456fn impl_derive(
457    input: TokenStream,
458    trait_path: impl Fn(&proc_macro2::TokenStream) -> proc_macro2::TokenStream,
459    impl_token: impl Fn(
460        &proc_macro2::TokenStream,
461        Vec<proc_macro2::TokenStream>,
462    ) -> proc_macro2::TokenStream,
463) -> TokenStream {
464    let ranim = ranim_path();
465    let input = parse_macro_input!(input as DeriveInput);
466    let name = &input.ident;
467    let generics = &input.generics;
468    let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
469
470    let fields = match &input.data {
471        Data::Struct(data) => &data.fields,
472        _ => panic!("Can only be derived for structs"),
473    };
474
475    let field_positions = get_field_positions(fields)
476        .ok_or("cannot get field from unit struct")
477        .unwrap();
478
479    let trait_path = trait_path(&ranim);
480    let impl_token = impl_token(&ranim, field_positions);
481    let expanded = quote! {
482        impl #impl_generics #trait_path for #name #ty_generics #where_clause {
483            #impl_token
484        }
485    };
486
487    TokenStream::from(expanded)
488}
489
490fn get_field_positions(fields: &Fields) -> Option<Vec<proc_macro2::TokenStream>> {
491    match fields {
492        Fields::Named(fields) => Some(
493            fields
494                .named
495                .iter()
496                .map(|f| {
497                    let pos = &f.ident;
498                    quote! { #pos }
499                })
500                .collect::<Vec<_>>(),
501        ),
502        Fields::Unnamed(fields) => Some(
503            (0..fields.unnamed.len())
504                .map(syn::Index::from)
505                .map(|i| {
506                    quote! { #i }
507                })
508                .collect::<Vec<_>>(),
509        ),
510        Fields::Unit => None,
511    }
512}