Parent fuction yield/wait until child is _ready():

:information_source: Attention Topic was automatically imported from the old Question2Answer platform.
:bust_in_silhouette: Asked By The_Black_Chess_King

I am running into a problem where my parent will try to set_text() of some labels that are his children, but apparently they are not ready yet which returns “null instance” error and crashes the game.

How do we tell the parent to wait for the children ready signal?

I know that this is the problem, because if I write a script on the child, and call the same function on it’s parent using _ready(), it works.

A not ready instance and null instances are different things. You can set variables even before adding an instance to the scene so being ready or not isn’t a problem.
Use the remote scene view when the error happens to see if the child really exists.

hammeron | 2019-12-28 20:53

:bust_in_silhouette: Reply From: johnygames

I don’t know exactly how your code is structured, but I would advise you to call the function that creates/prepares the labels first and then write your text in those boxes. That way you make sure that the boxes will be ready before you attempt to reference them.

Another way is to use the yield keyword, which waits for a signal before it executes what you want it to execute. More on that here, here and in the docs here.

Your problem could well easily be solved by using the onready keyword before declaring your labels like this:

onready var lbl_foo = $Label_Foo

That way Godot loads the label first before it does anything to it.

Last but not least, you could create a Timer node and use its timeout() signal to execute your functions after some time has elapsed.
I hope these tips help you. But then again I do not know how your code and nodes are laid out.

Well, I used direct access to these labels, on the parent ready. Like:

func _ready():
    $NAME_ITEM.set_text(str(GLOBAL_ITEMS[ITEM_ID]))

These labels don’t change, they are only static texts, but I set them like this because they need to retrieve a data stored in a library. In this case a autoloadd which has all items data.

I am going to experiment more with the yield, as I think it’s a better solution. And as well try to reference the labels before-hand with the onready var.

Thank you for your response and the links!

The_Black_Chess_King | 2019-12-28 21:07

Strangely enough, this type of thing worked:

_ready():
    for node in $Labels.get_children():
    		match node.name:
	    		"TEST_LABEL":nodes.set_text("Cheese with Peanut butter.")

I tried every method mentioned in the answer and some other of my own, but my project didn’t liked any. I don’t know the reason, it remained a mystery to me.

Also I didn’t fully prompt the error, going to put it here,

 0:00:00.840   get_node: Node not found: TEST_LABEL.
  <C++ Error>   Condition ' !node ' is true. returned: __null
  <C++ Source>  scene/main/node.cpp:1381 @ get_node()
  <Stack Trace> Status_UI.gd:18 @ STATUS_UPDATE() 
                Status_UI.gd:14 @ _ready()

And here is the line of code on ready.

$TEST_LABEL.set_text(str(int(GPS.WeightMax)).pad_decimals(1))

The_Black_Chess_King | 2019-12-30 02:39

IoI found the real culprit, It was my fault, a old node was hidden in the scene lower on the list that was using the same script, and I didn’t noticed, but upon simply doing: print(self.name)printed 2 times. So, doing the match:prevented it from crashing, because it works almost like a if.

The_Black_Chess_King | 2019-12-30 15:32

:bust_in_silhouette: Reply From: DavidPeterWorks

I use this:

func _ready():
	yield(get_tree().root, "ready")
    do_dependent_stuff()

If you have autoload scripts and you reload the scene, use this:

yield(get_tree().root.get_child(get_tree().root.get_child_count()-1), "ready")
 

I’m using 3.5.1 and found this works:

yield(self, "ready")

(a shorter version of the code in the actual answer).

I’m using this in a custom initialise() func on a custom node that is dynamically added to the scene tree. My onready variables aren’t created until _ready() is called, and I need them in order to initialise them.

To avoid creating a dependent sequence where I add the custom node to the tree before initialising it, in my initialiser I wait until _ready() has fired before initialising the onready variables. This keeps my code clean and neat when I dynamically add the custom node, and works no matter which order I call my initialise() and add_child().

After all, it makes more sense (to me) to initialise the node before adding it to the tree from the context of the creator:

var customNodeInstance = customNodeScene.instance()
customNodeInstance.initialise(x, y)
add_child(customNodeInstance)

Zodman | 2022-11-21 22:40

:bust_in_silhouette: Reply From: angstyloop

Here’s another way you can do it.

You can connect the “tree_entered” signal on the child node to a method on the ancestor node.

If you are interested in children of the child node, you might want to use the “child_entered_tree” signal instead. Or you could connect directly to the “tree_entered” signal on the lower child.

In your ancestor node, which presumably needs to make a function call like get_node(“MyChildNode”) or get_node(“MyChildNode/MyLowerChildNode”), and which is presumably causing issues and throwing errors like “bad index on null thing”, you can define a “ready” boolean variable initialized to “false”. All your “tree_entered” or “child_entered_tree” signal handlers (defined in the ancestor Node’s script) is set “ready” equal to “true”.

Then in your function that actually needs to call get_node and use the child’s data and methods, before you use the child you first check if “tree_ready” is true. If “tree_ready” is still false, you just skip the logic that messes with the node. You try to design your application in a such a way that it doesn’t care when things get loaded.

# MyAncestorNode.gd

extends Node2D

var child_node_ready = false
var lower_child_node_ready = false

func _process(delta):
    if ! child_node_ready:
        return
    print($MyChildNode.position)
    if ! lower_child_node_ready:
        return
    print($MyChildNode/MyLowerChildNode.position)

func _on_MyChildNode_child_entered_tree():
    lower_child_node_ready = true

func _on_MyChildNode_tree_entered():
    child_node_ready = true