1use 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 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 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 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 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 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 for item in arci_enums {
313 let arci_name = &item.ident;
314 let arci_path = match arci_name.to_string().as_str() {
315 "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 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 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
549fn 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 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}