embassy_agb_macros/
lib.rs

1use proc_macro::TokenStream;
2use quote::quote;
3use syn::{parse_macro_input, ItemFn, ReturnType, Type};
4
5/// Main entry point for embassy-agb async applications
6///
7/// This macro creates an async main function that runs on the embassy executor.
8/// The function must take a single `Spawner` parameter and can be async.
9///
10/// # Example
11///
12/// ```rust,no_run
13/// #![no_std]
14/// #![no_main]
15///
16/// use embassy_agb::time::Timer;
17/// use embassy_executor::Spawner;
18///
19/// #[embassy_agb::main]
20/// async fn main(spawner: Spawner) {
21///     let gba = embassy_agb::init(Default::default());
22///     
23///     // Your async game code here
24///     loop {
25///         Timer::after_millis(16).await; // 60 FPS
26///     }
27/// }
28/// ```
29#[proc_macro_attribute]
30pub fn main(_args: TokenStream, input: TokenStream) -> TokenStream {
31    let f = parse_macro_input!(input as ItemFn);
32
33    // Validate function signature
34    if f.sig.inputs.len() != 1 {
35        return quote! {
36            compile_error!("embassy_agb::main function must take exactly one parameter: Spawner");
37        }
38        .into();
39    }
40
41    // Check return type
42    let returns_never =
43        matches!(f.sig.output, ReturnType::Type(_, ref ty) if matches!(**ty, Type::Never(_)));
44
45    let fn_name = &f.sig.ident;
46    let fn_args = &f.sig.inputs;
47    let fn_body = &f.block;
48    let fn_attrs = &f.attrs;
49
50    // Extract the spawner parameter name
51    let spawner_param = match f.sig.inputs.first() {
52        Some(syn::FnArg::Typed(pat_type)) => match &*pat_type.pat {
53            syn::Pat::Ident(ident) => &ident.ident,
54            _ => panic!("Expected spawner parameter to be a simple identifier"),
55        },
56        _ => panic!("Expected spawner parameter"),
57    };
58
59    let result = if returns_never {
60        // Function returns ! - run forever
61        quote! {
62            #[::embassy_agb::agb::entry]
63            fn agb_main(gba: ::embassy_agb::agb::Gba) -> ! {
64                // Store the gba instance globally so embassy-agb can access it
65                unsafe {
66                    ::embassy_agb::_internal::set_agb_instance(gba);
67                }
68
69                unsafe fn __make_static<T>(t: &mut T) -> &'static mut T {
70                    ::core::mem::transmute(t)
71                }
72
73                let mut executor = ::embassy_agb::Executor::new();
74                let executor = unsafe { __make_static(&mut executor) };
75                executor.run(|spawner| {
76                    spawner.must_spawn(main_task(spawner));
77                });
78            }
79
80            #[::embassy_executor::task]
81            async fn main_task(#fn_args) -> ! {
82                #(#fn_attrs)*
83                async fn #fn_name(#fn_args) -> ! #fn_body
84                #fn_name(#spawner_param).await
85            }
86        }
87    } else {
88        // Function returns () - run and then loop
89        quote! {
90            #[::embassy_agb::agb::entry]
91            fn agb_main(gba: ::embassy_agb::agb::Gba) -> ! {
92                // Store the gba instance globally so embassy-agb can access it
93                unsafe {
94                    ::embassy_agb::_internal::set_agb_instance(gba);
95                }
96
97                unsafe fn __make_static<T>(t: &mut T) -> &'static mut T {
98                    ::core::mem::transmute(t)
99                }
100
101                let mut executor = ::embassy_agb::Executor::new();
102                let executor = unsafe { __make_static(&mut executor) };
103                executor.run(|spawner| {
104                    spawner.must_spawn(main_task(spawner));
105                });
106            }
107
108            #[::embassy_executor::task]
109            async fn main_task(#fn_args) {
110                #(#fn_attrs)*
111                async fn #fn_name(#fn_args) #fn_body
112                #fn_name(#spawner_param).await;
113
114                // If main returns, just halt forever
115                loop {
116                    ::embassy_agb::agb::halt();
117                }
118            }
119        }
120    };
121
122    result.into()
123}
124
125/// Task macro for embassy-agb
126///
127/// This is a re-export of the embassy_executor::task macro for convenience.
128#[proc_macro_attribute]
129pub fn task(args: TokenStream, input: TokenStream) -> TokenStream {
130    // Just pass through to embassy_executor::task with fully qualified path
131    let args = proc_macro2::TokenStream::from(args);
132    let input = proc_macro2::TokenStream::from(input);
133
134    quote! {
135        #[::embassy_executor::task(#args)]
136        #input
137    }
138    .into()
139}