-
-
Notifications
You must be signed in to change notification settings - Fork 21.4k
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
C#: Add WeakEvent to store subscriptions of events #73730
base: master
Are you sure you want to change the base?
Conversation
22baabe
to
2f0d6da
Compare
This should also fix #71032 |
This comment was marked as off-topic.
This comment was marked as off-topic.
@juse4pro Please don't bump without contributing significant new information. Use the 👍 reaction button on the first post instead. This is still a draft so unsure when it'll be ready |
2f0d6da
to
5a4c78a
Compare
5a4c78a
to
cf99c3b
Compare
- Add `GodotWeakEvent` type that stores the subscriptions of events. - Keeps only a weak reference to the target and the method info of the handler, so the target can be garbage collected. - If the target is collected, it will eventually get rid of the subscription data and avoids raising the event for collected targets. - Used in user script events and native class events, resulting in consistent behavior. - Fix generation of signal events documentation that was adding the documentation comments in the wrong place.
cf99c3b
to
0254260
Compare
In general changing from strong events to weak events is a breaking change, eg The weak bits could also be limited to just methods on GodotObject to somewhat limit the potential impact of this but then there would likely still be breaks and apparent leaks. We could potentially make this opt-in/out-out on a per subscription basis to get the best of both, but that still requires care when using the signals. |
I think it should be fine to change the behavior here because it fixes a "bug". If you were previously making sure to disconnect from every signal then this shouldn't affect you, but if you weren't this should now do it for you. Automatically disconnecting signals is the behavior many users have come to expect because that's how it works in GDScript and in C# with the
As long as the delegate's target is still alive it should work the same way in both cases. If the target is not alive then removing the delegate from the subscriptions is the purpose of this PR, so users don't have to disconnect signals manually3. Maybe I misunderstood what you meant.
I think it would be weird if methods on GodotObject get automatically unregistered but not others.
I'm not sure what you mean but one of the goals of this PR is to unify the event code for Godot native proxy-classes and user-defined classes. I'd want the same behavior for all signals, otherwise it'd be confusing. Footnotes
|
The problem that I was trying to (poorly) describe is that if the signal handler is a closure, the event will be the only reference to the closure object, so if that reference is made a weak reference, that object can now be collected and the event handler can no longer be called. Example (that makes and actually can trigger this) binding additional arguments to a handler:
|
I see now, you are right when using closures they could be released and unsubscribed. Maybe documenting it could be enough. The solution would be as simple as storing the closure object. We could provide a helper method for this: Timer timer;
void M1(int arg) {
timer.Timeout += KeepAlive(() => Handler(arg)); // KeepAlive will store the closure object so it's not released
timer.Start();
}
void Handler(int arg) { } But it would be trivial for the user to write their own. static List<object> _keepAlives = new();
TEventHandler KeepAlive<TEventHandler>(TEventHandler handler) where TEventHandler : Delegate
{
_keepAlives.Add(handler); // Store the closure object somewhere so it's not released
return handler;
} The bigger problem is that users would need to know that they need to do this, I can see how this could be easily missed. I guess it would possible to write an analyzer to check for this cases but it may not be easy to implement. |
I think that a lot of people are confused by the C# event behavior (I know I am) because of this comment in the docs page on C# Signals:
It sounds like information is currently incorrect, and you do actually need to call I think that it is going to be very common to use lambda expressions like I'm no expert in this area, but could we maybe make use of the auto generated Even if that worked though, it still wouldn't be ideal because unregistering the handler wouldn't actually happen if you never emit the signal again, also I'm not sure on what the performance impact would be. Just a thought. |
Yes, object instances that are disposed without having event subscriptions that call into them removed with
I personally use this pattern all the time, both in the I'm afraid I can't contribute much in the way of suggested solutions here. My two cents is that if I had to choose between auto-disconnection when using |
GodotWeakEvent
type that stores the subscriptions of events.+=
are not automatically removed when receiver is destroyed #70414.-=
from engine signal events #73062.