Possible signal delays during inter-node communication

Godot Version

v4.1.1.stable.official [bd6af8e0e]

Question

I’m trying to figure out inter-node communication. Consider a node tree that looks like this:

Game (Node2D)
– | Parent (Node)
----- | Child1 (Node)
----- | Child2 (Node)

Let’s say if a player presses a key, certain data is passed up to Parent from Child1 and Child2 at the same time via parameterized signals. The Parent then uses this data to do whatever calculations, check win conditions etc. This would be one iteration of the main game loop.

What I’m worried about is whether or not this is the right way to use nodes and node inheritance. For example calculating score is something that only happens after Parent receives signals from both children. How do I ensure that the data reaches Parent at the same time? Is this an asynchronous process and do I have to ‘await’ all the data before proceeding to processing? If a lot of large data was passed through one signal, would it take longer for it to reach Parent? Similarly if there’s some kind of time-consuming process running on a child node before emitting a signal would THAT cause a delay? The same questions are of course also applicable to child-child communication as well.

My initial approach was to handle input and all the other processing on the Parent itself and send any proceeding signals (changes in sprites etc.) down to the children, but scrapped it after learning about “Call Down, Signal Up & Across”. Another approach is to just do everything on a single node, but this doesn’t seem Godot-ish.

What is the right approach here?

EDIT: “Signals are asynchronous.” This is NOT correct in my original answer.
Signals are synchronous - sorry for any confusion.

And yes time consuming processes can lead to a delay in signal processing. Signals are very powerful though and highly optimised.

There is no ‘right’ approach. However I would have a process in the parent that would collect signal a, and signal b, and only process when both have been received. Something like:

func _on_child1_data(data):
    child1_data = data
    process_data_if_ready()

func _on_child2_data(data):
    child2_data = data
    process_data_if_ready()

func process_data_if_ready():
    if child1_data != null and child2_data != null:
        # Process the data,

You could even use a timer to reset child1_data if that is required too. It very much depends on your usage.

For simple child-parent relationships I often use something like this too for the children:

@onready var HOST = get_parent()

For simple child to parent calls I can then do away with signals and just call a function in the parent directly like this:

HOST.child1_data = data
HOST.process_data_if_ready()

Hope that makes sense and helps in some way.

2 Likes

Signals are actually called synchronously, all listeners are called immediately and carry over the same stack that emitted the signal.
You can wait for signals asynchronously by creating a coroutine with await keyword in the method.

2 Likes

@wchc yes you are right and I was wrong. They are synchronous and are called immediately.

However, it does not always feel like that when you get complex signal and re-signal set ups. I do stand corrected but I believe that when you have signals in physics processes you have to be a bit more careful.

PS Just to stop any confusion or misinformation, I will edit my original post. Thank you for pointing out the error.

2 Likes