|
| 1 | +/* |
| 2 | + * Copyright (c) godot-rust; Bromeon and contributors. |
| 3 | + * This Source Code Form is subject to the terms of the Mozilla Public |
| 4 | + * License, v. 2.0. If a copy of the MPL was not distributed with this |
| 5 | + * file, You can obtain one at https://mozilla.org/MPL/2.0/. |
| 6 | + */ |
| 7 | + |
| 8 | +// Code duplication: while there is some overlap with godot-macros/signal.rs for #[signal] handling, this file here is quite a bit simpler, |
| 9 | +// as it only deals with predefined signal definitions (no user-defined #[cfg], visibility, etc). On the other hand, there is documentation |
| 10 | +// for these signals, and integration is slightly different due to lack of WithBaseField trait. Nonetheless, some parts could potentially |
| 11 | +// be extracted into a future crate shared by godot-codegen and godot-macros. |
| 12 | + |
| 13 | +use crate::context::Context; |
| 14 | +use crate::conv; |
| 15 | +use crate::models::domain::{Class, ClassLike, ClassSignal, FnParam, RustTy, TyName}; |
| 16 | +use crate::util::{ident, safe_ident}; |
| 17 | +use proc_macro2::{Ident, TokenStream}; |
| 18 | +use quote::{format_ident, quote}; |
| 19 | + |
| 20 | +pub fn make_class_signals( |
| 21 | + class: &Class, |
| 22 | + signals: &[ClassSignal], |
| 23 | + _ctx: &mut Context, |
| 24 | +) -> Option<TokenStream> { |
| 25 | + if signals.is_empty() { |
| 26 | + return None; |
| 27 | + } |
| 28 | + |
| 29 | + let all_params: Vec<SignalParams> = signals |
| 30 | + .iter() |
| 31 | + .map(|s| SignalParams::new(&s.parameters)) |
| 32 | + .collect(); |
| 33 | + |
| 34 | + let signal_collection_struct = make_signal_collection(class, signals, &all_params); |
| 35 | + |
| 36 | + let signal_types = signals |
| 37 | + .iter() |
| 38 | + .zip(all_params.iter()) |
| 39 | + .map(|(signal, params)| make_signal_individual_struct(signal, params)); |
| 40 | + |
| 41 | + let class_name = class.name(); |
| 42 | + |
| 43 | + Some(quote! { |
| 44 | + #[cfg(since_api = "4.2")] |
| 45 | + pub use signals::*; |
| 46 | + |
| 47 | + #[cfg(since_api = "4.2")] |
| 48 | + mod signals { |
| 49 | + use crate::obj::Gd; |
| 50 | + use super::re_export::#class_name; |
| 51 | + use super::*; |
| 52 | + |
| 53 | + #signal_collection_struct |
| 54 | + #( #signal_types )* |
| 55 | + } |
| 56 | + }) |
| 57 | +} |
| 58 | + |
| 59 | +// Used outside, to document class with links to this type. |
| 60 | +pub fn make_collection_name(class_name: &TyName) -> Ident { |
| 61 | + format_ident!("SignalsIn{}", class_name.rust_ty) |
| 62 | +} |
| 63 | + |
| 64 | +fn make_individual_struct_name(signal_name: &str) -> Ident { |
| 65 | + let signal_pascal_name = conv::to_pascal_case(signal_name); |
| 66 | + format_ident!("Sig{}", signal_pascal_name) |
| 67 | +} |
| 68 | + |
| 69 | +fn make_signal_collection( |
| 70 | + class: &Class, |
| 71 | + signals: &[ClassSignal], |
| 72 | + params: &[SignalParams], |
| 73 | +) -> TokenStream { |
| 74 | + let class_name = class.name(); |
| 75 | + let collection_struct_name = make_collection_name(class_name); |
| 76 | + |
| 77 | + let provider_methods = signals.iter().zip(params).map(|(sig, params)| { |
| 78 | + let signal_name_str = &sig.name; |
| 79 | + let signal_name = ident(&sig.name); |
| 80 | + let individual_struct_name = make_individual_struct_name(&sig.name); |
| 81 | + let provider_docs = format!("Signature: `({})`", params.formatted_types); |
| 82 | + |
| 83 | + quote! { |
| 84 | + // Important to return lifetime 'c here, not '_. |
| 85 | + #[doc = #provider_docs] |
| 86 | + pub fn #signal_name(&mut self) -> #individual_struct_name<'c> { |
| 87 | + #individual_struct_name { |
| 88 | + typed: crate::registry::signal::TypedSignal::new(self.__gd.clone(), #signal_name_str) |
| 89 | + } |
| 90 | + } |
| 91 | + } |
| 92 | + }); |
| 93 | + |
| 94 | + let collection_docs = format!( |
| 95 | + "A collection of signals for the [`{c}`][crate::classes::{c}] class.", |
| 96 | + c = class_name.rust_ty |
| 97 | + ); |
| 98 | + |
| 99 | + quote! { |
| 100 | + #[doc = #collection_docs] |
| 101 | + pub struct #collection_struct_name<'c> { |
| 102 | + __gd: &'c mut Gd<#class_name>, |
| 103 | + } |
| 104 | + |
| 105 | + impl<'c> #collection_struct_name<'c> { |
| 106 | + #( #provider_methods )* |
| 107 | + } |
| 108 | + |
| 109 | + impl crate::obj::WithSignals for #class_name { |
| 110 | + type SignalCollection<'c> = #collection_struct_name<'c>; |
| 111 | + #[doc(hidden)] |
| 112 | + type __SignalObject<'c> = Gd<#class_name>; |
| 113 | + |
| 114 | + #[doc(hidden)] |
| 115 | + fn __signals_from_external(external: &mut Gd<Self>) -> Self::SignalCollection<'_> { |
| 116 | + Self::SignalCollection { |
| 117 | + __gd: external, |
| 118 | + } |
| 119 | + } |
| 120 | + } |
| 121 | + } |
| 122 | +} |
| 123 | + |
| 124 | +fn make_signal_individual_struct(signal: &ClassSignal, params: &SignalParams) -> TokenStream { |
| 125 | + let individual_struct_name = make_individual_struct_name(&signal.name); |
| 126 | + |
| 127 | + let SignalParams { |
| 128 | + param_list, |
| 129 | + type_list, |
| 130 | + name_list, |
| 131 | + .. |
| 132 | + } = params; |
| 133 | + |
| 134 | + let class_name = &signal.surrounding_class; |
| 135 | + let class_ty = quote! { #class_name }; |
| 136 | + let param_tuple = quote! { ( #type_list ) }; |
| 137 | + let typed_name = format_ident!("Typed{}", individual_struct_name); |
| 138 | + |
| 139 | + // Embedded in `mod signals`. |
| 140 | + quote! { |
| 141 | + // Reduce tokens to parse by reusing this type definitions. |
| 142 | + type #typed_name<'c> = crate::registry::signal::TypedSignal<'c, #class_ty, #param_tuple>; |
| 143 | + |
| 144 | + pub struct #individual_struct_name<'c> { |
| 145 | + typed: #typed_name<'c>, |
| 146 | + } |
| 147 | + |
| 148 | + impl<'c> #individual_struct_name<'c> { |
| 149 | + pub fn emit(&mut self, #param_list) { |
| 150 | + self.typed.emit_tuple( (#name_list) ); |
| 151 | + } |
| 152 | + } |
| 153 | + |
| 154 | + impl<'c> std::ops::Deref for #individual_struct_name<'c> { |
| 155 | + type Target = #typed_name<'c>; |
| 156 | + |
| 157 | + fn deref(&self) -> &Self::Target { |
| 158 | + &self.typed |
| 159 | + } |
| 160 | + } |
| 161 | + |
| 162 | + impl std::ops::DerefMut for #individual_struct_name<'_> { |
| 163 | + fn deref_mut(&mut self) -> &mut Self::Target { |
| 164 | + &mut self.typed |
| 165 | + } |
| 166 | + } |
| 167 | + } |
| 168 | +} |
| 169 | + |
| 170 | +// ---------------------------------------------------------------------------------------------------------------------------------------------- |
| 171 | + |
| 172 | +struct SignalParams { |
| 173 | + /// `name: Type, ...` |
| 174 | + param_list: TokenStream, |
| 175 | + |
| 176 | + /// `Type, ...` -- for example inside a tuple type. |
| 177 | + type_list: TokenStream, |
| 178 | + |
| 179 | + /// `name, ...` -- for example inside a tuple value. |
| 180 | + name_list: TokenStream, |
| 181 | + |
| 182 | + /// `"name: Type, ..."` in nice format. |
| 183 | + formatted_types: String, |
| 184 | +} |
| 185 | + |
| 186 | +impl SignalParams { |
| 187 | + fn new(params: &[FnParam]) -> Self { |
| 188 | + use std::fmt::Write; |
| 189 | + |
| 190 | + let mut param_list = TokenStream::new(); |
| 191 | + let mut type_list = TokenStream::new(); |
| 192 | + let mut name_list = TokenStream::new(); |
| 193 | + let mut formatted_types = String::new(); |
| 194 | + let mut first = true; |
| 195 | + |
| 196 | + for param in params.iter() { |
| 197 | + let param_name = safe_ident(¶m.name.to_string()); |
| 198 | + let param_ty = ¶m.type_; |
| 199 | + |
| 200 | + param_list.extend(quote! { #param_name: #param_ty, }); |
| 201 | + type_list.extend(quote! { #param_ty, }); |
| 202 | + name_list.extend(quote! { #param_name, }); |
| 203 | + |
| 204 | + let formatted_ty = match param_ty { |
| 205 | + RustTy::EngineClass { inner_class, .. } => format!("Gd<{inner_class}>"), |
| 206 | + other => other.to_string(), |
| 207 | + }; |
| 208 | + |
| 209 | + if first { |
| 210 | + first = false; |
| 211 | + } else { |
| 212 | + write!(formatted_types, ", ").unwrap(); |
| 213 | + } |
| 214 | + |
| 215 | + write!(formatted_types, "{}: {}", param_name, formatted_ty).unwrap(); |
| 216 | + } |
| 217 | + |
| 218 | + Self { |
| 219 | + param_list, |
| 220 | + type_list, |
| 221 | + name_list, |
| 222 | + formatted_types, |
| 223 | + } |
| 224 | + } |
| 225 | +} |
0 commit comments