Skip to content

Commit f5dc50c

Browse files
committed
Merge branch 'master' into lilizoey/refactor/signature
# Conflicts: # godot-core/src/registry/signal/typed_signal.rs # godot-core/src/task/futures.rs
2 parents 4d5e63a + a752851 commit f5dc50c

File tree

21 files changed

+1025
-361
lines changed

21 files changed

+1025
-361
lines changed

godot-codegen/src/generator/classes.rs

+9-2
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,9 @@
77
use crate::context::{Context, NotificationEnum};
88
use crate::generator::functions_common::{FnCode, FnDefinition, FnDefinitions};
99
use crate::generator::method_tables::MethodTableKey;
10-
use crate::generator::{constants, docs, enums, functions_common, notifications, virtual_traits};
10+
use crate::generator::{
11+
constants, docs, enums, functions_common, notifications, signals, virtual_traits,
12+
};
1113
use crate::models::domain::{
1214
ApiView, Class, ClassLike, ClassMethod, ExtensionApi, FnDirection, FnQualifier, Function,
1315
ModName, TyName,
@@ -121,6 +123,8 @@ fn make_class(class: &Class, ctx: &mut Context, view: &ApiView) -> GeneratedClas
121123
builders,
122124
} = make_class_methods(class, &class.methods, &cfg_attributes, ctx);
123125

126+
let signal_types = signals::make_class_signals(class, &class.signals, ctx);
127+
124128
let enums = enums::make_enums(&class.enums, &cfg_attributes);
125129
let constants = constants::make_constants(&class.constants);
126130
let inherits_macro = format_ident!("unsafe_inherits_transitive_{}", class_name.rust_ty);
@@ -133,14 +137,16 @@ fn make_class(class: &Class, ctx: &mut Context, view: &ApiView) -> GeneratedClas
133137
// Associated "sidecar" module is made public if there are other symbols related to the class, which are not
134138
// in top-level godot::classes module (notification enums are not in the sidecar, but in godot::classes::notify).
135139
// This checks if token streams (i.e. code) is empty.
136-
let has_sidecar_module = !enums.is_empty() || !builders.is_empty();
140+
let has_sidecar_module = !enums.is_empty() || !builders.is_empty() || signal_types.is_some();
137141

138142
let class_doc = docs::make_class_doc(
139143
class_name,
140144
base_ident_opt,
141145
notification_enum.is_some(),
142146
has_sidecar_module,
147+
signal_types.is_some(),
143148
);
149+
144150
let module_doc = docs::make_module_doc(class_name);
145151

146152
let virtual_trait = virtual_traits::make_virtual_methods_trait(
@@ -256,6 +262,7 @@ fn make_class(class: &Class, ctx: &mut Context, view: &ApiView) -> GeneratedClas
256262

257263
#builders
258264
#enums
265+
#signal_types
259266
};
260267
// note: TypePtr -> ObjectPtr conversion OK?
261268

godot-codegen/src/generator/docs.rs

+22-6
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
//!
1010
//! Single module for documentation, rather than having it in each symbol-specific file, so it's easier to keep docs consistent.
1111
12+
use crate::generator::signals;
1213
use crate::models::domain::{ModName, TyName};
1314
use crate::special_cases;
1415
use proc_macro2::Ident;
@@ -18,6 +19,7 @@ pub fn make_class_doc(
1819
base_ident_opt: Option<Ident>,
1920
has_notification_enum: bool,
2021
has_sidecar_module: bool,
22+
has_signal_collection: bool,
2123
) -> String {
2224
let TyName { rust_ty, godot_ty } = class_name;
2325

@@ -29,15 +31,28 @@ pub fn make_class_doc(
2931
.to_string()
3032
};
3133

32-
let notify_line = if has_notification_enum {
33-
format!("* [`{rust_ty}Notification`][crate::classes::notify::{rust_ty}Notification]: notification type\n")
34+
let (sidecar_signal_lines, module_name);
35+
if has_sidecar_module {
36+
let module = ModName::from_godot(&class_name.godot_ty).rust_mod;
37+
38+
sidecar_signal_lines = format!("* [`{module}`][crate::classes::{module}]: sidecar module with related enum/flag types\n");
39+
module_name = Some(module);
40+
} else {
41+
sidecar_signal_lines = String::new();
42+
module_name = None;
43+
};
44+
45+
let signal_line = if has_signal_collection {
46+
let signal_coll = signals::make_collection_name(class_name);
47+
let module = module_name.expect("signal implies presence of sidecar module");
48+
49+
format!("* [`{signal_coll}`][crate::classes::{module}::{signal_coll}]: signal collection\n")
3450
} else {
3551
String::new()
3652
};
3753

38-
let sidecar_line = if has_sidecar_module {
39-
let module_name = ModName::from_godot(&class_name.godot_ty).rust_mod;
40-
format!("* [`{module_name}`][crate::classes::{module_name}]: sidecar module with related enum/flag types\n")
54+
let notify_line = if has_notification_enum {
55+
format!("* [`{rust_ty}Notification`][crate::classes::notify::{rust_ty}Notification]: notification type\n")
4156
} else {
4257
String::new()
4358
};
@@ -59,8 +74,9 @@ pub fn make_class_doc(
5974
{inherits_line}\n\n\
6075
\
6176
Related symbols:\n\n\
62-
{sidecar_line}\
77+
{sidecar_signal_lines}\
6378
* [`{trait_name}`][crate::classes::{trait_name}]: virtual methods\n\
79+
{signal_line}\
6480
{notify_line}\
6581
\n\n\
6682
See also [Godot docs for `{godot_ty}`]({online_link}).\n\n{notes}",

godot-codegen/src/generator/mod.rs

+1
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ pub mod lifecycle_builtins;
2525
pub mod method_tables;
2626
pub mod native_structures;
2727
pub mod notifications;
28+
pub mod signals;
2829
pub mod utility_functions;
2930
pub mod virtual_traits;
3031

+225
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,225 @@
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(&param.name.to_string());
198+
let param_ty = &param.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+
}

godot-codegen/src/models/domain.rs

+9-2
Original file line numberDiff line numberDiff line change
@@ -166,6 +166,7 @@ pub struct Class {
166166
pub constants: Vec<ClassConstant>,
167167
pub enums: Vec<Enum>,
168168
pub methods: Vec<ClassMethod>,
169+
pub signals: Vec<ClassSignal>,
169170
}
170171

171172
impl ClassLike for Class {
@@ -414,8 +415,6 @@ pub struct ClassMethod {
414415
pub surrounding_class: TyName,
415416
}
416417

417-
impl ClassMethod {}
418-
419418
impl Function for ClassMethod {
420419
fn common(&self) -> &FunctionCommon {
421420
&self.common
@@ -443,6 +442,14 @@ impl fmt::Display for ClassMethod {
443442

444443
// ----------------------------------------------------------------------------------------------------------------------------------------------
445444

445+
pub struct ClassSignal {
446+
pub name: String,
447+
pub parameters: Vec<FnParam>,
448+
pub surrounding_class: TyName,
449+
}
450+
451+
// ----------------------------------------------------------------------------------------------------------------------------------------------
452+
446453
#[derive(Copy, Clone, Debug)]
447454
pub enum FnDirection {
448455
/// Godot -> Rust.

0 commit comments

Comments
 (0)