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 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 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 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 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 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 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 for item in arci_enums {
315 let arci_name = &item.ident;
316 let arci_path = match arci_name.to_string().as_str() {
317 "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 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 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
551fn 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 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}