Functions not continuing right when trying to do nested pauses with yield with signals

:information_source: Attention Topic was automatically imported from the old Question2Answer platform.
:bust_in_silhouette: Asked By Aaron
:warning: Old Version Published before Godot 3 was released.

I have some code that I try to pause using yield and signals, but it’s not pausing and continuing right.

I have this node structure:

node (node.gd)
    event 1 (event.gd)
        event 2 (event.gd)
        event 3 (event.gd)

The “event” nodes have an execute function, where an event does an operation and then recursively calls execute on its children. An event’s operation or the operations of its child events may involve animations and other long actions, so I want to pause when running a child before moving on to the next child. I’ve been trying to do with with yield and signals emitted by an event node.

event.gd

extends Node

signal event_complete

func execute():
    print(get_name(), ": starting self event")
    _do_event()
    print(get_name(), ": self event finished")

    if get_child_count() > 0:
        print(get_name(), ": doing child events")

    for i in range(get_child_count()):
        var c = get_child(i)
        assert(c extends get_script())

        print("firing event: ", c.get_name())
        c.execute()
        yield(c, "event_complete")
        print("finished firing event: ", c.get_name())

    if get_child_count() > 0:
        print(get_name(), ": child events finished")

    self.emit_signal("event_complete")

func _do_event():
    # Test code. Could be anything.
    print(get_name(), ": hello world")

node.gd

onready var event_1 = get_node("event 1")

...

func fire_event_1():
    print("node firing event: ", event_1.get_name())
    event_1.execute()
    yield(event_1, "event_complete")
    print("node finished firing event: ", event_1.get_name())

So during execute function an event does its operation (_do_event) then calls execute for each of its children, pausing until the current child emits event_complete, then emits event_complete itself. The root node likewise calls the first event’s execute function then waits until the first event is finished.

None of the yields are working right.

Somehow in the node script it never gets to the “node finished firing event” print statement the first few times. And when I call the node’s function fire_event_1 the first time it never calls execute on “event 3”, but it does when I call fire_event_1 a second time.

This is what’s printed when fire_event_1 is called for the first time:

node firing event: event 1
event 1: starting self event
event 1: hello world
event 1: self event finished
event 1: doing child events
firing event: event 2
event 2: starting self event
event 2: hello world
event 2: self event finished

What’s printed when fire_event_1 is called a second time:

node firing event: event 1
event 1: starting self event
event 1: hello world
event 1: self event finished
event 1: doing child events
firing event: event 2
event 2: starting self event
event 2: hello world
event 2: self event finished
finished firing event: event 2
firing event: event 3
event 3: starting self event
event 3: hello world
event 3: self event finished

And what’s printed when fire_event_1 is called a third time:

node firing event: event 1
event 1: starting self event
event 1: hello world
event 1: self event finished
event 1: doing child events
firing event: event 2
event 2: starting self event
event 2: hello world
event 2: self event finished
finished firing event: event 2
firing event: event 3
event 3: starting self event
event 3: hello world
event 3: self event finished
finished firing event: event 3
event 1: child events finished
node finished firing event: event 1
node finished firing event: event 1

What is the proper way to pause and resume my code during an event node’s execute function? Why do are my yield statements not resuming when I thought they’d resume given their signal parameters?

:bust_in_silhouette: Reply From: ericdl

Not entirely sure about this, but I believe the execute functions were firing their signals before the yield statements could detect them. The solution was to use call_deferred to call the execute functions.

Anyway, this is tested and working:

event.gd

extends Node


signal event_complete


func execute():
	print(get_name(), " saying hello")
	_do_event()
	print(get_name(), " finished saying hello")

	if get_child_count() > 0:
		print(get_name(), " starting ", str(get_child_count()), " child events:")
		for i in range(get_child_count()):
			var c = get_child(i)
			assert(c extends get_script())
			
			print(get_name(), " firing ", c.get_name())
			c.call_deferred("execute")
			yield(c, "event_complete")
			print("yield returned from ", c.get_name())
		self.emit_signal("event_complete")

	else:
		print(get_name(), " has no children")
		self.emit_signal("event_complete")


func _do_event():
    # Test code. Could be anything.
    print(get_name(), " says hello world!")

node.gd

extends Node


var event_1 = null


func _ready():
	event_1 = get_node("event 1")
	fire_event_1()


func fire_event_1():
	print("node starting ", event_1.get_name())
	event_1.call_deferred("execute")
	yield(event_1, "event_complete")
	print("node finished ", event_1.get_name(), ", all finished.")

Replacing my execute calls with call_deferred("execute") calls fixed this. I don’t know enough about yield to tell why that’s the case, but there you go.

I still have some bugs to work out because in my particular case fire_event_1 is actually called inside another function.

node.gd

func _input(event):
    if ...:
        foo()

func foo():
    fire_event_1()
    bar()

func fire_event_1():
    ...

func bar():
    ...

The issue I’m dealing with now is bar getting called before fire_event_1 finishes after its yield.

Through all this I’ve been working under the impression that yield could be used with signals to introduce pauses in code. Maybe this impression is inaccurate and I should structure my code differently?

Aaron | 2016-09-04 14:22

You might try putting a yield before bar in the same way as the other scripts.

As for pausing code with yield statements, do you want it to wait for a set amount of time, or wait for user input, etc. ?

ericdl | 2016-09-04 14:52

I’m not sure what you mean by putting a yield before bar. In fire_event_1 there’s one yield call, which is meant to wait until “event 1” emits its event_complete signal (that’s how signals work with yields, right?). Are you saying to add a second yield outside of fire_event_1, between it and bar?

As for why I’m pausing in the first place, the event nodes are meant to be a general superclass of game actions that I can chain together. An event could happen over multiple frames, so I’d want to wait until an event is finished before either moving on to the next event or moving on in the outer code that activated the root event. An event could be an animation, so I’d want to wait until the animation is finished. Another event could spawn enemies, so I’d want to wait until all the new enemies are created and positioned. Yet another event could be a pop-up text box, so I’d want to wait until the player closes the text box.

Aaron | 2016-09-04 16:05

Yes, try adding a yield between fire_event_1 and bar(), but make sure to use call_deferred("fire_event_1") to give the yield statement time to catch the signal. I can’t say for certain but i get the feeling all of your yield statements were missing their signals because the signals were firing before the yield statements were even executing.

ericdl | 2016-09-04 18:48