Custom Signal Arguments - Identify the Sender Node. (i.e. How do I know what node emitted a signal?)

Godot Version

4.2.1

Question

I have a “Save” button that I want to be disabled while the save code is running so the user can’t just spam it. After the save code runs, the button will be reenabled.
I want to try to keep my code clean and avoid having a bunch of scripts with references to the internal structure of my root node, so I figured that the best way to go about this would be to emit a signal to my root node telling it the save has been pressed.
That said, I don’t want to hardcode in a node path into my Save method. What if I change the scene structure later? What if I rename the save button? etc.

Perhaps I am approaching this problem too much how I would handle events javascript, but my gut instinct was that the signal should be emitting the button node, and the recieving method should take a button node as an argument. Problem is this is not one of the Extra Call Arguments options for signals.
The closest thing to what I want is “NodePath”, however this seems to be a problem as well. I add the custom argument “NodePath”, select my savebutton node, but then the node path selection doesn’t appear to behave consistently (bug? user error?) either returning an absolute mess of a node path that doesn’t correspond to anything, or it just gives me the node path “.”, which my root node script interprets as itself.

What am I doing wrong, and what is the correct way to approach this?

i suggest to use event bus global autoload

1 Like

this can easily disable the Save button when it’s clicked, and will need to reenable it after saving is done, which for saving system should already have their own autoload script and just connect the “done saving” signal of the Saving system (which is emitted after saving is done) on the saving button UI will make it reenable the button

1 Like

This sounds like it probably does what I want, but I’m struggling to figure out how the heck to do this in C#.

Can you provide an example?

just noticed the csharp tag
but i do believe C# has its own way of sending signal like, Action Delegate
https://www.reddit.com/r/godot/comments/gv6bcj/tutorial_on_how_you_can_use_c_delegates_with_godot/

or if you still want to try the event bus on C#, scroll a bit below to see comment with c# code eventbus-like
https://www.reddit.com/r/godot/comments/131s509/event_bus_in_godot_4/

Your question seems to be getting a bit complicated. A solution could be to have a dedicated Node for saving, and the button could feature an [Export] option that only accepts instances of your save class. This class can inherit from your abstract BaseSave class. By doing this, you avoid hardcoding paths, and your save class can also interact seamlessly with the resource class, especially when enhanced with the [GlobalClass] attribute.

For synchronization, your button can await the completion of the save process. Ensure that your BaseSave class includes the necessary abstract methods for the Save class. This way, your Save class will always be compatible with your button, even if you create different systems.

I hesitate to recommend interfaces because you can’t include an interface in an export.

I figured out what I needed to do.
Incase anyone else comes across it, here is what I learned from zdrmlpzdrmlp

Project Settings > Autoload > add a new script. this is where i added EventBus.cs
now this class can be referenced anywhere by

GetNode<EventBus>("/root/EventBus");

Here is my definition for EventBus. it has 2 signals, SaveStarted and SaveCompleted. Note that it’s required that they end in “EventHandler”

public partial class EventBus : Node
{
    [Signal]
    public delegate void SaveStartedEventHandler(); 
    [Signal]
    public delegate void SaveCompletedEventHandler();

}

Here is my SaveButton class.
I create a private property _eventBus, then set the method that should be run when the SaveCompleted signal is emitted.
in the editor i configure the “pressed” signal to trigger SaveStarted in this class.
SaveStarted will emit a signal “SaveStarted” from the event bus, and disable the button.

public partial class SaveButton : Button
{
    private EventBus _eventBus;
    public override void _Ready()
    {
        _eventBus = GetNode<EventBus>("/root/EventBus");
        _eventBus.SaveCompleted += SaveComplete;
    }
    private void SaveStarted()
    {
        GD.Print("Save Started");
        Disabled = true;
        _eventBus.EmitSignal(nameof(EventBus.SaveStarted));
    }
    public void SaveComplete()
    {
        GD.Print("Save Completed");
        Disabled = false;
    }
}

In my Root node class (not the actual class, just the parts relevant to the discussion)
I again get the EventBus in a private property, then add the Save method to the SaveStarted event. now when a SaveStarted signal is emitted, i can run some save code (not yet implemented) and once it is completed emit the SaveCompleted signal.
This will trigger SaveComplete in the SaveButton class, which will reenable the button.

public partial class Root : Control
{
    private EventBus _eventBus;
    public override void _Ready()
    {
        _eventBus = GetNode<EventBus>("/root/EventBus");
        _eventBus.SaveStarted += Save;
    }
    private void Save()
    {
        GD.Print("Saved");
        _eventBus.EmitSignal(nameof(EventBus.SaveCompleted));
    }
}

Hopefully this helps somebody else. I’ve included print statements to help verify it is working.
if configured correctly, clicking the save button should print

Save Started
Saved
Save Completed

Edit:
For posterity, incase anyone gets frustrated because I didn’t do what I original asked, this is how you would get a reference to the node that emitted the signal:

_eventBus.EmitSignal(nameof(EventBus.SaveStarted), this);
[Signal]
public delegate void SaveStartedEventHandler(Button button);
private void Save(Button button)

If you absolutely must pass a reference to the emitting node (in this case, SaveButton) you simply need to define the event handler to expect a reference to whatever object type you intend to emit as an arugment, then pass “this” as an argument in EmitSignal.

I didn’t do it this way because I felt that it was probably bad practice for my root node to have any control over the behavior of my button. Rather, the button itself should simply react appropriately to being pushed without any other modules being concerned with implementation details.

1 Like

This topic was automatically closed 30 days after the last reply. New replies are no longer allowed.