-
-
Notifications
You must be signed in to change notification settings - Fork 221
Description
🔍 Summary
When calling await ToSignal(DialogueManager.Instance, "dialogue_ended")
multiple times in C#, the number of connected signal handlers increases with each call. These handlers are never disconnected, leading to callback accumulation, and eventually triggering NullReferenceException on subsequent signal emissions.
💻 Environment
Godot version: 4.4
Language: C# (.NET 6 / Mono)
DialogueManager version: newest
Platform: Windows 10
Runtime context: Using DialogueManager.Instance.StartAsync(...) from C#
🧪 Steps to Reproduce
Call this method multiple times (e.g. triggered via player interaction):
public override async Task _InteractAsync()
{
GD.Print("---Call Open");
await DialogManager.Instance.StartAsync("res://dialogue/convesations/open_tulanh.dialogue", "start", this); //Don't mind my `DialogManager` function, it just internally does extra processing to make the Dialog follow the player.
await ToSignal(DialogueManager.Instance, "dialogue_ended");
GD.Print("---Call Close");
}
After the first interaction, everything works normally.
From the second interaction onward, the following error is logged:
E 0:00:09:775 void System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1+AsyncStateMachineBox`1.MoveNext(System.Threading.Thread): System.NullReferenceException: Object reference not set to an instance of an object.
<C++ Error> System.NullReferenceException
<Stack Trace> SignalAwaiter.cs:58 @ Godot.SignalAwaiter.SignalCallback(...)
Despite the error, execution continues to the end of the method.
🔎 Additional Debug Info
I added this debug line before emit_signal("dialogue_ended") inside the GDScript DialogueManager:
print("Listeners count:", get_signal_connection_list("dialogue_ended").size())
The listener count increases with each interaction. Example:
1st run: Listeners count: 1
2nd run: Listeners count: 2
3rd run: Listeners count: 3
...
This confirms that ToSignal creates a new signal connection each time, but the connection is not cleaned up after completion.
The internal callback is still being triggered later, but it seems the C# side has already disposed the corresponding await state, leading to a NullReferenceException.
✅ Expected Behavior
ToSignal(...) should automatically disconnect the signal handler after it resumes the task — or the library should provide a safe wrapper to prevent this accumulation.
🧼 Workaround
I replaced the ToSignal call with a manual connection using TaskCompletionSource:
public Task WaitForDialogueEnded()
{
var tcs = new TaskCompletionSource();
void Handler(Variant st)
{
DialogueManager.Instance.Disconnect("dialogue_ended", Callable.From((Action)Handler));
tcs.SetResult();
}
DialogueManager.Instance.Connect("dialogue_ended", Callable.From((Action)Handler));
return tcs.Task;
}
Now I call:
await WaitForDialogueEnded();
This prevents signal handler buildup and eliminates the exception.
P/S: I also tested ToSignal(...) with both a custom C# signal and a custom GDScript signal emitted from C#, and everything worked as expected — the handler was automatically disconnected after completion. This issue appears to be specific to the way dialogue_ended is implemented or emitted within the DialogueManager library.
P/s 2. I've tried to use like this await ToSignal(GetNode("/root/DialogueManager"), "dialogue_ended");
but issue remain