-
-
Notifications
You must be signed in to change notification settings - Fork 226
Inherited typed signals #1134
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
Merged
Merged
Inherited typed signals #1134
Conversation
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Upcast: signal collection jumps to nearest superclass with own signals. Import some symbols inside generated `signals` module.
Allows type-safe connect_self().
Addresses immediate issue where a `pub struct MyClass` has a private #[signal] declaration. This caused "leaks private type" problems with the associated type <MyClass as WithSignals>::SignalCollection, that mentions a generated (private) type. This approach can possibly be extended to the inverse case later, to allow the inverse case: pub #[signal]s in private classes.
API docs are being generated and will be shortly available at: https://godot-rust.github.io/docs/gdext/pr-1134 |
This was referenced Apr 21, 2025
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Labels
c: register
Register classes, functions and other symbols to GDScript
feature
Adds functionality to the library
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Following previous work on typed signals in #1000 and #1111.
Closes #1112.
Features
It is now possible to use signals from the base class without the
.clone().upcast().signals()
workaround:or from
self
:You can also trivially emit base signals:
Alternatives and trade-offs
This took me forever since I kept running into a wall with inheritance and trait hell. I had many ideas, which I implemented to varying degrees. Writing them down in case the question comes up why the current approach was chosen and not X, or to better understand what exactly led to this design.
Make
TypedSignal
completely "unlinked" without lifetime'c
, letting it only store aGd<T>
.emit
can easily cause double-borrow errors when invoked fromself
.&mut self
borrowed (precisely:BaseMut
guard), which then requires a lifetime.Back and forth between the "signal collection" (struct returned by
signals()
) being able to call one or multiple signals.let s = obj.signals(); s.first_sig(); s.second_sig();
is possible, but requires 2nd lifetimeTypedSignal<'a, 'c, ...>
.'a
renders temporary callsobj.signals.first_sig()
impossible, which is a big downgrade.Signal collection methods taking
self
or&mut self
:Deref
-enabled methods, so can't work for base classes.&mut self
with runtime checks to be invoked only once. Will be documented.Generic argument
C
(the static class on whichsignals()
is invoked)TypedSignal
was just typed with the class that declared the signals (e.g.Node
). NowC
can beMyClass
even if aNode
signal is manipulated.C
is necessary for only one thing:connect_self()
type safetyC
through all signal collections and all individual signal structs.Instead of fluent API, use something like
Signals::connect(self, Self::renamed, Self::handler)
C
and'c
deeply through multiple layers.Self::renamed
cannot beDeref
-inherited... so we'd still needSignals::connect(self.signals().renamed(), Self::handler)
&mut self
but can only do so once;Signals::emit(self.signals().damage_taken(), 123)
would then still need to deep-borrowself
throughself.signals()
.A macro
connect! { Node::renamed => Self::handler }
Explicitly selecting signals through
self.signals_of::<Node>().renamed()
func
,var
,const
, global API, class names, ...) and it's not really a point of complaint. Often people don't actually care (e.g. button handlers are inBaseButton
, notButton
). We at least have a dedicatedsignals()
namespace, and usually the number of total signals in a class is manageable.Inherits<Base>
bound, similar to theupcast_ref
pattern.Duplicate signals in each derived class
Deref
, but generates a ton of symbols in both Godot and user APIs.&mut self
despite once" issue.The current approach adds quite a bit of internal complexity and extra generic code. Hopefully the monomorphization is bearable; if it becomes a problem, we might look for simpler alternatives, e.g. splitting connect/emit APIs or duplicating signals in each class rather than
Deref
.Either way, the signals API is completely new, and this PR is just another addition in the chain of many before it. I'd like to settle for an initial version, but future API changes are still possible. We'll likely see more of the practical impact once more users start working with typed signals.