Skip to content

Commit 43b4b1c

Browse files
committed
Clean up docs for macro-generated connect_* methods
1 parent 3ef5d3e commit 43b4b1c

File tree

2 files changed

+87
-57
lines changed

2 files changed

+87
-57
lines changed

godot-core/src/registry/signal/connect_builder.rs

Lines changed: 58 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -16,17 +16,9 @@ use std::fmt::Debug;
1616

1717
/// Builder for customizing signal connections.
1818
///
19-
/// Allows a high degree of customization for connecting signals, while maintaining complete type safety.
19+
/// Allows a high degree of customization for connecting [`TypedSignal`], while maintaining complete type safety.
2020
///
21-
/// <div class="warning">
22-
/// <strong>Warning:</strong>
23-
/// Exact type parameters are subject to change and not part of the public API. We could annotate <code>#[doc(hidden)]</code>, but it would make
24-
/// things harder to understand. Thus, try not to name the <code>ConnectBuilder</code> type in your code; most connection setup doesn't need it.
25-
/// </div>
26-
// If naming the type becomes a requirement, there may be some options:
27-
// - Use a type alias in the module or TypedSignal, exposing only public parameters. This would work for constructor, but not all transformations.
28-
// - Pack multiple types together into "type lists", i.e. custom structs carrying the type state. For a user, this would appear as one type,
29-
// - which could also be #[doc(hidden)]. However, this may make the trait resolution more complex and worsen error messages, so not done now.
21+
/// See the [Signals](https://godot-rust.github.io/book/register/signals.html) chapter in the book for a general introduction and examples.
3022
///
3123
/// # Customization
3224
/// Customizing your signal connection must be done **before** providing the function being connected
@@ -39,13 +31,36 @@ use std::fmt::Debug;
3931
/// - [`flags()`][Self::flags]: Provide one or multiple [`ConnectFlags`][crate::classes::object::ConnectFlags], possibly combined with bitwise OR.
4032
///
4133
/// # Finalizing
42-
/// After customizing your builder, you can register the connection by using one of the following methods:
43-
/// - [`connect()`][Self::connect]: Connect a global/associated function or a closure.
44-
/// - [`connect_self()`][Self::connect_self]: Connect a method or closure that runs on the signal emitter.
45-
/// - [`connect_other()`][Self::connect_other]: Connect a method or closure that runs on a separate object.
46-
/// - [`connect_sync()`](#method.connect_sync): Connect a global/associated function or closure that should be callable across threads. \
47-
/// Allows signal to be emitted from other threads. \
48-
/// Requires `Send` + `Sync` bounds on the provided function/method, and is only available for the `experimental-threads` Cargo feature.
34+
/// After customizing your builder, you can register the connection with various `connect_*` functions.
35+
///
36+
/// To connect to **methods** (member functions) with a signal object, you have the following combinations:
37+
///
38+
/// | Signal object | 1st parameter `&mut C` | 1st parameter `&mut Gd<C>` |
39+
/// |---------------|------------------------------------------------|----------------------------------------------|
40+
/// | `self` | [`connect_self_mut`][Self::connect_self_mut] | [`connect_self_gd`][Self::connect_self_gd] |
41+
/// | other object | [`connect_other_mut`][Self::connect_other_mut] | [`connect_other_gd`][Self::connect_other_gd] |
42+
///
43+
/// <br>
44+
///
45+
/// For **global functions, associated functions and closures**, you can use the following APIs:
46+
/// - [`connect()`][Self::connect]: Connect any function running on the same thread as the signal emitter.
47+
/// - [`connect_sync()`](#method.connect_sync): Connect a global/associated function or closure that should be callable across threads.
48+
/// Allows signals to be emitted from other threads.
49+
/// - Requires `Send` + `Sync` bounds on the provided function.
50+
/// - Is only available for the Cargo feature `experimental-threads`.
51+
///
52+
/// # Implementation and documentation notes
53+
/// See [`TypedSignal` docs](struct.TypedSignal.html#implementation-and-documentation-notes) for a background on the `connect_*` API design.
54+
///
55+
/// <div class="warning">
56+
/// <strong>Warning:</strong>
57+
/// Exact type parameters are subject to change and not part of the public API. Since it's a type-state API, new states might require new
58+
/// type parameters. Thus, try not to name the <code>ConnectBuilder</code> type in your code; most connection setup doesn't need it.
59+
/// </div>
60+
// If naming the type becomes a requirement, there may be some options:
61+
// - Use a type alias in the module or TypedSignal, exposing only public parameters. This would work for constructor, but not all transformations.
62+
// - Pack multiple types together into "type lists", i.e. custom structs carrying the type state. For a user, this would appear as one type,
63+
// - which could also be #[doc(hidden)]. However, this may make the trait resolution more complex and worsen error messages, so not done now.
4964
#[must_use]
5065
pub struct ConnectBuilder<'ts, 'c, C: WithSignals, Ps> {
5166
parent_sig: &'ts TypedSignal<'c, C, Ps>,
@@ -120,10 +135,8 @@ where
120135
}
121136

122137
macro_rules! impl_builder_connect {
123-
($( $args:ident : $Ps:ident ),*) => {
124-
// --------------------------------------------------------------------------------------------------------------------------------------
125-
// SignalReceiver
126-
138+
($( #[$attr:meta] )? $( $args:ident : $Ps:ident ),*) => {
139+
$( #[$attr] )?
127140
impl<C: WithSignals, $($Ps: Debug + FromGodot + 'static),*>
128141
ConnectBuilder<'_, '_, C, ($($Ps,)*)> {
129142
/// Connect a non-member function (global function, associated function or closure).
@@ -135,7 +148,11 @@ macro_rules! impl_builder_connect {
135148
/// sig.connect(|arg| { /* closure */ });
136149
/// ```
137150
///
138-
/// - To connect to a method on the object that owns this signal, use [`connect_self()`][Self::connect_self].
151+
/// # Related APIs
152+
/// - To connect to a method on the object that owns this signal, use [`connect_self_mut()`][Self::connect_self_mut] or
153+
/// [`connect_self_gd()`][Self::connect_self_gd].
154+
/// - To connect to methods on other objects, use [`connect_other_mut()`][Self::connect_other_mut] or
155+
/// [`connect_other_gd()`][Self::connect_other_gd].
139156
/// - If you need [`connect flags`](ConnectFlags), call [`flags()`](Self::flags) before this.
140157
/// - If you need cross-thread signals, use [`connect_sync()`](#method.connect_sync) instead (requires feature "experimental-threads").
141158
pub fn connect<F, R>(self, mut function: F)
@@ -151,7 +168,8 @@ macro_rules! impl_builder_connect {
151168

152169
/// Connect a method with `&mut self` as the first parameter (user classes only).
153170
///
154-
/// - Use [`connect_self_gd()`][Self::connect_self_gd] to receive `Gd<Self>` instead and avoid implicit `bind_mut()` on emit.
171+
/// # Related APIs
172+
/// - Use [`connect_self_gd()`][Self::connect_self_gd] to receive `Gd<Self>` instead and avoid implicit `bind_mut()` on emit. \
155173
/// For engine classes, `&mut self` is not supported at all.
156174
/// - To connect to methods on other objects, use [`connect_other_mut()`][Self::connect_other_mut].
157175
/// - If you need [connect flags](ConnectFlags), call [`flags()`](Self::flags) before this.
@@ -172,6 +190,7 @@ macro_rules! impl_builder_connect {
172190

173191
/// Connect a method with `&mut Gd<Self>` as the first parameter (user + engine classes).
174192
///
193+
/// # Related APIs
175194
/// - If your class `C` is user-defined and you'd like to have an automatic `bind_mut()` and receive `&mut self`, then
176195
/// use [`connect_self_mut()`][Self::connect_self_mut] instead.
177196
/// - To connect to methods on other objects, use [`connect_other_gd()`][Self::connect_other_gd].
@@ -196,9 +215,10 @@ macro_rules! impl_builder_connect {
196215
/// - `&OtherC`, as long as `OtherC` is a user class that contains a `Base<T>` field (it implements the
197216
/// [`WithBaseField`](crate::obj::WithBaseField) trait).
198217
///
199-
/// ---
200-
///
201-
/// - To connect to methods on the object that owns this signal, use [`connect_self_mut()`][Self::connect_self].
218+
/// # Related APIs
219+
/// - Use [`connect_other_gd()`][Self::connect_other_gd] to receive `Gd<Self>` instead and avoid implicit `bind_mut()` on emit. \
220+
/// For engine classes, `&mut self` is not supported at all.
221+
/// - To connect to methods on the object that owns this signal, use [`connect_self_mut()`][Self::connect_self_mut].
202222
/// - If you need [connect flags](ConnectFlags), call [`flags()`](Self::flags) before this.
203223
/// - If you need cross-thread signals, use [`connect_sync()`](#method.connect_sync) instead (requires feature "experimental-threads").
204224
pub fn connect_other_mut<F, R, OtherC>(self, object: &impl ToSignalObj<OtherC>, mut method: F)
@@ -223,8 +243,7 @@ macro_rules! impl_builder_connect {
223243
/// - `&OtherC`, as long as `OtherC` is a user class that contains a `Base<T>` field (it implements the
224244
/// [`WithBaseField`](crate::obj::WithBaseField) trait).
225245
///
226-
/// ---
227-
///
246+
/// # Related APIs
228247
/// - To connect to methods on the object that owns this signal, use [`connect_self_gd()`][Self::connect_self_gd].
229248
/// - If you need [connect flags](ConnectFlags), call [`flags()`](Self::flags) before this.
230249
/// - If you need cross-thread signals, use [`connect_sync()`](#method.connect_sync) instead (requires feature "experimental-threads").
@@ -271,14 +290,14 @@ macro_rules! impl_builder_connect {
271290
};
272291
}
273292

274-
impl_builder_connect!();
275-
impl_builder_connect!(arg0: P0);
276-
impl_builder_connect!(arg0: P0, arg1: P1);
277-
impl_builder_connect!(arg0: P0, arg1: P1, arg2: P2);
278-
impl_builder_connect!(arg0: P0, arg1: P1, arg2: P2, arg3: P3);
279-
impl_builder_connect!(arg0: P0, arg1: P1, arg2: P2, arg3: P3, arg4: P4);
280-
impl_builder_connect!(arg0: P0, arg1: P1, arg2: P2, arg3: P3, arg4: P4, arg5: P5);
281-
impl_builder_connect!(arg0: P0, arg1: P1, arg2: P2, arg3: P3, arg4: P4, arg5: P5, arg6: P6);
282-
impl_builder_connect!(arg0: P0, arg1: P1, arg2: P2, arg3: P3, arg4: P4, arg5: P5, arg6: P6, arg7: P7);
283-
impl_builder_connect!(arg0: P0, arg1: P1, arg2: P2, arg3: P3, arg4: P4, arg5: P5, arg6: P6, arg7: P7, arg8: P8);
284-
impl_builder_connect!(arg0: P0, arg1: P1, arg2: P2, arg3: P3, arg4: P4, arg5: P5, arg6: P6, arg7: P7, arg8: P8, arg9: P9);
293+
impl_builder_connect!(#[doc(hidden)] );
294+
impl_builder_connect!(#[doc(hidden)] arg0: P0);
295+
impl_builder_connect!(#[doc(hidden)] arg0: P0, arg1: P1);
296+
impl_builder_connect!( arg0: P0, arg1: P1, arg2: P2);
297+
impl_builder_connect!(#[doc(hidden)] arg0: P0, arg1: P1, arg2: P2, arg3: P3);
298+
impl_builder_connect!(#[doc(hidden)] arg0: P0, arg1: P1, arg2: P2, arg3: P3, arg4: P4);
299+
impl_builder_connect!(#[doc(hidden)] arg0: P0, arg1: P1, arg2: P2, arg3: P3, arg4: P4, arg5: P5);
300+
impl_builder_connect!(#[doc(hidden)] arg0: P0, arg1: P1, arg2: P2, arg3: P3, arg4: P4, arg5: P5, arg6: P6);
301+
impl_builder_connect!(#[doc(hidden)] arg0: P0, arg1: P1, arg2: P2, arg3: P3, arg4: P4, arg5: P5, arg6: P6, arg7: P7);
302+
impl_builder_connect!(#[doc(hidden)] arg0: P0, arg1: P1, arg2: P2, arg3: P3, arg4: P4, arg5: P5, arg6: P6, arg7: P7, arg8: P8);
303+
impl_builder_connect!(#[doc(hidden)] arg0: P0, arg1: P1, arg2: P2, arg3: P3, arg4: P4, arg5: P5, arg6: P6, arg7: P7, arg8: P8, arg9: P9);

godot-core/src/registry/signal/typed_signal.rs

Lines changed: 29 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -43,11 +43,13 @@ impl<C: WithBaseField> ToSignalObj<C> for C {
4343
/// Short-lived type, only valid in the scope of its surrounding object type `C`, for lifetime `'c`. The generic argument `Ps` represents
4444
/// the parameters of the signal, thus ensuring the type safety.
4545
///
46+
/// See the [Signals](https://godot-rust.github.io/book/register/signals.html) chapter in the book for a general introduction and examples.
47+
///
4648
/// The [`WithSignals::SignalCollection`] struct returns multiple signals with distinct, code-generated types, but they all implement
4749
/// `Deref` and `DerefMut` to `TypedSignal`. This allows you to either use the concrete APIs of the generated types, or the more generic
4850
/// ones of `TypedSignal`.
4951
///
50-
/// # Connecting a signal to a receiver.
52+
/// # Connecting a signal to a receiver
5153
/// Receiver functions are functions that are called when a signal is emitted. You can connect a signal in many different ways:
5254
/// - [`connect()`][Self::connect]: Connect a global/associated function or a closure.
5355
/// - [`connect_self()`][Self::connect_self]: Connect a method or closure that runs on the signal emitter.
@@ -60,8 +62,19 @@ impl<C: WithBaseField> ToSignalObj<C> for C {
6062
///
6163
/// For generic use, you can also use [`emit_tuple()`][Self::emit_tuple], which does not provide parameter names.
6264
///
63-
/// # More information
64-
/// See the [Signals](https://godot-rust.github.io/book/register/signals.html) chapter in the book for a detailed introduction and examples.
65+
/// # Implementation and documentation notes
66+
/// Individual `connect_*` methods are generated using a declarative macro, to support a variadic number of parameters **and** type inference.
67+
/// Without inference, it is not reliably possible[^rust-issue] to pass `|this, arg| { ... }` closures and would require the more verbose
68+
/// `|this: &mut MyClass, arg: GString| { ... }` syntax. Unfortunately, this design choice precludes the usage of traits for abstraction over
69+
/// different functions. There are potential workarounds[^workaround], let us know in case you find a way.
70+
///
71+
/// To keep this documentation readable, we only document one overload of each implementation: arbitrarily the one with three parameters
72+
/// `(P0, P1, P2)`. Keep this in mind when looking at a concrete signature.
73+
///
74+
/// [^rust-issue]: See Rust issue [#63702](https://github.com/rust-lang/rust/issues/63702) or
75+
/// [rust-lang discussion](https://users.rust-lang.org/t/what-are-the-limits-of-type-inference-in-closures/31519).
76+
/// [^workaround]: Using macros to have one less indirection level is one workaround.
77+
/// [Identity functions](https://users.rust-lang.org/t/type-inference-in-closures/78399/3) might be another.
6578
pub struct TypedSignal<'c, C: WithSignals, Ps> {
6679
/// In Godot, valid signals (unlike funcs) are _always_ declared in a class and become part of each instance. So there's always an object.
6780
object: C::__SignalObj<'c>,
@@ -162,10 +175,8 @@ impl<'c, C: WithSignals, Ps: meta::ParamTuple> TypedSignal<'c, C, Ps> {
162175
}
163176

164177
macro_rules! impl_signal_connect {
165-
($( $args:ident : $Ps:ident ),*) => {
166-
// --------------------------------------------------------------------------------------------------------------------------------------
167-
// SignalReceiver
168-
178+
($( #[$attr:meta] )? $( $args:ident : $Ps:ident ),*) => {
179+
$( #[$attr] )?
169180
impl<C: WithSignals, $($Ps: Debug + FromGodot + 'static),*>
170181
TypedSignal<'_, C, ($($Ps,)*)> {
171182
/// Connect a non-member function (global function, associated function or closure).
@@ -240,14 +251,14 @@ macro_rules! impl_signal_connect {
240251
};
241252
}
242253

243-
impl_signal_connect!();
244-
impl_signal_connect!(arg0: P0);
245-
impl_signal_connect!(arg0: P0, arg1: P1);
246-
impl_signal_connect!(arg0: P0, arg1: P1, arg2: P2);
247-
impl_signal_connect!(arg0: P0, arg1: P1, arg2: P2, arg3: P3);
248-
impl_signal_connect!(arg0: P0, arg1: P1, arg2: P2, arg3: P3, arg4: P4);
249-
impl_signal_connect!(arg0: P0, arg1: P1, arg2: P2, arg3: P3, arg4: P4, arg5: P5);
250-
impl_signal_connect!(arg0: P0, arg1: P1, arg2: P2, arg3: P3, arg4: P4, arg5: P5, arg6: P6);
251-
impl_signal_connect!(arg0: P0, arg1: P1, arg2: P2, arg3: P3, arg4: P4, arg5: P5, arg6: P6, arg7: P7);
252-
impl_signal_connect!(arg0: P0, arg1: P1, arg2: P2, arg3: P3, arg4: P4, arg5: P5, arg6: P6, arg7: P7, arg8: P8);
253-
impl_signal_connect!(arg0: P0, arg1: P1, arg2: P2, arg3: P3, arg4: P4, arg5: P5, arg6: P6, arg7: P7, arg8: P8, arg9: P9);
254+
impl_signal_connect!(#[doc(hidden)] );
255+
impl_signal_connect!(#[doc(hidden)] arg0: P0);
256+
impl_signal_connect!(#[doc(hidden)] arg0: P0, arg1: P1);
257+
impl_signal_connect!( arg0: P0, arg1: P1, arg2: P2);
258+
impl_signal_connect!(#[doc(hidden)] arg0: P0, arg1: P1, arg2: P2, arg3: P3);
259+
impl_signal_connect!(#[doc(hidden)] arg0: P0, arg1: P1, arg2: P2, arg3: P3, arg4: P4);
260+
impl_signal_connect!(#[doc(hidden)] arg0: P0, arg1: P1, arg2: P2, arg3: P3, arg4: P4, arg5: P5);
261+
impl_signal_connect!(#[doc(hidden)] arg0: P0, arg1: P1, arg2: P2, arg3: P3, arg4: P4, arg5: P5, arg6: P6);
262+
impl_signal_connect!(#[doc(hidden)] arg0: P0, arg1: P1, arg2: P2, arg3: P3, arg4: P4, arg5: P5, arg6: P6, arg7: P7);
263+
impl_signal_connect!(#[doc(hidden)] arg0: P0, arg1: P1, arg2: P2, arg3: P3, arg4: P4, arg5: P5, arg6: P6, arg7: P7, arg8: P8);
264+
impl_signal_connect!(#[doc(hidden)] arg0: P0, arg1: P1, arg2: P2, arg3: P3, arg4: P4, arg5: P5, arg6: P6, arg7: P7, arg8: P8, arg9: P9);

0 commit comments

Comments
 (0)