Skip to content

Commit c4b74c8

Browse files
authored
Merge pull request #1134 from godot-rust/feature/signal-collection-inheritance
Inherited typed signals
2 parents f883763 + 270bf9c commit c4b74c8

File tree

16 files changed

+818
-162
lines changed

16 files changed

+818
-162
lines changed

godot-codegen/src/context.rs

+23
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ pub struct Context<'a> {
2525
inheritance_tree: InheritanceTree,
2626
cached_rust_types: HashMap<GodotTy, RustTy>,
2727
notifications_by_class: HashMap<TyName, Vec<(Ident, i32)>>,
28+
classes_with_signals: HashSet<TyName>,
2829
notification_enum_names_by_class: HashMap<TyName, NotificationEnum>,
2930
method_table_indices: HashMap<MethodTableKey, usize>,
3031
method_table_next_index: HashMap<String, usize>,
@@ -66,6 +67,10 @@ impl<'a> Context<'a> {
6667
// Populate class lookup by name
6768
engine_classes.insert(class_name.clone(), class);
6869

70+
if !option_as_slice(&class.signals).is_empty() {
71+
ctx.classes_with_signals.insert(class_name.clone());
72+
}
73+
6974
// Populate derived-to-base relations
7075
if let Some(base) = class.inherits.as_ref() {
7176
let base_name = TyName::from_godot(base);
@@ -278,6 +283,24 @@ impl<'a> Context<'a> {
278283
self.cached_rust_types.get(ty)
279284
}
280285

286+
/// Walks up in the hierarchy, and returns the first (nearest) base class which declares at least 1 signal.
287+
///
288+
/// Always returns a result, as `Object` (the root) itself declares signals.
289+
pub fn find_nearest_base_with_signals(&self, class_name: &TyName) -> TyName {
290+
let tree = self.inheritance_tree();
291+
292+
let mut class = class_name.clone();
293+
while let Some(base) = tree.direct_base(&class) {
294+
if self.classes_with_signals.contains(&base) {
295+
return base;
296+
} else {
297+
class = base;
298+
}
299+
}
300+
301+
panic!("Object (root) should always have signals")
302+
}
303+
281304
pub fn notification_constants(&'a self, class_name: &TyName) -> Option<&'a Vec<(Ident, i32)>> {
282305
self.notifications_by_class.get(class_name)
283306
}

godot-codegen/src/generator/classes.rs

+7-4
Original file line numberDiff line numberDiff line change
@@ -123,7 +123,10 @@ fn make_class(class: &Class, ctx: &mut Context, view: &ApiView) -> GeneratedClas
123123
builders,
124124
} = make_class_methods(class, &class.methods, &cfg_attributes, ctx);
125125

126-
let signal_types = signals::make_class_signals(class, &class.signals, ctx);
126+
let signals::SignalCodegen {
127+
signal_code,
128+
has_own_signals,
129+
} = signals::make_class_signals(class, &class.signals, ctx);
127130

128131
let enums = enums::make_enums(&class.enums, &cfg_attributes);
129132
let constants = constants::make_constants(&class.constants);
@@ -137,14 +140,14 @@ fn make_class(class: &Class, ctx: &mut Context, view: &ApiView) -> GeneratedClas
137140
// Associated "sidecar" module is made public if there are other symbols related to the class, which are not
138141
// in top-level godot::classes module (notification enums are not in the sidecar, but in godot::classes::notify).
139142
// This checks if token streams (i.e. code) is empty.
140-
let has_sidecar_module = !enums.is_empty() || !builders.is_empty() || signal_types.is_some();
143+
let has_sidecar_module = !enums.is_empty() || !builders.is_empty() || has_own_signals;
141144

142145
let class_doc = docs::make_class_doc(
143146
class_name,
144147
base_ident_opt,
145148
notification_enum.is_some(),
146149
has_sidecar_module,
147-
signal_types.is_some(),
150+
has_own_signals,
148151
);
149152

150153
let module_doc = docs::make_module_doc(class_name);
@@ -262,7 +265,7 @@ fn make_class(class: &Class, ctx: &mut Context, view: &ApiView) -> GeneratedClas
262265

263266
#builders
264267
#enums
265-
#signal_types
268+
#signal_code
266269
};
267270
// note: TypePtr -> ObjectPtr conversion OK?
268271

godot-codegen/src/generator/signals.rs

+132-35
Original file line numberDiff line numberDiff line change
@@ -12,48 +12,121 @@
1212

1313
use crate::context::Context;
1414
use crate::conv;
15-
use crate::models::domain::{Class, ClassLike, ClassSignal, FnParam, RustTy, TyName};
15+
use crate::models::domain::{Class, ClassLike, ClassSignal, FnParam, ModName, RustTy, TyName};
1616
use crate::util::{ident, safe_ident};
1717
use proc_macro2::{Ident, TokenStream};
1818
use quote::{format_ident, quote};
1919

20+
pub struct SignalCodegen {
21+
pub signal_code: TokenStream,
22+
pub has_own_signals: bool,
23+
}
24+
2025
pub fn make_class_signals(
2126
class: &Class,
2227
signals: &[ClassSignal],
23-
_ctx: &mut Context,
24-
) -> Option<TokenStream> {
25-
if signals.is_empty() {
26-
return None;
27-
}
28-
28+
ctx: &mut Context,
29+
) -> SignalCodegen {
2930
let all_params: Vec<SignalParams> = signals
3031
.iter()
3132
.map(|s| SignalParams::new(&s.parameters))
3233
.collect();
3334

34-
let signal_collection_struct = make_signal_collection(class, signals, &all_params);
35+
let class_name = class.name();
36+
37+
// If no signals are defined in current class, walk up until we find some.
38+
let (own_collection_struct, nearest_collection_name, nearest_class, has_own_signals);
39+
if signals.is_empty() {
40+
// Use the nearest base class that *has* signals, and store its collection name.
41+
let nearest = ctx.find_nearest_base_with_signals(class_name);
42+
43+
// Doesn't define own collection struct if no signals are present (note that WithSignals is still implemented).
44+
own_collection_struct = TokenStream::new();
45+
nearest_collection_name = make_collection_name(&nearest);
46+
nearest_class = Some(nearest);
47+
has_own_signals = false;
48+
} else {
49+
let (code, name) = make_signal_collection(class, signals, &all_params);
50+
51+
own_collection_struct = code;
52+
nearest_collection_name = name;
53+
nearest_class = None;
54+
has_own_signals = true;
55+
};
3556

3657
let signal_types = signals
3758
.iter()
3859
.zip(all_params.iter())
3960
.map(|(signal, params)| make_signal_individual_struct(signal, params));
4061

41-
let class_name = class.name();
62+
let with_signals_impl =
63+
make_with_signals_impl(class_name, &nearest_collection_name, nearest_class.as_ref());
4264

43-
Some(quote! {
65+
let deref_impl =
66+
has_own_signals.then(|| make_upcast_deref_impl(class_name, &nearest_collection_name));
67+
68+
let code = quote! {
4469
#[cfg(since_api = "4.2")]
4570
pub use signals::*;
4671

4772
#[cfg(since_api = "4.2")]
4873
mod signals {
49-
use crate::obj::Gd;
74+
use crate::obj::{Gd, GodotClass};
5075
use super::re_export::#class_name;
76+
use crate::registry::signal::TypedSignal;
5177
use super::*;
5278

53-
#signal_collection_struct
79+
// These may be empty if the class doesn't define any signals itself.
80+
#own_collection_struct
5481
#( #signal_types )*
82+
83+
// These are always present.
84+
#with_signals_impl
85+
#deref_impl
5586
}
56-
})
87+
};
88+
89+
SignalCodegen {
90+
signal_code: code,
91+
has_own_signals,
92+
}
93+
}
94+
95+
/// Creates `impl WithSignals`.
96+
///
97+
/// Present for every single class, as every class has at least inherited signals (since `Object` has some).
98+
fn make_with_signals_impl(
99+
class_name: &TyName,
100+
collection_struct_name: &Ident,
101+
nearest_class: Option<&TyName>, // None if own class has signals.
102+
) -> TokenStream {
103+
let base_use_statement = quote! { use crate::obj::WithSignals; };
104+
let use_statement = if let Some(nearest_class) = nearest_class {
105+
let module_name = ModName::from_godot(&nearest_class.godot_ty);
106+
quote! {
107+
#base_use_statement
108+
use crate::classes::#module_name::#collection_struct_name;
109+
}
110+
} else {
111+
base_use_statement
112+
};
113+
114+
quote! {
115+
#use_statement
116+
impl WithSignals for #class_name {
117+
type SignalCollection<'c, C: WithSignals> = #collection_struct_name<'c, C>;
118+
type __SignalObj<'c> = Gd<Self>;
119+
// type __SignalObj<'c, C: WithSignals> = Gd<Self>;
120+
121+
// During construction, C = Self.
122+
#[doc(hidden)]
123+
fn __signals_from_external(gd_mut: &mut Gd<Self>) -> Self::SignalCollection<'_, Self> {
124+
Self::SignalCollection {
125+
__internal_obj: Some(gd_mut.clone()),
126+
}
127+
}
128+
}
129+
}
57130
}
58131

59132
// Used outside, to document class with links to this type.
@@ -70,7 +143,9 @@ fn make_signal_collection(
70143
class: &Class,
71144
signals: &[ClassSignal],
72145
params: &[SignalParams],
73-
) -> TokenStream {
146+
) -> (TokenStream, Ident) {
147+
debug_assert!(!signals.is_empty()); // checked outside
148+
74149
let class_name = class.name();
75150
let collection_struct_name = make_collection_name(class_name);
76151

@@ -83,9 +158,9 @@ fn make_signal_collection(
83158
quote! {
84159
// Important to return lifetime 'c here, not '_.
85160
#[doc = #provider_docs]
86-
pub fn #signal_name(&mut self) -> #individual_struct_name<'c> {
161+
pub fn #signal_name(&mut self) -> #individual_struct_name<'c, C> {
87162
#individual_struct_name {
88-
typed: crate::registry::signal::TypedSignal::new(self.__gd.clone(), #signal_name_str)
163+
typed: TypedSignal::extract(&mut self.__internal_obj, #signal_name_str)
89164
}
90165
}
91166
}
@@ -96,26 +171,48 @@ fn make_signal_collection(
96171
c = class_name.rust_ty
97172
);
98173

99-
quote! {
174+
let code = quote! {
100175
#[doc = #collection_docs]
101-
pub struct #collection_struct_name<'c> {
102-
__gd: &'c mut Gd<#class_name>,
176+
// C is needed for signals of derived classes that are upcast via Deref; C in that class is the derived class.
177+
pub struct #collection_struct_name<'c, C: WithSignals = #class_name>
178+
{
179+
#[doc(hidden)]
180+
pub(crate) __internal_obj: Option<C::__SignalObj<'c>>,
103181
}
104182

105-
impl<'c> #collection_struct_name<'c> {
183+
impl<'c, C: WithSignals> #collection_struct_name<'c, C> {
106184
#( #provider_methods )*
107185
}
186+
};
108187

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>;
188+
(code, collection_struct_name)
189+
}
113190

114-
#[doc(hidden)]
115-
fn __signals_from_external(external: &mut Gd<Self>) -> Self::SignalCollection<'_> {
116-
Self::SignalCollection {
117-
__gd: external,
118-
}
191+
fn make_upcast_deref_impl(class_name: &TyName, collection_struct_name: &Ident) -> TokenStream {
192+
// Root of hierarchy, no "upcast" derefs.
193+
if class_name.rust_ty == "Object" {
194+
return TokenStream::new();
195+
}
196+
197+
quote! {
198+
impl<'c, C: WithSignals> std::ops::Deref for #collection_struct_name<'c, C> {
199+
// The whole upcast mechanism is based on C remaining the same even through upcast.
200+
type Target = <
201+
<
202+
#class_name as crate::obj::GodotClass
203+
>::Base as WithSignals
204+
>::SignalCollection<'c, C>;
205+
206+
fn deref(&self) -> &Self::Target {
207+
type Derived = #class_name;
208+
crate::private::signal_collection_to_base::<C, Derived>(self)
209+
}
210+
}
211+
212+
impl<'c, C: WithSignals> std::ops::DerefMut for #collection_struct_name<'c, C> {
213+
fn deref_mut(&mut self) -> &mut Self::Target {
214+
type Derived = #class_name;
215+
crate::private::signal_collection_to_base_mut::<C, Derived>(self)
119216
}
120217
}
121218
}
@@ -139,10 +236,10 @@ fn make_signal_individual_struct(signal: &ClassSignal, params: &SignalParams) ->
139236
// Embedded in `mod signals`.
140237
quote! {
141238
// Reduce tokens to parse by reusing this type definitions.
142-
type #typed_name<'c> = crate::registry::signal::TypedSignal<'c, #class_ty, #param_tuple>;
239+
type #typed_name<'c, C> = TypedSignal<'c, C, #param_tuple>;
143240

144-
pub struct #individual_struct_name<'c> {
145-
typed: #typed_name<'c>,
241+
pub struct #individual_struct_name<'c, C: WithSignals = #class_ty> {
242+
typed: #typed_name<'c, C>,
146243
}
147244

148245
impl<'c> #individual_struct_name<'c> {
@@ -151,15 +248,15 @@ fn make_signal_individual_struct(signal: &ClassSignal, params: &SignalParams) ->
151248
}
152249
}
153250

154-
impl<'c> std::ops::Deref for #individual_struct_name<'c> {
155-
type Target = #typed_name<'c>;
251+
impl<'c, C: WithSignals> std::ops::Deref for #individual_struct_name<'c, C> {
252+
type Target = #typed_name<'c, C>;
156253

157254
fn deref(&self) -> &Self::Target {
158255
&self.typed
159256
}
160257
}
161258

162-
impl std::ops::DerefMut for #individual_struct_name<'_> {
259+
impl<C: WithSignals> std::ops::DerefMut for #individual_struct_name<'_, C> {
163260
fn deref_mut(&mut self) -> &mut Self::Target {
164261
&mut self.typed
165262
}

godot-core/src/obj/gd.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -721,7 +721,7 @@ where
721721
///
722722
/// [`WithUserSignals::signals()`]: crate::obj::WithUserSignals::signals()
723723
#[cfg(since_api = "4.2")]
724-
pub fn signals(&mut self) -> T::SignalCollection<'_> {
724+
pub fn signals(&mut self) -> T::SignalCollection<'_, T> {
725725
T::__signals_from_external(self)
726726
}
727727
}

0 commit comments

Comments
 (0)