Skip to content

Signal connections established with += are not automatically removed when receiver is destroyed #70414

Open
@derkork

Description

Godot version

v4.0.beta9.mono.official [e780dc3]

System information

Windows 10

Issue description

Assume you create a custom node class which emits a signal every second:

// Sender.cs
	[Signal]
	public delegate void MySignalEventHandler();
	
	public override void _Ready()
	{
		GetTree().CreateTimer(1).Timeout += OnTimeout;
	}

	
	private void OnTimeout()
	{
		EmitSignal(nameof(MySignal));
		GetTree().CreateTimer(1).Timeout += OnTimeout;
	}

Now you have a receiver which connects to this signal either with the classic "Connect" method or with the new += syntax. The receiver destroys itself after a few seconds:

// Receiver.cs
 public override void _Ready()
    {
        if (UseConnect)
        {
            Sender.Connect(nameof(Sender.MySignal), Callable.From(Receive));
        }
        else
        {
            Sender.MySignal += Receive;
        }
        
        // destroy after the specified time
        if (DestroyAfterSeconds > 0)
        {
            GetTree().CreateTimer(DestroyAfterSeconds).Timeout += QueueFree;
        }
    }

Either way, I would expect that the signal is automatically disconnected when when the receiver is destroyed. This works when using the Connect method, but doesn't work when using the += method. In the latter case the signal connection stays and the sender throws an exception when it emits the signal the next time:

E 0:00:10:0018   void Godot.Bridge.ScriptManagerBridge.RaiseEventSignal(IntPtr , Godot.NativeInterop.godot_string_name* , Godot.NativeInterop.godot_variant** , Int32 , Godot.NativeInterop.godot_bool* ): System.ObjectDisposedException: Cannot access a disposed object.
Object name: 'sasake.Receiver'.
  <C++ Error>    Exception
  <C++ Source>   /root/godot/modules/mono/glue/GodotSharp/GodotSharp/Core/Object.base.cs:73 @ IntPtr Godot.Object.GetPtr(Godot.Object )()
  <Stack Trace>  Object.base.cs:73 @ IntPtr Godot.Object.GetPtr(Godot.Object )()
                 Node.cs:590 @ Godot.StringName Godot.Node.GetName()()
                 Node.cs:290 @ Godot.StringName Godot.Node.get_Name()()
                 Receiver.cs:31 @ void sasake.Receiver.Receive()()
                 Sender_ScriptSignals.generated.cs:26 @ void Sender.RaiseGodotClassSignalCallbacks(Godot.NativeInterop.godot_string_name& , Godot.NativeInterop.NativeVariantPtrArgs )()
                 ScriptManagerBridge.cs:341 @ void Godot.Bridge.ScriptManagerBridge.RaiseEventSignal(IntPtr , Godot.NativeInterop.godot_string_name* , Godot.NativeInterop.godot_variant** , Int32 , Godot.NativeInterop.godot_bool* )()

It looks like the sender tries to invoke a callback on the now disposed receiver. Again this works fine when using Connect to connect the signal.

Steps to reproduce

In the example project open the sender.tscn scene and run it. You can see in the output that the receiver receives two signals before it self destructs.

image

After that you can see the exception raised in the sender:

image

When you switch the receiver to use "Connect" using the inspector:

image

Then everything works as expected.

Minimal reproduction project

example.zip

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    • Status

      In Progress

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions