Mixing normal and signalled yields

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

I have the following code:

onready var sequence: GDScriptFunctionState = stepper()

func stepper():
  ...
  yield()

  viewport.gui_disable_input = true
  yield(blanc1.show_activate(), "completed")
  viewport.gui_disable_input = false

  yield()
  ...

func _on_next_pressed():
  if sequence is GDScriptFunctionState && sequence.is_valid():
    sequence = sequence.resume();

Now, in theory, what should happen is that pressing next button should run the animation and then go back into a resumable state. However, what happens in practice is that the next button stops working and the next resume returns null, breaking the chain.

My theory is that the signal yield is returning one GDScriptFunctionState, but by the time next is called that state is no longer valid an therefore the resume fails. and therefore, _on_next_pressed would need to somehow get the state returned on the next yield call, which it can’t.

Regardless of whether my theory is correct or not, how can I advance my stepper coroutine while allowing it to wait on the animation call?

:bust_in_silhouette: Reply From: timothybrentwood

Bundle your individual steps into functions and create a dictionary that maps which function to call at which step then your _on_next_press() just advances which step is currently on.

var steps = {1 : "function_one", 2 : "function_two"}
var current_step = 1
onready var MAX_STEP = steps.keys().max()

func function_one():
	print("one")

func function_two():
	print("two")
	
func _on_next_pressed() -> void:
	if current_step <= MAX_STEP:
		call(steps.get(current_step))
		current_step += 1

You could also take a more object oriented approach and make a Step object with an execute() function then create all your steps in separate objects that inherit from that Step object and call the execute() function in place of the call() in my code above.

That doesn’t really answer my question about coroutines, it instead presents an alternative algorithm.

For one, variables that are created during one of my steps are used in later steps.

For two, this seems like a very clumsy and inefficient way for achieving the same purpose as you have the additional costs of a hash lookup and string lookup (the call function)

jaguar1983 | 2022-12-14 05:28

Seems like you’re smart enough to figure out why your algorithm doesn’t work. Cheers.

timothybrentwood | 2022-12-18 01:18

Yeah, I ended up digging into the source code and figuring it out. I’m going to post what I found out and how I got around it.

jaguar1983 | 2022-12-18 06:26

:bust_in_silhouette: Reply From: jaguar1983

So, I ended up digging into the source code and discovered that my theory is correct:

My theory is that the signal yield is returning one GDScriptFunctionState, but by the time next is called that state is no longer valid an therefore the resume fails. and therefore, _on_next_pressed would need to somehow get the state returned on the next yield call, which it can’t.

This means that you can’t mix yield() and yield(object, signal) in a single coroutine. In fact, in 4.0yield() is being removed and yield(object, signal) is being replaced with await, so that will be the only way to suspend a coroutine going forward.

I ended up removing all of the yield() calls and replacing them with yield(next_button, "pressed"):

onready var sequence: GDScriptFunctionState = stepper()

func stepper():
  ...
  var next_button:Button = $HUD/Next
  ...
  yield(next_button, "pressed")

  viewport.gui_disable_input = true
  yield(blanc1.show_activate(), "completed")
  viewport.gui_disable_input = false

  yield(next_button, "pressed")
  ...