Skip to content

Commit a6580fb

Browse files
committed
Added ConnectHandle for disconnections
Changes some of the `connect_` methods for typed signals to return a handle containing information about the connection. Can be used to disconnect the signal when not needed. Makes it simpler to disconnect signals to objects that are dead / about to die: Object can call disconnect on handles before being freed (for example in a "impl Drop" implementation).
1 parent 443621c commit a6580fb

File tree

6 files changed

+340
-26
lines changed

6 files changed

+340
-26
lines changed

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

Lines changed: 22 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ use crate::meta;
1212
use crate::meta::InParamTuple;
1313
use crate::obj::{bounds, Bounds, Gd, GodotClass, WithSignals};
1414
use crate::registry::signal::signal_receiver::{IndirectSignalReceiver, SignalReceiver};
15-
use crate::registry::signal::{ToSignalObj, TypedSignal};
15+
use crate::registry::signal::{ConnectHandle, ToSignalObj, TypedSignal};
1616

1717
/// Builder for customizing signal connections.
1818
///
@@ -125,15 +125,15 @@ where
125125
fn inner_connect_godot_fn<F>(
126126
self,
127127
godot_fn: impl FnMut(&[&Variant]) -> Result<Variant, ()> + 'static,
128-
) {
128+
) -> ConnectHandle {
129129
let callable_name = match &self.data.callable_name {
130130
Some(user_provided_name) => user_provided_name,
131131
None => &make_callable_name::<F>(),
132132
};
133133

134134
let callable = Callable::from_local_fn(callable_name, godot_fn);
135135
self.parent_sig
136-
.inner_connect_untyped(&callable, self.data.connect_flags);
136+
.inner_connect_untyped(callable, self.data.connect_flags)
137137
}
138138
}
139139

@@ -154,7 +154,7 @@ impl<C: WithSignals, Ps: InParamTuple + 'static> ConnectBuilder<'_, '_, C, Ps> {
154154
/// [`connect_other_gd()`][Self::connect_other_gd].
155155
/// - If you need [`connect flags`](ConnectFlags), call [`flags()`](Self::flags) before this.
156156
/// - If you need cross-thread signals, use [`connect_sync()`](#method.connect_sync) instead (requires feature "experimental-threads").
157-
pub fn connect<F>(self, mut function: F)
157+
pub fn connect<F>(self, mut function: F) -> ConnectHandle
158158
where
159159
for<'c_rcv> F: SignalReceiver<(), Ps>,
160160
for<'c_rcv> IndirectSignalReceiver<'c_rcv, (), Ps, F>: From<&'c_rcv mut F>,
@@ -165,7 +165,7 @@ impl<C: WithSignals, Ps: InParamTuple + 'static> ConnectBuilder<'_, '_, C, Ps> {
165165
.call((), args);
166166
});
167167

168-
self.inner_connect_godot_fn::<F>(godot_fn);
168+
self.inner_connect_godot_fn::<F>(godot_fn)
169169
}
170170

171171
/// Connect a method with `&mut self` as the first parameter (user classes only).
@@ -176,7 +176,7 @@ impl<C: WithSignals, Ps: InParamTuple + 'static> ConnectBuilder<'_, '_, C, Ps> {
176176
/// - To connect to methods on other objects, use [`connect_other_mut()`][Self::connect_other_mut].
177177
/// - If you need [connect flags](ConnectFlags), call [`flags()`](Self::flags) before this.
178178
/// - If you need cross-thread signals, use [`connect_sync()`](#method.connect_sync) instead (requires feature `experimental-threads`).
179-
pub fn connect_self_mut<F>(self, mut function: F)
179+
pub fn connect_self_mut<F>(self, mut function: F) -> ConnectHandle
180180
where
181181
C: Bounds<Declarer = bounds::DeclUser>,
182182
for<'c_rcv> F: SignalReceiver<&'c_rcv mut C, Ps>,
@@ -191,7 +191,7 @@ impl<C: WithSignals, Ps: InParamTuple + 'static> ConnectBuilder<'_, '_, C, Ps> {
191191
.call(&mut *guard, args);
192192
});
193193

194-
self.inner_connect_godot_fn::<F>(godot_fn);
194+
self.inner_connect_godot_fn::<F>(godot_fn)
195195
}
196196

197197
/// Connect a method with `&mut Gd<Self>` as the first parameter (user + engine classes).
@@ -202,7 +202,7 @@ impl<C: WithSignals, Ps: InParamTuple + 'static> ConnectBuilder<'_, '_, C, Ps> {
202202
/// - To connect to methods on other objects, use [`connect_other_gd()`][Self::connect_other_gd].
203203
/// - If you need [connect flags](ConnectFlags), call [`flags()`](Self::flags) before this.
204204
/// - If you need cross-thread signals, use [`connect_sync()`](#method.connect_sync) instead (requires feature `experimental-threads`).
205-
pub fn connect_self_gd<F>(self, mut function: F)
205+
pub fn connect_self_gd<F>(self, mut function: F) -> ConnectHandle
206206
where
207207
F: SignalReceiver<Gd<C>, Ps>,
208208
for<'c_rcv> IndirectSignalReceiver<'c_rcv, Gd<C>, Ps, F>: From<&'c_rcv mut F>,
@@ -215,7 +215,7 @@ impl<C: WithSignals, Ps: InParamTuple + 'static> ConnectBuilder<'_, '_, C, Ps> {
215215
.call(gd.clone(), args);
216216
});
217217

218-
self.inner_connect_godot_fn::<F>(godot_fn);
218+
self.inner_connect_godot_fn::<F>(godot_fn)
219219
}
220220

221221
/// Connect a method with any `&mut OtherC` as the first parameter (user classes only).
@@ -231,7 +231,11 @@ impl<C: WithSignals, Ps: InParamTuple + 'static> ConnectBuilder<'_, '_, C, Ps> {
231231
/// - To connect to methods on the object that owns this signal, use [`connect_self_mut()`][Self::connect_self_mut].
232232
/// - If you need [connect flags](ConnectFlags), call [`flags()`](Self::flags) before this.
233233
/// - If you need cross-thread signals, use [`connect_sync()`](#method.connect_sync) instead (requires feature "experimental-threads").
234-
pub fn connect_other_mut<F, OtherC>(self, object: &impl ToSignalObj<OtherC>, mut method: F)
234+
pub fn connect_other_mut<F, OtherC>(
235+
self,
236+
object: &impl ToSignalObj<OtherC>,
237+
mut method: F,
238+
) -> ConnectHandle
235239
where
236240
OtherC: GodotClass + Bounds<Declarer = bounds::DeclUser>,
237241
for<'c_rcv> F: SignalReceiver<&'c_rcv mut OtherC, Ps>,
@@ -246,7 +250,7 @@ impl<C: WithSignals, Ps: InParamTuple + 'static> ConnectBuilder<'_, '_, C, Ps> {
246250
.call(&mut *guard, args);
247251
});
248252

249-
self.inner_connect_godot_fn::<F>(godot_fn);
253+
self.inner_connect_godot_fn::<F>(godot_fn)
250254
}
251255

252256
/// Connect a method with any `&mut Gd<OtherC>` as the first parameter (user + engine classes).
@@ -260,7 +264,11 @@ impl<C: WithSignals, Ps: InParamTuple + 'static> ConnectBuilder<'_, '_, C, Ps> {
260264
/// - To connect to methods on the object that owns this signal, use [`connect_self_gd()`][Self::connect_self_gd].
261265
/// - If you need [connect flags](ConnectFlags), call [`flags()`](Self::flags) before this.
262266
/// - If you need cross-thread signals, use [`connect_sync()`](#method.connect_sync) instead (requires feature "experimental-threads").
263-
pub fn connect_other_gd<F, OtherC>(self, object: &impl ToSignalObj<OtherC>, mut method: F)
267+
pub fn connect_other_gd<F, OtherC>(
268+
self,
269+
object: &impl ToSignalObj<OtherC>,
270+
mut method: F,
271+
) -> ConnectHandle
264272
where
265273
OtherC: GodotClass,
266274
F: SignalReceiver<Gd<OtherC>, Ps>,
@@ -274,7 +282,7 @@ impl<C: WithSignals, Ps: InParamTuple + 'static> ConnectBuilder<'_, '_, C, Ps> {
274282
.call(gd.clone(), args);
275283
});
276284

277-
self.inner_connect_godot_fn::<F>(godot_fn);
285+
self.inner_connect_godot_fn::<F>(godot_fn)
278286
}
279287

280288
/// Connect to this signal using a thread-safe function, allows the signal to be called across threads.
@@ -304,6 +312,6 @@ impl<C: WithSignals, Ps: InParamTuple + 'static> ConnectBuilder<'_, '_, C, Ps> {
304312

305313
let callable = Callable::from_sync_fn(callable_name, godot_fn);
306314
self.parent_sig
307-
.inner_connect_untyped(&callable, self.data.connect_flags);
315+
.inner_connect_untyped(callable, self.data.connect_flags);
308316
}
309317
}
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
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+
use std::borrow::Cow;
9+
10+
use crate::builtin::Callable;
11+
use crate::classes::Object;
12+
use crate::obj::Gd;
13+
14+
/// Handle representing a typed signal connection to a receiver.
15+
///
16+
/// Returned by connections made by the `connect_*` methods of
17+
/// [`TypedSignal`][crate::registry::signal::TypedSignal] and [`ConnectBuilder`][crate::registry::signal::ConnectBuilder].
18+
///
19+
/// Connections managed by a handle can be disconnected using [`disconnect()`][Self::disconnect].
20+
pub struct ConnectHandle {
21+
receiver_object: Gd<Object>,
22+
signal_name: Cow<'static, str>,
23+
callable: Callable,
24+
}
25+
26+
impl ConnectHandle {
27+
// Should only be invoked by connect_* methods.
28+
pub(super) fn new(
29+
receiver_object: Gd<Object>,
30+
signal_name: Cow<'static, str>,
31+
callable: Callable,
32+
) -> Self {
33+
Self {
34+
receiver_object,
35+
signal_name,
36+
callable,
37+
}
38+
}
39+
40+
/// Disconnects the signal from the connected callable.
41+
///
42+
/// Panics (Debug)
43+
/// If the connection does not exist. Use [`is_connected()`][Self::is_connected] to make sure the connection exists.
44+
pub fn disconnect(mut self) {
45+
debug_assert!(self.is_connected());
46+
47+
self.receiver_object
48+
.disconnect(&*self.signal_name, &self.callable);
49+
}
50+
51+
/// Whether the handle represents a valid connection.
52+
///
53+
/// Returns false if the signals and callables managed by this handle have been disconnected in any other way than by using
54+
/// [`disconnect()`][Self::disconnect] -- e.g. through [`Signal::disconnect()`][crate::builtin::Signal::disconnect] or
55+
/// [`Object::disconnect()`][crate::classes::Object::disconnect].
56+
pub fn is_connected(&self) -> bool {
57+
self.receiver_object
58+
.is_connected(&*self.signal_name, &self.callable)
59+
}
60+
}

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

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,19 +8,22 @@
88
// Whole module only available in Godot 4.2+.
99

1010
mod connect_builder;
11+
mod connect_handle;
1112
mod signal_object;
1213
mod signal_receiver;
1314
mod typed_signal;
1415

1516
use crate::builtin::{GString, Variant};
1617
use crate::meta;
1718
pub(crate) use connect_builder::*;
19+
pub(crate) use connect_handle::*;
1820
pub(crate) use signal_object::*;
1921
pub(crate) use typed_signal::*;
2022

2123
// Used in `godot` crate.
2224
pub mod re_export {
2325
pub use super::connect_builder::ConnectBuilder;
26+
pub use super::connect_handle::ConnectHandle;
2427
pub use super::signal_receiver::IndirectSignalReceiver;
2528
pub use super::signal_receiver::SignalReceiver;
2629
pub use super::typed_signal::TypedSignal;

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

Lines changed: 20 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
66
*/
77

8-
use super::{make_callable_name, make_godot_fn, ConnectBuilder, SignalObject};
8+
use super::{make_callable_name, make_godot_fn, ConnectBuilder, ConnectHandle, SignalObject};
99
use crate::builtin::{Callable, Variant};
1010
use crate::classes::object::ConnectFlags;
1111
use crate::meta;
@@ -139,27 +139,34 @@ impl<'c, C: WithSignals, Ps: meta::ParamTuple> TypedSignal<'c, C, Ps> {
139139
fn inner_connect_godot_fn<F>(
140140
&self,
141141
godot_fn: impl FnMut(&[&Variant]) -> Result<Variant, ()> + 'static,
142-
) {
142+
) -> ConnectHandle {
143143
let callable_name = make_callable_name::<F>();
144144
let callable = Callable::from_local_fn(&callable_name, godot_fn);
145-
self.inner_connect_untyped(&callable, None);
145+
self.inner_connect_untyped(callable, None)
146146
}
147147

148148
/// Connect an untyped callable, with optional flags.
149149
///
150150
/// Used by [`inner_connect_godot_fn`] and `ConnectBuilder::connect_sync`.
151-
pub(super) fn inner_connect_untyped(&self, callable: &Callable, flags: Option<ConnectFlags>) {
151+
pub(super) fn inner_connect_untyped(
152+
&self,
153+
callable: Callable,
154+
flags: Option<ConnectFlags>,
155+
) -> ConnectHandle {
152156
use crate::obj::EngineBitfield;
153157

154158
let signal_name = self.name.as_ref();
155159

156-
self.object.to_owned_object().with_object_mut(|obj| {
157-
let mut c = obj.connect_ex(signal_name, callable);
160+
let mut owned_object = self.object.to_owned_object();
161+
owned_object.with_object_mut(|obj| {
162+
let mut c = obj.connect_ex(signal_name, &callable);
158163
if let Some(flags) = flags {
159164
c = c.flags(flags.ord() as u32);
160165
}
161166
c.done();
162167
});
168+
169+
ConnectHandle::new(owned_object, self.name.clone(), callable)
163170
}
164171

165172
pub(crate) fn to_untyped(&self) -> crate::builtin::Signal {
@@ -179,7 +186,7 @@ impl<C: WithSignals, Ps: InParamTuple + 'static> TypedSignal<'_, C, Ps> {
179186
///
180187
/// - To connect to a method on the object that owns this signal, use [`connect_self()`][Self::connect_self].
181188
/// - If you need [`connect flags`](ConnectFlags) or cross-thread signals, use [`builder()`][Self::builder].
182-
pub fn connect<F>(&self, mut function: F)
189+
pub fn connect<F>(&self, mut function: F) -> ConnectHandle
183190
where
184191
for<'c_rcv> F: SignalReceiver<(), Ps> + 'static,
185192
for<'c_rcv> IndirectSignalReceiver<'c_rcv, (), Ps, F>: From<&'c_rcv mut F>,
@@ -190,14 +197,14 @@ impl<C: WithSignals, Ps: InParamTuple + 'static> TypedSignal<'_, C, Ps> {
190197
.call((), args);
191198
});
192199

193-
self.inner_connect_godot_fn::<F>(godot_fn);
200+
self.inner_connect_godot_fn::<F>(godot_fn)
194201
}
195202

196203
/// Connect a method (member function) with `&mut self` as the first parameter.
197204
///
198205
/// - To connect to methods on other objects, use [`connect_other()`][Self::connect_other].
199206
/// - If you need [`connect flags`](ConnectFlags) or cross-thread signals, use [`builder()`][Self::builder].
200-
pub fn connect_self<F, Declarer>(&self, mut function: F)
207+
pub fn connect_self<F, Declarer>(&self, mut function: F) -> ConnectHandle
201208
where
202209
for<'c_rcv> F: SignalReceiver<&'c_rcv mut C, Ps> + 'static,
203210
for<'c_rcv> IndirectSignalReceiver<'c_rcv, &'c_rcv mut C, Ps, F>: From<&'c_rcv mut F>,
@@ -212,7 +219,7 @@ impl<C: WithSignals, Ps: InParamTuple + 'static> TypedSignal<'_, C, Ps> {
212219
.call(target_mut, args);
213220
});
214221

215-
self.inner_connect_godot_fn::<F>(godot_fn);
222+
self.inner_connect_godot_fn::<F>(godot_fn)
216223
}
217224

218225
/// Connect a method (member function) with any `&mut OtherC` as the first parameter, where
@@ -231,7 +238,8 @@ impl<C: WithSignals, Ps: InParamTuple + 'static> TypedSignal<'_, C, Ps> {
231238
&self,
232239
object: &impl ToSignalObj<OtherC>,
233240
mut method: F,
234-
) where
241+
) -> ConnectHandle
242+
where
235243
OtherC: UniformObjectDeref<Declarer>,
236244
for<'c_rcv> F: SignalReceiver<&'c_rcv mut OtherC, Ps> + 'static,
237245
for<'c_rcv> IndirectSignalReceiver<'c_rcv, &'c_rcv mut OtherC, Ps, F>: From<&'c_rcv mut F>,
@@ -246,6 +254,6 @@ impl<C: WithSignals, Ps: InParamTuple + 'static> TypedSignal<'_, C, Ps> {
246254
.call(target_mut, args);
247255
});
248256

249-
self.inner_connect_godot_fn::<F>(godot_fn);
257+
self.inner_connect_godot_fn::<F>(godot_fn)
250258
}
251259
}

0 commit comments

Comments
 (0)