openrr_internal_codegen/
plugin.rs

1//! Codegen for openrr-plugin
2
3use std::{mem, path::Path};
4
5use anyhow::Result;
6use fs_err as fs;
7use heck::ToSnakeCase;
8use proc_macro2::TokenStream;
9use quote::{format_ident, quote};
10use syn::{
11    Ident, parse_quote,
12    visit_mut::{self, VisitMut},
13};
14
15use super::*;
16
17pub(crate) fn r#gen(workspace_root: &Path) -> Result<()> {
18    const FULLY_IGNORE: &[&str] = &["SetCompleteCondition"];
19    const IGNORE: &[&str] = &["SetCompleteCondition"];
20    const USE_TRY_INTO: &[&str] = &["SystemTime"];
21
22    let out_dir = &workspace_root.join("openrr-plugin/src/gen");
23    fs::create_dir_all(out_dir)?;
24    let mut api_items = TokenStream::new();
25    let mut proxy_impls = TokenStream::new();
26    let mut trait_names = vec![];
27    let (arci_traits, arci_structs, arci_enums) = arci_types(workspace_root)?;
28    for item in arci_traits {
29        let name = &&*item.ident.to_string();
30        if FULLY_IGNORE.contains(name) {
31            continue;
32        }
33        trait_names.push(item.ident.clone());
34        if IGNORE.contains(name) {
35            continue;
36        }
37
38        let trait_name = &item.ident;
39        let mut is_async_trait = false;
40        let mut arci_method_impl = vec![];
41        let mut sabi_method_def = vec![];
42        let mut sabi_method_impl = vec![];
43        for method in &item.items {
44            if let syn::TraitItem::Fn(method) = method {
45                let sig = &method.sig;
46                let method_name = &sig.ident;
47                let is_async_method = sig.asyncness.is_some();
48                if is_async_method {
49                    is_async_trait = true;
50                }
51                let mut has_receiver = false;
52                let mut arci_args = vec![];
53                let mut sabi_args = vec![];
54                for arg in &sig.inputs {
55                    match arg {
56                        syn::FnArg::Receiver(_) => {
57                            has_receiver = true;
58                            sabi_args.push(if is_async_trait {
59                                if is_async_method {
60                                    quote! { &*this }
61                                } else {
62                                    quote! { &**self }
63                                }
64                            } else {
65                                quote! { self }
66                            });
67                        }
68                        syn::FnArg::Typed(arg) => {
69                            let pat = &arg.pat;
70                            if let Some(path) = get_ty_path(&arg.ty)
71                                && USE_TRY_INTO
72                                    .contains(&&*path.segments.last().unwrap().ident.to_string())
73                            {
74                                arci_args.push(quote! { #pat.try_into()? });
75                                sabi_args.push(quote! { rtry!(#pat.try_into()) });
76                                continue;
77                            }
78                            if matches!(&*arg.ty, syn::Type::Reference(_)) && !is_str(&arg.ty) {
79                                arci_args.push(quote! { (*#pat).into() });
80                                sabi_args.push(quote! { &#pat.into() });
81                                continue;
82                            }
83                            let t = if is_vec(&arg.ty).is_some_and(|ty| !is_primitive(ty)) {
84                                quote! { #pat.into_iter().map(Into::into).collect() }
85                            } else {
86                                quote! { #pat.into() }
87                            };
88                            arci_args.push(t.clone());
89                            sabi_args.push(t);
90                        }
91                    }
92                }
93                let (return_result, is_vec) = match &method.sig.output {
94                    syn::ReturnType::Type(_, ty) => match is_result(ty) {
95                        Some(ty) => (true, is_vec(ty).is_some_and(|ty| !is_primitive(ty))),
96                        None => (false, is_vec(ty).is_some_and(|ty| !is_primitive(ty))),
97                    },
98                    syn::ReturnType::Default => (false, false),
99                };
100                let into = if is_vec {
101                    quote! { .into_iter().map(Into::into).collect() }
102                } else {
103                    quote! { .into() }
104                };
105                {
106                    // impl arci::<trait_name> for <trait_name>Proxy
107                    let mut call = quote! { self.0.#method_name(#(#arci_args),*) };
108                    if is_async_method {
109                        call = quote! { #call.await };
110                    }
111                    call = if return_result {
112                        quote! { Ok(#call.into_result()?#into) }
113                    } else {
114                        quote! { #call #into }
115                    };
116                    arci_method_impl.push(quote! {
117                        #sig { #call }
118                    });
119                }
120                {
121                    // impl R<trait_name>Trait for (impl arci::<trait_name>)
122                    let mut sig = sig.clone();
123                    sig.asyncness = None;
124                    ReplacePath.visit_signature_mut(&mut sig);
125                    if is_async_method {
126                        sig.output = match &sig.output {
127                            syn::ReturnType::Type(_, ty) => parse_quote! { -> FfiFuture<#ty> },
128                            syn::ReturnType::Default => parse_quote! { -> FfiFuture<()> },
129                        };
130                    }
131                    sabi_method_def.push(quote! { #sig; });
132                    let mut call = quote! { arci::#trait_name::#method_name(#(#sabi_args),*) };
133                    if is_async_method {
134                        call = quote! { #call.await };
135                    }
136                    call = if return_result {
137                        quote! { ROk(rtry!(#call)#into) }
138                    } else {
139                        quote! { #call #into }
140                    };
141                    if is_async_method {
142                        call = quote! { async move { #call }.into_ffi() };
143                        if has_receiver {
144                            call = quote! {
145                                let this = self.clone();
146                                #call
147                            };
148                        }
149                    }
150                    sabi_method_impl.push(quote! {
151                        #sig {
152                            let _guard = crate::TOKIO.enter();
153                            #call
154                        }
155                    });
156                }
157            }
158        }
159
160        // impl arci::<trait_name> for <trait_name>Proxy
161        let proxy_name = format_ident!("{trait_name}Proxy");
162        let proxy_name_lit = proxy_name.to_string();
163        let proxy_type = gen_proxy_type(trait_name, quote! { arci:: }, is_async_trait);
164        let async_trait = if is_async_trait {
165            quote! { #[arci::async_trait] }
166        } else {
167            quote! {}
168        };
169        api_items.extend(quote! {
170            #proxy_type
171            #async_trait
172            impl arci::#trait_name for #proxy_name {
173                #(#arci_method_impl)*
174            }
175            impl std::fmt::Debug for #proxy_name {
176                fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
177                    f.debug_struct(#proxy_name_lit).finish()
178                }
179            }
180        });
181
182        // impl R<trait_name>Trait for (impl arci::<trait_name>)
183        let trait_object_name = format_ident!("{trait_name}TraitObject");
184        let sabi_trait_name = format_ident!("R{trait_name}Trait");
185        let sabi_trait_trait_object_name = format_ident!("{sabi_trait_name}_TO");
186        let this = if is_async_trait {
187            quote! { Arc<T> }
188        } else {
189            quote! { T }
190        };
191        let clone = if is_async_trait {
192            quote! { Clone + }
193        } else {
194            quote! {}
195        };
196        let sized = if is_async_trait {
197            quote! { ?Sized + }
198        } else {
199            quote! {}
200        };
201        proxy_impls.extend(quote! {
202            pub(crate) type #trait_object_name = #sabi_trait_trait_object_name<RBox<()>>;
203            #[abi_stable::sabi_trait]
204            pub(crate) trait #sabi_trait_name: Send + Sync + #clone 'static {
205                #(#sabi_method_def)*
206            }
207            impl<T> #sabi_trait_name for #this
208            where
209                T: #sized arci::#trait_name + 'static
210            {
211                #(#sabi_method_impl)*
212            }
213        });
214    }
215    fn map_field(
216        pats: &mut Vec<TokenStream>,
217        from_arci: &mut Vec<TokenStream>,
218        to_arci: &mut Vec<TokenStream>,
219        syn::Field { ident, ty, .. }: &syn::Field,
220        index: usize,
221    ) {
222        let index_or_ident = ident
223            .clone()
224            .unwrap_or_else(|| format_ident!("field{}", index));
225        let pat = quote! { #index_or_ident, };
226        pats.push(pat.clone());
227        if is_primitive(ty) {
228            from_arci.push(pat.clone());
229            to_arci.push(pat);
230            return;
231        }
232        if let Some(ty) = is_option(ty)
233            && let Some(ty) = is_vec(ty)
234        {
235            let t = if is_primitive(ty) {
236                quote! { map(|v| v.into_iter().collect()) }
237            } else {
238                quote! { map(|v| v.into_iter().map(Into::into).collect()) }
239            };
240            let mut to = quote! { #index_or_ident.into_option().#t, };
241            let mut from = quote! { #index_or_ident.#t.into(), };
242            if ident.is_some() {
243                from = quote! { #ident: #from };
244                to = quote! { #ident: #to };
245            }
246            from_arci.push(from);
247            to_arci.push(to);
248            return;
249        }
250        if let Some(ty) = is_vec(ty) {
251            let mut t = if is_primitive(ty) {
252                quote! { #index_or_ident.into_iter().collect(), }
253            } else {
254                quote! { #index_or_ident.into_iter().map(Into::into).collect(), }
255            };
256            if ident.is_some() {
257                t = quote! { #ident: #t };
258            }
259            from_arci.push(t.clone());
260            to_arci.push(t);
261            return;
262        }
263        let mut t = quote! { #index_or_ident.into(), };
264        if ident.is_some() {
265            t = quote! { #ident: #t };
266        }
267        from_arci.push(t.clone());
268        to_arci.push(t);
269    }
270    // Generate R* structs.
271    for item in arci_structs {
272        let arci_name = &item.ident;
273        let arci_path = quote! { arci::#arci_name };
274        let r_name = format_ident!("R{}", item.ident);
275        let struct_doc = format!(" FFI-safe equivalent of [`arci::{arci_name}`].");
276        let fields = item
277            .fields
278            .iter()
279            .cloned()
280            .map(|syn::Field { ident, mut ty, .. }| {
281                ReplacePath.visit_type_mut(&mut ty);
282                quote! { #ident: #ty, }
283            });
284        let mut pats = vec![];
285        let mut from_arci = vec![];
286        let mut to_arci = vec![];
287        for (i, field) in item.fields.iter().enumerate() {
288            map_field(&mut pats, &mut from_arci, &mut to_arci, field, i);
289        }
290        proxy_impls.extend(quote! {
291            #[doc = #struct_doc]
292            #[derive(StableAbi)]
293            #[repr(C)]
294            pub(crate) struct #r_name {
295                #(#fields)*
296            }
297            impl From<#arci_path> for #r_name {
298                fn from(v: #arci_path) -> Self {
299                    let #arci_path { #(#pats)* } = v;
300                    Self { #(#from_arci)* }
301                }
302            }
303            impl From<#r_name> for #arci_path {
304                fn from(v: #r_name) -> Self {
305                    let #r_name { #(#pats)* } = v;
306                    Self { #(#to_arci)* }
307                }
308            }
309        });
310    }
311    // Generate R* enums.
312    for item in arci_enums {
313        let arci_name = &item.ident;
314        let arci_path = match arci_name.to_string().as_str() {
315            // TODO: auto-determine current module
316            "Button" | "Axis" | "GamepadEvent" => quote! { arci::gamepad::#arci_name },
317            _ => quote! { arci::#arci_name },
318        };
319        let r_name = format_ident!("R{}", item.ident);
320        let enum_doc = format!(" FFI-safe equivalent of [`arci::{arci_name}`].");
321        let mut variants = vec![];
322        let mut from_arci = vec![];
323        let mut to_arci = vec![];
324        for v in &item.variants {
325            let mut pats_fields = vec![];
326            let mut from_arci_fields = vec![];
327            let mut to_arci_fields = vec![];
328            for (i, field) in v.fields.iter().enumerate() {
329                map_field(
330                    &mut pats_fields,
331                    &mut from_arci_fields,
332                    &mut to_arci_fields,
333                    field,
334                    i,
335                );
336            }
337            match &v.fields {
338                syn::Fields::Named(..) => {
339                    let fields =
340                        v.fields
341                            .iter()
342                            .cloned()
343                            .map(|syn::Field { ident, mut ty, .. }| {
344                                ReplacePath.visit_type_mut(&mut ty);
345                                quote! { #ident: #ty, }
346                            });
347                    let ident = &v.ident;
348                    variants.extend(quote! { #ident { #(#fields)* }, });
349                    from_arci.extend(
350                        quote! { #arci_path::#ident { #(#pats_fields)* } => Self::#ident { #(#from_arci_fields)* }, },
351                    );
352                    to_arci.extend(
353                        quote! { #r_name::#ident { #(#pats_fields)* } => Self::#ident { #(#to_arci_fields)* }, },
354                    );
355                }
356                syn::Fields::Unnamed(..) => {
357                    let fields = v.fields.iter().cloned().map(|syn::Field { mut ty, .. }| {
358                        ReplacePath.visit_type_mut(&mut ty);
359                        quote! { #ty, }
360                    });
361                    let ident = &v.ident;
362                    variants.extend(quote! { #ident(#(#fields)*), });
363                    from_arci.extend(
364                        quote! { #arci_path::#ident( #(#pats_fields)* ) => Self::#ident( #(#from_arci_fields)* ), },
365                    );
366                    to_arci.extend(
367                        quote! { #r_name::#ident( #(#pats_fields)* ) => Self::#ident( #(#to_arci_fields)* ), },
368                    );
369                }
370                syn::Fields::Unit => {
371                    let ident = &v.ident;
372                    variants.extend(quote! { #ident, });
373                    from_arci.extend(quote! { #arci_path::#ident => Self::#ident, });
374                    to_arci.extend(quote! { #r_name::#ident => Self::#ident, });
375                }
376            }
377        }
378        proxy_impls.extend(quote! {
379            #[doc = #enum_doc]
380            #[derive(StableAbi)]
381            #[repr(C)]
382            pub(crate) enum #r_name {
383                #(#variants)*
384            }
385            impl From<#arci_path> for #r_name {
386                fn from(v: #arci_path) -> Self {
387                    match v {
388                        #(#from_arci)*
389                    }
390                }
391            }
392            impl From<#r_name> for #arci_path {
393                fn from(v: #r_name) -> Self {
394                    match v {
395                        #(#to_arci)*
396                    }
397                }
398            }
399        });
400    }
401
402    let (plugin_trait_api, plugin_trait_proxy) = gen_plugin_trait(&trait_names);
403    let api = quote! {
404        // TODO: remove the need for `arci::` imports.
405        use abi_stable::StableAbi;
406        use arci::{
407            gamepad::GamepadEvent,
408            BaseVelocity,
409            Error,
410            Isometry2,
411            Isometry3,
412            Scan2D,
413            TrajectoryPoint,
414            WaitFuture,
415        };
416        use super::*;
417        #plugin_trait_api
418        #api_items
419    };
420    let proxy = quote! {
421        use abi_stable::{
422            rtry,
423            std_types::{RBox, RDuration, ROk, RResult, RStr},
424        };
425        use super::*;
426        #plugin_trait_proxy
427        #proxy_impls
428    };
429
430    write(&out_dir.join("api.rs"), api)?;
431    write(&out_dir.join("proxy.rs"), proxy)?;
432    Ok(())
433}
434
435struct ReplacePath;
436impl VisitMut for ReplacePath {
437    fn visit_type_mut(&mut self, ty: &mut syn::Type) {
438        if is_primitive(ty) {
439            return;
440        }
441        if is_str(ty) {
442            *ty = parse_quote!(RStr<'_>);
443            return;
444        }
445        if let syn::Type::Reference(t) = ty {
446            *ty = mem::replace(&mut *t.elem, syn::Type::Verbatim(TokenStream::new()));
447        }
448
449        visit_mut::visit_type_mut(self, ty);
450    }
451
452    fn visit_path_mut(&mut self, path: &mut syn::Path) {
453        let mut last = path.segments.pop().unwrap().into_value();
454        if last.ident.to_string().starts_with("Isometry") {
455            last.arguments = syn::PathArguments::None;
456            last.ident = format_ident!("{}F64", last.ident);
457        }
458        last.ident = format_ident!("R{}", last.ident);
459        path.segments.clear();
460        path.segments.push(last);
461        visit_mut::visit_path_mut(self, path);
462    }
463
464    fn visit_fn_arg_mut(&mut self, arg: &mut syn::FnArg) {
465        match arg {
466            syn::FnArg::Receiver(_) => {}
467            syn::FnArg::Typed(arg) => self.visit_pat_type_mut(arg),
468        }
469    }
470}
471
472fn gen_plugin_trait(traits: &[Ident]) -> (TokenStream, TokenStream) {
473    let mut plugin_method_def = vec![];
474    let mut plugin_method_impl = vec![];
475    let mut sabi_plugin_method_def = vec![];
476    let mut sabi_plugin_method_impl = vec![];
477    for trait_name in traits {
478        let method_name = format_ident!("new_{}", trait_name.to_string().to_snake_case());
479        let proxy_name = format_ident!("{trait_name}Proxy");
480        let new_doc = format!(
481            " Creates a new instance of [`arci::{trait_name}`] with the specified arguments.",
482        );
483        plugin_method_def.push(quote! {
484            #[doc = #new_doc]
485            fn #method_name(
486                &self,
487                args: String,
488            ) -> Result<Option<Box<dyn arci::#trait_name>>, arci::Error> {
489                let _ = args;
490                Ok(None)
491            }
492        });
493        plugin_method_impl.push(quote! {
494            #[doc = #new_doc]
495            pub fn #method_name(
496                &self,
497                args: String,
498            ) -> Result<Option<#proxy_name>, arci::Error> {
499                Ok(self.0.#method_name(args.into()).into_result()?.into_option())
500            }
501        });
502
503        let sabi_method_sig = quote! {
504            fn #method_name(
505                &self,
506                args: RString,
507            ) -> RResult<ROption<crate::#proxy_name>, RError>
508        };
509        sabi_plugin_method_def.push(quote! {
510            #sabi_method_sig;
511        });
512        sabi_plugin_method_impl.push(quote! {
513            #sabi_method_sig {
514                ROk(rtry!(crate::Plugin::#method_name(self, args.into()))
515                    .map(crate::#proxy_name::new)
516                    .into())
517            }
518        });
519    }
520    let proxy_type = gen_proxy_type(&format_ident!("Plugin"), quote! {}, false);
521    let api = quote! {
522        /// The plugin trait.
523        pub trait Plugin: Send + Sync + 'static {
524            #(#plugin_method_def)*
525        }
526        #proxy_type
527        impl PluginProxy {
528            #(#plugin_method_impl)*
529        }
530    };
531    let proxy = quote! {
532        pub(crate) type PluginTraitObject = RPluginTrait_TO<RBox<()>>;
533
534        #[sabi_trait]
535        pub(crate) trait RPluginTrait: Send + Sync + 'static {
536            #(#sabi_plugin_method_def)*
537        }
538
539        impl<T> RPluginTrait for T
540        where
541            T: crate::Plugin,
542        {
543            #(#sabi_plugin_method_impl)*
544        }
545    };
546    (api, proxy)
547}
548
549// Generate *Proxy type.
550fn gen_proxy_type(
551    trait_name: &Ident,
552    prefix_path: TokenStream,
553    is_async_trait: bool,
554) -> TokenStream {
555    let proxy_name = format_ident!("{trait_name}Proxy");
556    let trait_object_name = format_ident!("{trait_name}TraitObject");
557    let new_doc = format!(" Creates a new `{proxy_name}`.");
558    let struct_doc = format!(
559        " FFI-safe equivalent of [`Box<dyn {0}{trait_name}>`]({0}{trait_name}).",
560        prefix_path.to_string().replace(' ', ""),
561    );
562    // Don't implement Clone even if it is async trait -- use of Arc is implementation detail.
563    let inner = if is_async_trait {
564        quote! { Arc::new(inner) }
565    } else {
566        quote! { inner }
567    };
568    quote! {
569        #[doc = #struct_doc]
570        #[derive(StableAbi)]
571        #[repr(C)]
572        pub struct #proxy_name(pub(crate) crate::proxy::#trait_object_name);
573
574        impl #proxy_name {
575            #[doc = #new_doc]
576            pub fn new<T>(inner: T) -> Self
577            where
578                T: #prefix_path #trait_name + 'static
579            {
580                Self(crate::proxy::#trait_object_name::from_value(
581                    #inner,
582                    abi_stable::erased_types::TD_Opaque,
583                ))
584            }
585        }
586    }
587}