Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Type-safe signals #1000

Draft
wants to merge 5 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion godot-core/src/builtin/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -146,9 +146,9 @@ pub use crate::{array, dict, real, reals, varray};

// Re-export generated enums.
pub use crate::gen::central::global_reexported_enums::{Corner, EulerOrder, Side, VariantOperator};
pub use crate::sys::VariantType;
// Not yet public.
pub(crate) use crate::gen::central::VariantDispatch;
pub use crate::sys::VariantType;

#[doc(hidden)]
pub mod __prelude_reexport {
Expand Down
2 changes: 1 addition & 1 deletion godot-core/src/classes/class_runtime.rs
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ pub(crate) fn display_string<T: GodotClass>(
obj: &Gd<T>,
f: &mut std::fmt::Formatter<'_>,
) -> std::fmt::Result {
let string: GString = obj.raw.as_object().to_string();
let string: GString = obj.raw.as_object_ref().to_string();
<GString as std::fmt::Display>::fmt(&string, f)
}

Expand Down
23 changes: 23 additions & 0 deletions godot-core/src/obj/gd.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ use crate::meta::{
ArrayElement, AsArg, CallContext, ClassName, CowArg, FromGodot, GodotConvert, GodotType,
ParamType, PropertyHintInfo, RefArg, ToGodot,
};
use crate::obj::cap::WithSignals;
use crate::obj::{
bounds, cap, Bounds, DynGd, EngineEnum, GdDerefTarget, GdMut, GdRef, GodotClass, Inherits,
InstanceId, RawGd,
Expand Down Expand Up @@ -327,6 +328,11 @@ impl<T: GodotClass> Gd<T> {
.expect("Upcast to Object failed. This is a bug; please report it.")
}

/// Equivalent to [`upcast_mut::<Object>()`][Self::upcast_mut], but without bounds.
pub(crate) fn upcast_object_mut(&mut self) -> &mut classes::Object {
self.raw.as_object_mut()
}

/// **Upcast shared-ref:** access this object as a shared reference to a base class.
///
/// This is semantically equivalent to multiple applications of [`Self::deref()`]. Not really useful on its own, but combined with
Expand Down Expand Up @@ -700,6 +706,23 @@ where
}
}

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

// ----------------------------------------------------------------------------------------------------------------------------------------------
// Trait impls

Expand Down
7 changes: 6 additions & 1 deletion godot-core/src/obj/raw_gd.rs
Original file line number Diff line number Diff line change
Expand Up @@ -206,11 +206,16 @@ impl<T: GodotClass> RawGd<T> {
// self.as_target_mut()
// }

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

pub(crate) fn as_object_mut(&mut self) -> &mut classes::Object {
// SAFETY: Object is always a valid upcast target.
unsafe { self.as_upcast_mut() }
}

/// # Panics
/// If this `RawGd` is null. In Debug mode, sanity checks (valid upcast, ID comparisons) can also lead to panics.
///
Expand Down
47 changes: 45 additions & 2 deletions godot-core/src/obj/traits.rs
Original file line number Diff line number Diff line change
Expand Up @@ -261,8 +261,8 @@ pub trait IndexEnum: EngineEnum {
// Possible alternative for builder APIs, although even less ergonomic: Base<T> could be Base<T, Self> and return Gd<Self>.
#[diagnostic::on_unimplemented(
message = "Class `{Self}` requires a `Base<T>` field",
label = "missing field `_base: Base<...>`",
note = "A base field is required to access the base from within `self`, for script-virtual functions or #[rpc] methods",
label = "missing field `_base: Base<...>` in struct declaration",
note = "A base field is required to access the base from within `self`, as well as for #[signal], #[rpc] and #[func(virtual)]",
note = "see also: https://godot-rust.github.io/book/register/classes.html#the-base-field"
)]
pub trait WithBaseField: GodotClass + Bounds<Declarer = bounds::DeclUser> {
Expand Down Expand Up @@ -571,6 +571,49 @@ pub mod cap {
fn __godot_property_get_revert(&self, property: StringName) -> Option<Variant>;
}

// Move one level up, like WithBaseField?
pub trait WithFuncs {
type FuncCollection;
type StaticFuncCollection;

fn static_funcs() -> Self::StaticFuncCollection;
fn funcs(&self) -> Self::FuncCollection;
}

pub trait WithSignals: WithBaseField {
type SignalCollection<'a>;

/// Access user-defined signals of the current object `self`.
///
/// For classes that have at least one `#[signal]` defined, returns a collection of signal names. Each returned signal has a specialized
/// API for connecting and emitting signals in a type-safe way. If you need to access signals from outside (given a `Gd` pointer), use
/// [`Gd::signals()`] instead.
///
/// If you haven't already, read the [book chapter about signals](https://godot-rust.github.io/book/register/signals.html) for a
/// walkthrough.
///
/// # Provided API
///
/// The returned collection provides a method for each signal, with the same name as the corresponding `#[signal]`. \
/// For example, if you have...
/// ```ignore
/// #[signal]
/// fn damage_taken(&mut self, amount: i32);
/// ```
/// ...then you can access the signal as `self.signals().damage_taken()`, which returns an object with the following API:
///
/// | Method signature | Description |
/// |------------------|-------------|
/// | `connect(f: impl FnMut(i32))` | Connects global or associated function, or a closure. |
/// | `connect_self(f: impl FnMut(&mut Self, i32))` | Connects a `&mut self` method or closure. |
/// | `emit(amount: i32)` | Emits the signal with the given arguments. |
///
fn signals(&mut self) -> Self::SignalCollection<'_>;

#[doc(hidden)]
fn __signals_from_external(external: &Gd<Self>) -> Self::SignalCollection<'_>;
}

/// Auto-implemented for `#[godot_api] impl MyClass` blocks
pub trait ImplementsGodotApi: GodotClass {
#[doc(hidden)]
Expand Down
11 changes: 10 additions & 1 deletion godot-core/src/private.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,6 @@ use std::sync::atomic;
#[cfg(debug_assertions)]
use std::sync::{Arc, Mutex};
use sys::Global;

// ----------------------------------------------------------------------------------------------------------------------------------------------
// Global variables

Expand Down Expand Up @@ -120,6 +119,16 @@ pub(crate) fn call_error_remove(in_error: &sys::GDExtensionCallError) -> Option<
call_error
}

// ----------------------------------------------------------------------------------------------------------------------------------------------
// Functional and signal APIs

// pub fn emit_signal<T>(obj: &mut BaseMut<T>, varargs: &[Variant])
// where
// T: GodotClass<Declarer = bounds::DeclEngine> + Inherits<crate::classes::Object>,
// {
// obj.upcast_mut().emit_signal(varargs);
// }

// ----------------------------------------------------------------------------------------------------------------------------------------------
// Plugin and global state handling

Expand Down
12 changes: 12 additions & 0 deletions godot-core/src/registry/functional/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
/*
* Copyright (c) godot-rust; Bromeon and contributors.
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/

mod typed_signal;
mod variadic;

pub use typed_signal::*;
pub use variadic::*;
Loading
Loading