Signal Defined in Gdscript: How Do I Connect to a Method in C#?

Godot Version

4.3

Question

I am converting part of my project from gdscript to C# for performance reasons. In the original, a gdscript client can define a signal and call a signal registration method in server class that looks like this:

func add_input_signal(port: int, s: Signal) -> void:
	var signal_receiver = func(value: int) -> void: _insert_new_event(
		port, value
	)
	s.connect(signal_receiver)

# Utility function to add an input event to the queue
func _insert_new_event(port: int, value: int) -> void:
... # the registration automatically connects a port to any incoming signal.

The gdscript client has a signal defined thus:
signal input_100(value: int)
followed by:
server.add_input_signal(100, input_100)
to connect the signal to a particular server port. Then to emit the signal the client does something like:
input_100.emit(666)

The example is a little over-complicated because it uses a Lambda to hide the port parameter. All Iā€™m really doing is trying to pass a signal object to the server and as a function argument, and let the server connect. Now I am rewriting the server in C#, which I am not very familiar with. My naive translation of the server registration method is:

public void AddInputSignal(int port, Godot.Signal s)
{
	var signal_receiver = (int value) => _InsertNewEvent(port, value);
	s.Connect(signal_receiver);
}

Unfortunately, the Godot.Signal parameter s has no Connect method.

I have read the page on C# signals in the documentation, but it doesnā€™t seem like my particular use case is represented. Or, if it is, I am lacking the C# sophistication to recognize it :slight_smile:

Any tips for me? TIA

Edit: this probably isnā€™t a huge issue; I think I can just put a gdscript interface around the C# code and keep the C# simple.

A better design would be to return the signal receiver lambda, letting the caller decide what to do with it. E.g.

public Action<int> GetInputSignal(int port)
{
    return (int value) => _InsertNewEvent(port, value);   
}

Then on the caller side:

input_100 += GetInputSignal(100);

(or the equivalent connect method)

1 Like

I thought this was going to work, but it does not. The Action return type makes the handler not recognizable by gdscript. It fails with ā€œInvalid call. Nonexistent function ā€˜GetInputSignalā€™ in baseā€¦ā€. Now that Iā€™ve spent some more time thinking about this, I may come up with an alternative.

It looks like the C# integration makes it impossible(?) to dynamically create event handler delegates using lambdas, as I was attempting.

This ended up being a little convoluted, but I was able to retain the original API by adding a helper class that can be instantiated on a per-signal basis, and which will associate the port with the user-supplied signal:

using Godot;

[GlobalClass]
public partial class AMCForthInput : Godot.RefCounted
{
    private AMCForth Forth;
    private int Port;

    private void Emit(int value)
    {
        Forth.InputEvent(Port, value);
    }

    public void Initialize(AMCForth forth, int port, Signal s)
    {
        Forth = forth;
        Port = port;
        Callable handler = new(this, MethodName.Emit);
        s.Owner.Connect(s.Name, handler);
    }
}

Now the original API for connecting a user-supplied signal to an input port is a one-liner:

public void AddInputSignal(int port, Signal s)
{
    new AMCForthInput().Initialize(this, port, s);
}

...

// this will send an input value to a specific port
public void InputEvent(int port, int value)
    {  
...

And the whole thing is called from gdscript, thus:

signal input_100(value: int)

...

func _ready() -> void:
    forth.AddInputSignal(100, input_100)

...

# and when it's time to emit the signal, 666 goes to port 100
input_100.emit(666)

Boy that was fun!

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