Are signals fired synchronically?

The docs on Signal do not say anything about how they work internally in this regard.

Two questions.

  1. Does emit wait until the subscriber(s) receive and process the signal? It does not appear so but a confirmation would be welcome.

  2. If several signals are emitted in sequence, are they meant/guaranteed to be received in the same order as they were emitted? No clue here.

Thank you all.

1 Like
  1. No. Emitting a signal just lets any listeners know that the signal has been emitted, and the emitter carries on with its business. It doesn’t wait for a response. If it did wait for a response, then an emiiter with no listeners would be stuck waiting forever.

  2. That’s an interesting question. Here is a little test scenario I made, where I am observing the final value of test_variable:

@onready var debug_label = $CanvasLayer/Control/Label

var test_variable : int = 0

signal signal_1
signal signal_2
signal signal_3

func _ready():
	signal_1.connect(_on_signal_1)
	signal_2.connect(_on_signal_2)
	signal_3.connect(_on_signal_3)
	firing_signals()

func firing_signals():
	signal_1.emit()
	signal_2.emit()
	signal_3.emit()

func _on_signal_1():
	test_variable = 1
	
func _on_signal_2():
	for x in range(100000):
		var node = get_child(0)
		node.visible = not node.visible #introducing fake lag
	test_variable = 2

func _on_signal_3():
	test_variable = 3

func _process(_delta):
	debug_label.text = str(test_variable)

I ran this very simple test multiple times and the final value of test_variable did turn out to be 3 every time, but this may not happen if your situation is more complicated than this. I added the fake lag spike to see if the processing of 2 would delay the processing of 3, and it did. I think this happened because this is all happening on the same scene so it is handling the reactions to the signals in the order they are emitted. Makes sense, but this test is not really a good use of the signal pattern.

But, if there were multiple different scenes involved spread around the scene tree that were the listeners of the signals, I don’t know how reliably you can predict the result of test_variable. Maybe it depends on tree order, maybe it depends on what else is being processed at the moment. I’m not sure!

In the end though, it’s probably best not to rely on the order of execution of signals. If the order of execution is crucial, then it might be a good idea to refactor what you want to do into a different coding pattern that is more explicitly linear. Or maybe you can create a similar test using a bunch of scenes and let us know how it goes!

2 Likes

No, emit_signal doesn’t wait for subscribers to process the signal. It triggers the connected methods and immediately continues with the next line of code, so it’s non-blocking.

As for the order of signals, yes, they are received in the same order they are emitted. Godot ensures that signals are processed sequentially, so if you emit several in a row, they’ll be handled in that order.

2 Likes

Much, much appreciated, guys.

2 Likes

Are you sure about this? This and this reddit post as well as my own quick testing seem to suggest otherwise. That is, as long as the signal receiver is synchronous the emitter will only continue after the receivers are done.

1 Like

I think you’re right! My bad.

Interesting question. According to the first reddit thread, someone actually looked at the code (which is the only way to know for sure, empirical evidence is not adequate), "Yes. I used to think it stored the signals emitted and resolved all the connections later at the end of this loop, but on a hunch I checked the engine code and it actually calls all the connected methods as soon as you emit the signal, and only returns control to your function once everything is done. "

But what he didn’t indicate is what happens if there is no pending connect in a wait state? In this case I assume it simply returns and continues on.

1 Like

Out of interest, I had a quick peek at the source myself. As far as I can tell (I’m not a C++ programmer), the object keeps a map of all signals and their connected functions. When the signal is emitted, it iterates through the map/list of callables stored for the specific signal and calls them immediately (if not emitted deferred). If there is no receiver connected, the list is empty, no function is called, and the control is returned to the emitter.

Now that I was already looking at the source, I tried to figure out if signals are called in order. As it seems, they are. The signals are stored in a custom HashMap, but it is also partly a LinkedList for iteration. Therefore, they should be called in insertion order (there seems to be an exception for the CONNECT_REFERENCE_COUNTED flag, but I don’t know what it does).

That said, take all this with a grain of salt. As I’ve said, I’m not well-versed in C++, and I’m not very familiar with the Godot codebase.

2 Likes

So the emit calls are synchronic? That is a surprise.

This would mean that, for example, result data could be reported back to the emitter. It would certainly be there for the picking, at least.

Note: I tried to place an await in front of an emit and the following pops up:

(REDUNDANT_AWAIT): “await” keyword not needed in this case, because the expression isn’t a coroutine nor a signal.gdscript(25)

How could it be redundant if it is a synchronic call?

it seems to be saying it isn’t having to wait for another thread.

1 Like

It is already synchronous so there is no point in awaiting it. You only need to ‘await’ asynchronous functions.

As for ‘reporting’ a value. Yes that’s possible. You can simply imagine it as emit calling all receiver function right there.

1 Like

So to be clear the call into the functions that are waiting execution are executed on the same thread that called into emit()?

1 Like

Yes, as far as I understand it.

Also note that coroutines != multi-threaded. ‘await’ is for the former.

1 Like

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