Call_deferred on add_child

Godot Version

4.2.2

Question

I was using add_child in a function that is connected to a signal, if I just call the function it works, but if I emit the signal, it works and gives me an error saying I should use “call_deferred” on the add_child statement. The child that I am creating gets it’s global_position set right after it is created, and my issue was that if I use call_deferred, the position is incorrect, but if I don’t the position works. It isn’t much of an issue since I make the child follow the player so it’s position gets fixed anyway, but I was just wondering why call_deferred seems to make global_position set incorrectly.

No. It sounds like you’re first setting the signal wrong. But, then you fix the signal. The signal sends the emitters. And, again your position on call_deferred is wrong. You’re describing battling two incorrect codes. Maybe a snippet would help.

extends Node2D

@onready var timer = $Timer
@onready var animated_sprite_2d = $AnimatedSprite2D
@onready var los_raycast = $"LOS Raycast"

@export var in_range_targets: Array[CharacterBody2D] = []
@export var los_targets: Array[CharacterBody2D] = []
@export var tethered: bool = false:
	get:
		return tethered
	set(value):
		if value == true:
			tethered = value
			var current_frame = animated_sprite_2d.get_frame()
			var current_progress = animated_sprite_2d.get_frame_progress()
			animated_sprite_2d.play("Tethered")
			animated_sprite_2d.set_frame_and_progress(current_frame, current_progress)
		else:
			tethered = value
			var current_frame = animated_sprite_2d.get_frame()
			var current_progress = animated_sprite_2d.get_frame_progress()
			animated_sprite_2d.play("Untethered")
			animated_sprite_2d.set_frame_and_progress(current_frame, current_progress)
@export var tethered_player: CharacterBody2D

var on_player_vertex = preload("res://Scenes/Objects/player_tether_node.tscn")
var created_vertices = [self]
signal tether_created(player, previous_target)
signal tether_broken(vertex)

func _physics_process(delta):
	var space_state = get_world_2d().direct_space_state
	
	if tethered == true && tethered_player != null:
		var query = PhysicsRayQueryParameters2D.create(los_raycast.global_position, tethered_player.global_position,
		0b00000000_00000000_00000000_00000011, [self])
		query.collide_with_areas = true
		var result = space_state.intersect_ray(query)
		if !result.is_empty():
			print("Tether Broken", ", Sent from: ", self, "\n")
			untether()
			
	for target in in_range_targets:
		var query = PhysicsRayQueryParameters2D.create(los_raycast.global_position, target.global_position,
		0b00000000_00000000_00000000_00000011, [self])
		query.collide_with_areas = true
		var result = space_state.intersect_ray(query)
		if result.is_empty() && !los_targets.has(target) && timer.is_stopped():
			timer.start()
		elif !result.is_empty() && los_targets.has(target):
			los_targets.erase(target)

func _on_area_2d_body_entered(body):
	in_range_targets.append(body)
	if !in_range_targets.is_empty():
		timer.start()

func _on_area_2d_body_exited(body):
	in_range_targets.erase(body)
	los_targets.erase(body)
	if body == tethered_player:
		untether()

func _on_timer_timeout():
	los_check()

func tether(target: CharacterBody2D):
	tethered = true
	tethered_player = target
	tether_created.emit(tethered_player, self)
	print("Is tethered: ", tethered, " Sent from: ", self, "\n")
	print("Tethered to: ", tethered_player, " Sent from: ", self, "\n")

func untether():
	print("Untethered, Sent from: ", self, "\n")
	tethered = false
	los_targets.erase(tethered_player)
	tethered_player = null
	tether_broken.emit(self)
	if !los_targets.is_empty():
		timer.start()

func los_check():
	los_raycast.enabled = true
	for target in in_range_targets:
		los_raycast.target_position = -(los_raycast.global_position - target.global_position)
		los_raycast.force_raycast_update()
		if (los_raycast.get_collider() == null) && (los_targets.find(target) == -1):
			los_targets.append(target)
	los_raycast.enabled = false
	print("Targets in line of sight: ", los_targets, " Sent from: ", self, "\n")
	if !tethered && !los_targets.is_empty():
		tether(los_targets[0])

func instantiate_on_player_vertex(player, previous_target):
	var instance = on_player_vertex.instantiate()
	add_child(instance)
	instance.anchor = player
	instance.previous_target = previous_target
	created_vertices.append(instance)
	instance.connect("tether_created", instantiate_on_player_vertex)
	instance.connect("tether_broken", break_subsequent_vertices)

func break_subsequent_vertices(vertex):
	pass

Hopefully this is formatted correctly, otherwise I’ll edit it.

The add_child is within the instantiate_on_player_vertex function, which is hooked up to the tether_created signal, which I connected through the editor.

Also please feel free to give me any other pointers, this is my first time writing in gdscript and I’d love some pointers or if anything I’m doing here is particularly egregious or incorrect.

I don’t see any position or global position set.

For signals like this it’s better to use the signal object directly, for example

instance.connect("tether_created", instantiate_on_player_vertex)
# turns into
instance.tether_created.connect(instantiate_on_player_vertex)

But this shouldn’t cause you any problems.

Is your timer’s “Process Callback” set to physics?

Sorry, I took out the global_position setting since it was being odd, what I originally had was:

func instantiate_on_player_vertex(player, previous_target):
	var instance = on_player_vertex.instantiate()
	add_child(instance)
    instance.global_position = player.global_position
	instance.anchor = player
	instance.previous_target = previous_target
	created_vertices.append(instance)
	instance.connect("tether_created", instantiate_on_player_vertex)
	instance.connect("tether_broken", break_subsequent_vertices)

I’ll make those changes and look into that for the signals, thank you.

The Timer’s process callback is set to idle, should it be set to physics?

No, it just seems strange that add_child should be deferred, what is the error message you are getting? You can also connect the signal deferred, that would likely behave better as the entire function waits for an idle moment instead of only the add_child function, so positions are updated after add_child.

2024-07-21-180325_664x500_scrot

The error reads:

E 0:00:05:0170 tether_node.gd:98 @ instantiate_on_player_vertex(): Can’t change this state while flushing queries. Use call_deferred() or set_deferred() to change monitoring state instead.

Line 98 is the add_child() line.

ah I see, it can’t add_child since that would trigger a few signals, such as Node’s entering_tree and it’s in the middle of processing signals, i.e. your Timer’s timeout.

If setting the connection to deferred doesn’t work I would try making an anonymous function to defer multiple lines

func instantiate_on_player_vertex(player, previous_target):
	var instance = on_player_vertex.instantiate()
	
	var add_later = func():
		add_child(instance)
		instance.global_position = player.global_position
		instance.anchor = player
		instance.previous_target = previous_target
	add_later.call_deferred()
	
	created_vertices.append(instance)
	instance.connect("tether_created", instantiate_on_player_vertex)
	instance.connect("tether_broken", break_subsequent_vertices)

I see, I’ll have to read more about that to try to understand all of that, pretty new to Godot and signals. I set it to deferred and still got the error so I’ll try out the anonymous function and see how it goes.

Thanks!

First of all you shouldn’t use the default name. I know how you feel. I feel irrational and eroded when I have to call out operation names. But, unfortunately it’s a matter of time until you should be able to see warnings from godot; identifier is declared as a built in function. So you might want to get used to fixing that, if the nodes are important.

Do you mean the default names for nodes? Or default names for functions created through connecting signals?

Thank you for the tip!

You might actually be making a mistake here. Depending on the course of the script here. And, how much of it actually works. You may be just making one mistake with the add_child(instance). Because you’re putting it somewhere in the middle of the script. Maybe try to append it to the end. After all the variables are set.
IG, like this:

Just a thought.

I do like the look of that better, I’ll probably swap over to that, and I’ll see if that changes the error that I was getting before.

It’s not and IDE program. If you ever researched programing at a beginner level. Godot is a very strange hybrid. Because there’s a lot of computer resources available today. It’s actually a VAUX writer program. It’s a virtualizer.