Deferred calls and API callbacks

In my spare time, I enjoy browsing through GitHub and reading other people’s code to see how much I can understand. Recently, I came across the official Godot demo projects, specifically the multiplayer pong demo.

I’m having trouble understanding the concept of deferred calls. I get what they are doing, but I’m not clear on when I should use them. My understanding is that deferred calls execute a method during “idle time,” when the CPU isn’t processing anything else, making it safe to do operations that might interfere with other code, like modifying the scene tree.

Here’s the relevant code from the project:

#region Network callbacks from SceneTree
# Callback from SceneTree.
func _player_connected(_id: int) -> void:
    # Someone connected, start the game!
    var pong: Node2D = load("res://pong.tscn").instantiate()
    # Connect deferred so we can safely erase it from the callback.
    pong.game_finished.connect(_end_game, CONNECT_DEFERRED)

    get_tree().get_root().add_child(pong)
    hide()

func _server_disconnected() -> void:
    _end_game("Server disconnected.")
#endregion

#region Game creation methods
func _end_game(with_error: String = "") -> void:
    if has_node("/root/Pong"):
        # Erase immediately, otherwise network might show
        # errors (this is why we connected deferred above).
        get_node(^"/root/Pong").free()
        show()

I’m particularly confused about the comment, “Connect deferred so we can safely erase it from the callback.” From what I understand, we are using the multiplayer API and hooking our methods via signals, which act as callbacks. I need help understanding what it means to “erase from the callback” and what does “safely erase” imply? What are the consequences of doing it non-safely? I also didn’t get the “erase immediately” part in _end_game method.

Can someone clarify this for me?

The Callback is the function that gets called when the game_finished signal is sent. In this case, it is called _end_game.

So we need to safely erase/free ^"/root/Pong" by a) deferring the callback until the system is in some known state (idle perhaps) and then in the callback verifying that the node ^"/root/Pong" does exist in memory before erasing. Otherwise you will get an error on the free() method saying something like node was previously erased.

Note the Callback _end_game is just a function and can be called independently as _server_disconnected() does.

1 Like

Thank you for the clarification!

Are there any other instances where I would need to use a deferred call besides erasing something from memory? Race conditions come to mind, but I can’t think of specific examples where they might occur.

Edit: I suppose accessing an already deleted node is a type of race condition. I feel like I might be getting too technical, which sometimes leaves me feeling out of the loop. When I don’t fully understand something, I worry I’m missing out on knowledge that could be useful in the future.

You may have to defer until a process has settled. What if an event happens in the middle of add_child() and your event modifies some aspect of that, e.g. adding a panel and gets shown in the wrong place.

It really is not a perfect science, but I think if something is have baked then you can also suspect that a processs was interrupted and messed up.

This is an interesting discussion

https://www.reddit.com/r/godot/comments/17yvwnp/in_which_cases_do_you_use_call_deferred/

Also the docs

1 Like