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