Skip to content

Commit ad42a35

Browse files
authored
Merge pull request #1000 from godot-rust/feature/signals
Type-safe signals
2 parents 5bf9f8d + 4a25b76 commit ad42a35

File tree

30 files changed

+1435
-158
lines changed

30 files changed

+1435
-158
lines changed

.github/workflows/full-ci.yml

+1
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,7 @@ jobs:
8585
RUSTDOCFLAGS: >
8686
-D rustdoc::broken-intra-doc-links -D rustdoc::private-intra-doc-links -D rustdoc::invalid-codeblock-attributes
8787
-D rustdoc::invalid-rust-codeblocks -D rustdoc::invalid-html-tags -D rustdoc::bare-urls -D rustdoc::unescaped-backticks
88+
-D warnings
8889
run: cargo doc -p godot --ignore-rust-version
8990

9091

godot-core/src/builtin/mod.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -146,9 +146,9 @@ pub use crate::{array, dict, real, reals, varray};
146146

147147
// Re-export generated enums.
148148
pub use crate::gen::central::global_reexported_enums::{Corner, EulerOrder, Side, VariantOperator};
149-
pub use crate::sys::VariantType;
150149
// Not yet public.
151150
pub(crate) use crate::gen::central::VariantDispatch;
151+
pub use crate::sys::VariantType;
152152

153153
#[doc(hidden)]
154154
pub mod __prelude_reexport {

godot-core/src/classes/class_runtime.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ pub(crate) fn display_string<T: GodotClass>(
4747
obj: &Gd<T>,
4848
f: &mut std::fmt::Formatter<'_>,
4949
) -> std::fmt::Result {
50-
let string: GString = obj.raw.as_object().to_string();
50+
let string: GString = obj.raw.as_object_ref().to_string();
5151
<GString as std::fmt::Display>::fmt(&string, f)
5252
}
5353

godot-core/src/meta/mod.rs

+3
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,9 @@ pub use class_name::ClassName;
6060
pub use godot_convert::{FromGodot, GodotConvert, ToGodot};
6161
pub use traits::{ArrayElement, GodotType, PackedArrayElement};
6262

63+
#[cfg(since_api = "4.2")]
64+
pub use crate::registry::signal::variadic::ParamTuple;
65+
6366
pub(crate) use array_type_info::ArrayTypeInfo;
6467
pub(crate) use traits::{
6568
element_godot_type_name, element_variant_type, GodotFfiVariant, GodotNullableFfi,

godot-core/src/obj/gd.rs

+23-1
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ use crate::meta::{
2020
};
2121
use crate::obj::{
2222
bounds, cap, Bounds, DynGd, GdDerefTarget, GdMut, GdRef, GodotClass, Inherits, InstanceId,
23-
RawGd,
23+
RawGd, WithSignals,
2424
};
2525
use crate::private::callbacks;
2626
use crate::registry::property::{object_export_element_type_string, Export, Var};
@@ -327,6 +327,11 @@ impl<T: GodotClass> Gd<T> {
327327
.expect("Upcast to Object failed. This is a bug; please report it.")
328328
}
329329

330+
/// Equivalent to [`upcast_mut::<Object>()`][Self::upcast_mut], but without bounds.
331+
pub(crate) fn upcast_object_mut(&mut self) -> &mut classes::Object {
332+
self.raw.as_object_mut()
333+
}
334+
330335
/// **Upcast shared-ref:** access this object as a shared reference to a base class.
331336
///
332337
/// This is semantically equivalent to multiple applications of [`Self::deref()`]. Not really useful on its own, but combined with
@@ -700,6 +705,23 @@ where
700705
}
701706
}
702707

708+
impl<T> Gd<T>
709+
where
710+
T: WithSignals,
711+
{
712+
/// Access user-defined signals of this object.
713+
///
714+
/// For classes that have at least one `#[signal]` defined, returns a collection of signal names. Each returned signal has a specialized
715+
/// API for connecting and emitting signals in a type-safe way. This method is the equivalent of [`WithSignals::signals()`], but when
716+
/// called externally (not from `self`). If you are within the `impl` of a class, use `self.signals()` directly instead.
717+
///
718+
/// If you haven't already, read the [book chapter about signals](https://godot-rust.github.io/book/register/signals.html) for a
719+
/// walkthrough.
720+
pub fn signals(&self) -> T::SignalCollection<'_> {
721+
T::__signals_from_external(self)
722+
}
723+
}
724+
703725
// ----------------------------------------------------------------------------------------------------------------------------------------------
704726
// Trait impls
705727

godot-core/src/obj/raw_gd.rs

+6-1
Original file line numberDiff line numberDiff line change
@@ -210,11 +210,16 @@ impl<T: GodotClass> RawGd<T> {
210210
// self.as_target_mut()
211211
// }
212212

213-
pub(crate) fn as_object(&self) -> &classes::Object {
213+
pub(crate) fn as_object_ref(&self) -> &classes::Object {
214214
// SAFETY: Object is always a valid upcast target.
215215
unsafe { self.as_upcast_ref() }
216216
}
217217

218+
pub(crate) fn as_object_mut(&mut self) -> &mut classes::Object {
219+
// SAFETY: Object is always a valid upcast target.
220+
unsafe { self.as_upcast_mut() }
221+
}
222+
218223
/// # Panics
219224
/// If this `RawGd` is null. In Debug mode, sanity checks (valid upcast, ID comparisons) can also lead to panics.
220225
///

godot-core/src/obj/script.rs

+6-6
Original file line numberDiff line numberDiff line change
@@ -120,9 +120,10 @@ pub trait ScriptInstance: Sized {
120120
args: &[&Variant],
121121
) -> Result<Variant, sys::GDExtensionCallErrorType>;
122122

123-
/// Identifies the script instance as a placeholder. If this function and
124-
/// [IScriptExtension::is_placeholder_fallback_enabled](crate::classes::IScriptExtension::is_placeholder_fallback_enabled) return true,
125-
/// Godot will call [`Self::property_set_fallback`] instead of [`Self::set_property`].
123+
/// Identifies the script instance as a placeholder, routing property writes to a fallback if applicable.
124+
///
125+
/// If this function and [IScriptExtension::is_placeholder_fallback_enabled] return true, Godot will call [`Self::property_set_fallback`]
126+
/// instead of [`Self::set_property`].
126127
fn is_placeholder(&self) -> bool;
127128

128129
/// Validation function for the engine to verify if the script exposes a certain method.
@@ -157,8 +158,7 @@ pub trait ScriptInstance: Sized {
157158
/// The engine may call this function if it failed to get a property value via [`ScriptInstance::get_property`] or the native type's getter.
158159
fn property_get_fallback(&self, name: StringName) -> Option<Variant>;
159160

160-
/// The engine may call this function if
161-
/// [`IScriptExtension::is_placeholder_fallback_enabled`](crate::classes::IScriptExtension::is_placeholder_fallback_enabled) is enabled.
161+
/// The engine may call this function if [`IScriptExtension::is_placeholder_fallback_enabled`] is enabled.
162162
fn property_set_fallback(this: SiMut<Self>, name: StringName, value: &Variant) -> bool;
163163

164164
/// This function will be called to handle calls to [`Object::get_method_argument_count`](crate::classes::Object::get_method_argument_count)
@@ -347,7 +347,7 @@ pub unsafe fn create_script_instance<T: ScriptInstance>(
347347
/// This function both checks if the passed script matches the one currently assigned to the passed object, as well as verifies that
348348
/// there is an instance for the script.
349349
///
350-
/// Use this function to implement [`IScriptExtension::instance_has`](crate::classes::IScriptExtension::instance_has).
350+
/// Use this function to implement [`IScriptExtension::instance_has`].
351351
#[cfg(since_api = "4.2")]
352352
pub fn script_instance_exists<O, S>(object: &Gd<O>, script: &Gd<S>) -> bool
353353
where

godot-core/src/obj/traits.rs

+36-2
Original file line numberDiff line numberDiff line change
@@ -261,8 +261,8 @@ pub trait IndexEnum: EngineEnum {
261261
// Possible alternative for builder APIs, although even less ergonomic: Base<T> could be Base<T, Self> and return Gd<Self>.
262262
#[diagnostic::on_unimplemented(
263263
message = "Class `{Self}` requires a `Base<T>` field",
264-
label = "missing field `_base: Base<...>`",
265-
note = "A base field is required to access the base from within `self`, for script-virtual functions or #[rpc] methods",
264+
label = "missing field `_base: Base<...>` in struct declaration",
265+
note = "A base field is required to access the base from within `self`, as well as for #[signal], #[rpc] and #[func(virtual)]",
266266
note = "see also: https://godot-rust.github.io/book/register/classes.html#the-base-field"
267267
)]
268268
pub trait WithBaseField: GodotClass + Bounds<Declarer = bounds::DeclUser> {
@@ -428,6 +428,40 @@ pub trait WithBaseField: GodotClass + Bounds<Declarer = bounds::DeclUser> {
428428
}
429429
}
430430

431+
pub trait WithSignals: WithBaseField {
432+
type SignalCollection<'a>;
433+
434+
/// Access user-defined signals of the current object `self`.
435+
///
436+
/// For classes that have at least one `#[signal]` defined, returns a collection of signal names. Each returned signal has a specialized
437+
/// API for connecting and emitting signals in a type-safe way. If you need to access signals from outside (given a `Gd` pointer), use
438+
/// [`Gd::signals()`] instead.
439+
///
440+
/// If you haven't already, read the [book chapter about signals](https://godot-rust.github.io/book/register/signals.html) for a
441+
/// walkthrough.
442+
///
443+
/// # Provided API
444+
///
445+
/// The returned collection provides a method for each signal, with the same name as the corresponding `#[signal]`. \
446+
/// For example, if you have...
447+
/// ```ignore
448+
/// #[signal]
449+
/// fn damage_taken(&mut self, amount: i32);
450+
/// ```
451+
/// ...then you can access the signal as `self.signals().damage_taken()`, which returns an object with the following API:
452+
///
453+
/// | Method signature | Description |
454+
/// |------------------|-------------|
455+
/// | `connect(f: impl FnMut(i32))` | Connects global or associated function, or a closure. |
456+
/// | `connect_self(f: impl FnMut(&mut Self, i32))` | Connects a `&mut self` method or closure. |
457+
/// | `emit(amount: i32)` | Emits the signal with the given arguments. |
458+
///
459+
fn signals(&mut self) -> Self::SignalCollection<'_>;
460+
461+
#[doc(hidden)]
462+
fn __signals_from_external(external: &Gd<Self>) -> Self::SignalCollection<'_>;
463+
}
464+
431465
/// Extension trait for all reference-counted classes.
432466
pub trait NewGd: GodotClass {
433467
/// Return a new, ref-counted `Gd` containing a default-constructed instance.

godot-core/src/private.rs

-1
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,6 @@ use std::sync::atomic;
2626
#[cfg(debug_assertions)]
2727
use std::sync::{Arc, Mutex};
2828
use sys::Global;
29-
3029
// ----------------------------------------------------------------------------------------------------------------------------------------------
3130
// Global variables
3231

godot-core/src/registry/mod.rs

+7
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,13 @@ pub mod method;
1515
pub mod plugin;
1616
pub mod property;
1717

18+
#[cfg(since_api = "4.2")]
19+
pub mod signal;
20+
21+
// Contents re-exported in `godot` crate; just keep empty.
22+
#[cfg(before_api = "4.2")]
23+
pub mod signal {}
24+
1825
// RpcConfig uses MultiplayerPeer::TransferMode and MultiplayerApi::RpcMode, which are only enabled in `codegen-full` feature.
1926
#[cfg(feature = "codegen-full")]
2027
mod rpc_config;

0 commit comments

Comments
 (0)