1extern crate proc_macro;
2use proc_macro::TokenStream;
3use quote::{format_ident, quote};
4use std::collections::HashMap;
5use syn::{
6 Expr, Ident, ItemFn, ItemStruct, Lit, Meta, Token, Type,
7 parse::{Parse, ParseStream, Result as SynResult},
8 parse_macro_input,
9};
10
11#[proc_macro_attribute]
21pub fn standalone_module(_args: TokenStream, item: TokenStream) -> TokenStream {
22 let input = parse_macro_input!(item as ItemFn);
23
24 let rusty_name = input.sig.ident.clone();
25 let executor_name = format_ident!(
26 "{}_executor_do_not_use_or_you_will_be_fired",
27 input.sig.ident
28 );
29
30 let output = quote! {
31 #input
32
33 #[allow(non_upper_case_globals)]
36 static mut #executor_name: ::msfs::StandaloneModuleExecutor = ::msfs::StandaloneModuleExecutor {
37 executor: ::msfs::executor::Executor {
38 handle: |m| std::boxed::Box::pin(#rusty_name(m)),
39 future: None,
40 tx: None,
41 },
42 };
43
44 #[unsafe(no_mangle)]
45 pub extern "C" fn module_init() {
46 unsafe {
47 ::msfs::wrap_executor(&raw mut #executor_name, |e| e.handle_init());
48 }
49 }
50
51 #[unsafe(no_mangle)]
52 pub extern "C" fn module_deinit() {
53 unsafe {
54 ::msfs::wrap_executor(&raw mut #executor_name, |e| e.handle_deinit());
55 }
56 }
57 };
58
59 TokenStream::from(output)
60}
61
62struct GaugeArgs {
63 name: Option<String>,
64}
65
66impl Parse for GaugeArgs {
67 fn parse(input: ParseStream) -> SynResult<Self> {
68 match input.parse::<Ident>() {
69 Ok(i) if i == "name" => {
70 input.parse::<Token![=]>()?;
71 Ok(GaugeArgs {
72 name: Some(input.parse::<Ident>()?.to_string()),
73 })
74 }
75 _ => Ok(GaugeArgs { name: None }),
76 }
77 }
78}
79
80#[proc_macro_attribute]
99pub fn gauge(args: TokenStream, item: TokenStream) -> TokenStream {
100 let args = parse_macro_input!(args as GaugeArgs);
101 let input = parse_macro_input!(item as ItemFn);
102
103 let rusty_name = input.sig.ident.clone();
104 let executor_name = format_ident!(
105 "{}_executor_do_not_use_or_you_will_be_fired",
106 input.sig.ident
107 );
108
109 let extern_name = args.name.unwrap_or_else(|| input.sig.ident.to_string());
110 let extern_gauge_name = format_ident!("{}_gauge_callback", extern_name);
111 let extern_mouse_name = format_ident!("{}_mouse_callback", extern_name);
112
113 let output = quote! {
114 #input
115
116 #[allow(non_upper_case_globals)]
119 static mut #executor_name: ::msfs::GaugeExecutor = ::msfs::GaugeExecutor {
120 fs_ctx: None,
121 executor: ::msfs::executor::Executor {
122 handle: |gauge| std::boxed::Box::pin(#rusty_name(gauge)),
123 tx: None,
124 future: None,
125 },
126 };
127
128 #[doc(hidden)]
129 #[unsafe(no_mangle)]
130 pub extern "C" fn #extern_gauge_name(
131 ctx: ::msfs::sys::FsContext,
132 service_id: std::os::raw::c_int,
133 p_data: *mut std::os::raw::c_void,
134 ) -> bool {
135 unsafe {
136 ::msfs::wrap_executor(&raw mut #executor_name, |e| e.handle_gauge(ctx, service_id, p_data))
137 }
138 }
139
140 #[doc(hidden)]
141 #[unsafe(no_mangle)]
142 pub extern "C" fn #extern_mouse_name(
143 fx: std::os::raw::c_float,
144 fy: std::os::raw::c_float,
145 i_flags: std::os::raw::c_uint,
146 ) {
147 unsafe {
148 ::msfs::wrap_executor(&raw mut #executor_name, |e| e.handle_mouse(fx, fy, i_flags));
149 }
150 }
151 };
152
153 TokenStream::from(output)
154}
155
156fn parse_struct_fields(
157 input: &mut ItemStruct,
158 attributes: &[&str],
159 get_type: Option<fn(&str) -> &str>,
160) -> Vec<HashMap<String, String>> {
161 let mut data = Vec::new();
162
163 for (i, field) in &mut input.fields.iter_mut().enumerate() {
164 let mut meta = HashMap::new();
165
166 meta.insert(
167 "field_name".to_string(),
168 if let Some(ident) = &field.ident {
169 ident.to_string()
170 } else {
171 i.to_string()
172 },
173 );
174
175 if let Some(get_type) = get_type {
176 let ty = match &field.ty {
177 Type::Path(p) => p.path.get_ident().unwrap().to_string(),
178 _ => panic!("Unsupported type"),
179 };
180
181 meta.insert("type".to_string(), get_type(ty.as_str()).to_string());
182 } else {
183 let t = &field.ty;
184 meta.insert("type".to_string(), quote!(#t).to_string());
185 }
186
187 let mut attrs = Vec::new();
188 for a in &field.attrs {
189 let simish = if let Some(i) = a.path().get_ident() {
190 attributes.contains(&i.to_string().as_str())
191 } else {
192 false
193 };
194 if simish {
195 let (name, value) = match &a.meta {
196 Meta::NameValue(mnv) => {
197 let name = mnv.path.get_ident().unwrap().to_string();
198 let value = match &mnv.value {
199 Expr::Lit(l) => match &l.lit {
200 Lit::Str(s) => s.value(),
201 Lit::Float(f) => f.base10_digits().to_string(),
202 _ => panic!("argument must be a string or float"),
203 },
204 _ => panic!("argument must be a string or float"),
205 };
206 (name, value)
207 }
208 _ => panic!("attribute must be in for #[name = \"value\"]"),
209 };
210
211 meta.insert(name, value);
212 } else {
213 attrs.push(a.clone());
214 }
215 }
216 field.attrs = attrs;
217
218 data.push(meta);
219 }
220
221 data
222}
223
224#[proc_macro_attribute]
242pub fn sim_connect_data_definition(_args: TokenStream, item: TokenStream) -> TokenStream {
243 let mut input = parse_macro_input!(item as ItemStruct);
244 let name = input.ident.clone();
245
246 let data = parse_struct_fields(
247 &mut input,
248 &["name", "unit", "epsilon"],
249 Some(|ty| match ty {
250 "bool" => "INT32",
251 "i32" => "INT32",
252 "i64" => "INT64",
253 "f32" => "FLOAT32",
254 "f64" => "FLOAT64",
255 "DataXYZ" => "XYZ",
256 _ => panic!("Unsupported type {}", ty),
257 }),
258 );
259
260 let mut array = String::from("&[\n");
261 for meta in data {
262 let name = meta["name"].clone();
263 let unit = meta
264 .get("unit")
265 .unwrap_or_else(|| panic!("{} needs a #[unit] decorator", name));
266
267 let fallback = "0.0".to_string();
268 let epsilon = meta.get("epsilon").unwrap_or(&fallback);
269
270 let ty = meta["type"].clone();
271 array += &format!(
272 " ({name:?}, {unit:?}, {epsilon}, ::msfs::sys::SIMCONNECT_DATATYPE_SIMCONNECT_DATATYPE_{ty}),\n"
273 );
274 }
275 array += "]";
276 let array = syn::parse_str::<Expr>(&array).unwrap();
277
278 let output = quote! {
279 #[repr(C)]
280 #input
281
282 impl ::msfs::sim_connect::DataDefinition for #name {
283 const DEFINITIONS: &'static [(&'static str, &'static str, f32, ::msfs::sys::SIMCONNECT_DATATYPE)] = #array;
284 }
285 };
286
287 TokenStream::from(output)
288}
289
290#[proc_macro_attribute]
301pub fn sim_connect_client_data_definition(_args: TokenStream, item: TokenStream) -> TokenStream {
302 let mut input = parse_macro_input!(item as ItemStruct);
303 let name = input.ident.clone();
304
305 let data = parse_struct_fields(&mut input, &["epsilon"], None);
306
307 let mut array = String::from("vec![\n");
308
309 for meta in data {
310 let fallback = "0.0".to_string();
311 let epsilon = meta.get("epsilon").unwrap_or(&fallback);
312
313 array += &format!(
314 " (unsafe {{
315 let uninit = std::mem::MaybeUninit::<{struct_name}>::uninit();
316 let base = uninit.as_ptr() as *const {struct_name};
317 let field = &((*base).{field_name}) as *const _;
318 (field as usize) - (base as usize)
319 }}, std::mem::size_of::<{type}>(), {epsilon}),
320 ",
321 struct_name=name, field_name=meta["field_name"], type=meta["type"], epsilon=epsilon,
322 );
323 }
324
325 array += "]";
326
327 let array = syn::parse_str::<Expr>(&array).unwrap();
328 let output = quote! {
329 #input
330
331 impl ::msfs::sim_connect::ClientDataDefinition for #name {
332 fn get_definitions() -> Vec<(usize, usize, f32)> { #array }
333 }
334 };
335
336 TokenStream::from(output)
337}