use proc_macro2::{Ident, TokenStream};
use quote::{format_ident, quote};
use crate::context::Context;
use crate::conv;
use crate::models::domain::{Class, ClassLike, ClassSignal, FnParam, ModName, RustTy, TyName};
use crate::util::{ident, safe_ident};
pub struct SignalCodegen {
pub signal_code: TokenStream,
pub has_own_signals: bool,
}
pub fn make_class_signals(
class: &Class,
signals: &[ClassSignal],
ctx: &mut Context,
) -> SignalCodegen {
let all_params: Vec<SignalParams> = signals
.iter()
.map(|s| SignalParams::new(&s.parameters))
.collect();
let class_name = class.name();
let (own_collection_struct, nearest_collection_name, nearest_class, has_own_signals);
if signals.is_empty() {
let nearest = ctx.find_nearest_base_with_signals(class_name);
own_collection_struct = TokenStream::new();
nearest_collection_name = make_collection_name(&nearest);
nearest_class = Some(nearest);
has_own_signals = false;
} else {
let (code, name) = make_signal_collection(class, signals, &all_params);
own_collection_struct = code;
nearest_collection_name = name;
nearest_class = None;
has_own_signals = true;
};
let signal_types = signals
.iter()
.zip(all_params.iter())
.map(|(signal, params)| make_signal_individual_struct(signal, params));
let with_signals_impl =
make_with_signals_impl(class_name, &nearest_collection_name, nearest_class.as_ref());
let deref_impl =
has_own_signals.then(|| make_upcast_deref_impl(class_name, &nearest_collection_name));
let code = quote! {
pub use signals::*;
mod signals {
use crate::obj::{Gd, GodotClass};
use super::re_export::#class_name;
use crate::obj::signal::TypedSignal;
use super::*;
#own_collection_struct
#( #signal_types )*
#with_signals_impl
#deref_impl
}
};
SignalCodegen {
signal_code: code,
has_own_signals,
}
}
fn make_with_signals_impl(
class_name: &TyName,
collection_struct_name: &Ident,
nearest_class: Option<&TyName>, ) -> TokenStream {
let base_use_statement = quote! { use crate::obj::WithSignals; };
let use_statement = if let Some(nearest_class) = nearest_class {
let module_name = ModName::from_godot(&nearest_class.godot_ty);
quote! {
#base_use_statement
use crate::classes::#module_name::#collection_struct_name;
}
} else {
base_use_statement
};
quote! {
#use_statement
impl WithSignals for #class_name {
type SignalCollection<'c, C: WithSignals> = #collection_struct_name<'c, C>;
type __SignalObj<'c> = Gd<Self>;
#[doc(hidden)]
fn __signals_from_external(gd_ref: & Gd<Self>) -> Self::SignalCollection<'_, Self> {
Self::SignalCollection {
__internal_obj: Some(gd_ref.clone()),
}
}
}
}
}
pub fn make_collection_name(class_name: &TyName) -> Ident {
format_ident!("SignalsOf{}", class_name.rust_ty)
}
fn make_individual_struct_name(signal_name: &str) -> Ident {
let signal_pascal_name = conv::to_pascal_case(signal_name);
format_ident!("Sig{}", signal_pascal_name)
}
fn make_signal_collection(
class: &Class,
signals: &[ClassSignal],
params: &[SignalParams],
) -> (TokenStream, Ident) {
debug_assert!(!signals.is_empty());
let class_name = class.name();
let collection_struct_name = make_collection_name(class_name);
let provider_methods = signals.iter().zip(params).map(|(sig, params)| {
let signal_name_str = &sig.name;
let signal_name = ident(&sig.name);
let individual_struct_name = make_individual_struct_name(&sig.name);
let provider_docs = format!("Signature: `({})`", params.formatted_types);
quote! {
#[doc = #provider_docs]
pub fn #signal_name(&mut self) -> #individual_struct_name<'c, C> {
#individual_struct_name {
typed: TypedSignal::extract(&mut self.__internal_obj, #signal_name_str)
}
}
}
});
let collection_docs = format!(
"A collection of signals for the [`{c}`][crate::classes::{c}] class.",
c = class_name.rust_ty
);
let code = quote! {
#[doc = #collection_docs]
pub struct #collection_struct_name<'c, C: WithSignals > {
#[doc(hidden)]
pub(crate) __internal_obj: Option<C::__SignalObj<'c>>,
}
impl<'c, C: WithSignals> #collection_struct_name<'c, C> {
#( #provider_methods )*
}
};
(code, collection_struct_name)
}
fn make_upcast_deref_impl(class_name: &TyName, collection_struct_name: &Ident) -> TokenStream {
if class_name.rust_ty == "Object" {
return TokenStream::new();
}
quote! {
impl<'c, C: WithSignals> std::ops::Deref for #collection_struct_name<'c, C> {
type Target = <
<
#class_name as crate::obj::GodotClass
>::Base as WithSignals
>::SignalCollection<'c, C>;
fn deref(&self) -> &Self::Target {
type Derived = #class_name;
crate::private::signal_collection_to_base::<C, Derived>(self)
}
}
impl<'c, C: WithSignals> std::ops::DerefMut for #collection_struct_name<'c, C> {
fn deref_mut(&mut self) -> &mut Self::Target {
type Derived = #class_name;
crate::private::signal_collection_to_base_mut::<C, Derived>(self)
}
}
}
}
fn make_signal_individual_struct(signal: &ClassSignal, params: &SignalParams) -> TokenStream {
let individual_struct_name = make_individual_struct_name(&signal.name);
let SignalParams {
param_list,
type_list,
name_list,
..
} = params;
let param_tuple = quote! { ( #type_list ) };
let typed_name = format_ident!("Typed{}", individual_struct_name);
quote! {
type #typed_name<'c, C> = TypedSignal<'c, C, #param_tuple>;
pub struct #individual_struct_name<'c, C: WithSignals > {
typed: #typed_name<'c, C>,
}
impl<'c, C: WithSignals> #individual_struct_name<'c, C> {
pub fn emit(&mut self, #param_list) {
self.typed.emit_tuple( (#name_list) );
}
}
impl<'c, C: WithSignals> std::ops::Deref for #individual_struct_name<'c, C> {
type Target = #typed_name<'c, C>;
fn deref(&self) -> &Self::Target {
&self.typed
}
}
impl<C: WithSignals> std::ops::DerefMut for #individual_struct_name<'_, C> {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.typed
}
}
}
}
struct SignalParams {
param_list: TokenStream,
type_list: TokenStream,
name_list: TokenStream,
formatted_types: String,
}
impl SignalParams {
fn new(params: &[FnParam]) -> Self {
use std::fmt::Write;
let mut param_list = TokenStream::new();
let mut type_list = TokenStream::new();
let mut name_list = TokenStream::new();
let mut formatted_types = String::new();
let mut first = true;
for param in params.iter() {
let param_name = safe_ident(¶m.name.to_string());
let param_ty = ¶m.type_;
let param_ty_tokens = param_ty.tokens_non_null();
param_list.extend(quote! { #param_name: #param_ty_tokens, });
type_list.extend(quote! { #param_ty_tokens, });
name_list.extend(quote! { #param_name, });
let formatted_ty = match param_ty {
RustTy::EngineClass { inner_class, .. } => format!("Gd<{inner_class}>"),
other => other.to_string(),
};
if first {
first = false;
} else {
write!(formatted_types, ", ").unwrap();
}
write!(formatted_types, "{param_name}: {formatted_ty}").unwrap();
}
Self {
param_list,
type_list,
name_list,
formatted_types,
}
}
}