Why does 'yield on signal' work differently in functions?

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

This came up on this question.

Yield works differently inside a function other than _ready().

I have a new project with one scene which has two nodes: A node 2D with button name TheButton. Here is the attached script:

extends Node2D

func do_yield():
      yield($TheButton, "pressed")
      print("4, on the second press")

func _ready():
    print("1")
    yield($TheButton, "pressed")
    print("2, after pressing")
    do_yield()
    print("3, immediately after 2")

When running this scene:

  1. The _ready() function starts and prints “1” as expected.
  2. The yield pauses the thread until the button is pressed. It prints “2”.
  3. do_yield() is called. The yield does not pause, but waits on a new thread and immediately returns. “3” is printed.
  4. When the button is pressed again, the paused thread from do_yield() fulfills waiting on the signal and “4” is printed.

Can anyone explain why the yield statement works differently in different functions?

:bust_in_silhouette: Reply From: Inces

It works as intended. Yield() is only supposed to pause one thread, isn’t it ?
You can’t expect corroutine do_yield()to pause ready, because yieldline is only met inside this corroutine. So print(3)is executed 1 frame after do_yield line, because there is no reason for it to wait until corroutine is completed. At the same time, do_yield()is paused itself and waiting for button press.

corroutines don’t have to return, calling functions continue witohout it

Thank you for your answer. I want to clarify what you said, so this comment got long:

  • _ready() and do_yield() are each coroutines because each contains a yield statement. When called, the current thread is suspended and execution of the function starts on a newly created thread.
  • On executing any form of a yield statement, the current thread is suspended and execution continues with the calling thread.
  • When the thread running do_yield() is suspended and _ready() continues, the statement print("3, immediately after 2") is executed, though the display may not update until the beginning of the next frame.

This makes the sequence:

  1. The system thread (S1) calls ready(). This suspends S1, creates a new thread (call it T1), and starts T1 executing ready(). It prints 1.
  2. T1 executes yield($TheButton, "pressed"). T1 suspends and S1 wakes and continues.
  3. $TheButton is eventually pressed, and the pressed signal wakes T1. It prints 2.
  4. T1 executes do_yield(). Because do_yield() is a coroutine, T1 suspends and a new thread (call it T2) starts executing.
  5. T2 executes the yield($TheButton, "pressed") in do_yield(). T2 suspends, T1 wakes and prints 3. T1 completes its function body and is destroyed.
  6. $TheButton is eventually pressed, and the pressed signal wakes T2. It prints 4, finishes its body, and is destroyed.

To make debugging more fun:

  • Can Godot show which signals are firing? Outside of connection to each one?
  • Can Godot use syntax highlighting to differentiate ‘normal’ calls from coroutine calls that spawn a thread?
  • Can Godot list what threads are active and where in the source they are executing?

And finally, am I correct that emit and connect do not touch the threads directly, only yield?

CharlesMerriam | 2022-06-02 04:53

Yeah, I actually used wrong wording I guess - I meant subroutine :slight_smile:
This naming difference may be exactly what You asked for. As I believe do yield() would become true coroutine if this one line went :

var x = do_yield()

This way ready() would actually have to wait until yield line of do yield() until x would become yieldedfunctionstate. You can also go :

yield(self,"do_yield","completed") 

to force ready() to wait until do_yield() gets to its return line
To be honest I don’t understand swapping yielded function states inbetween corroutines, these are some advanced options for me
I tried experimenting with it earlier but it beat me :slight_smile:
Maybe You can get to something ? :slight_smile:

PS I don’t understand what You meant about signals and connect relative to threads. I fail to see simarity between those methods.

Inces | 2022-06-02 18:03

:bust_in_silhouette: Reply From: SnapCracklins

For the record, I would not use a yield here. I would just make a variable the button holds (or global if some other scene needs it) and just run a match on button press to check the variable amount and run the event you want on each step.

Example:

  func on_button_pressed():
            Var _gain = myButtonVariable+1
            myButtonVariable = _gain
            match myButtonVariable:
                  1:
                       ### this event here
                  2:
                       ### this event here
                  3:

You get the idea. Yield can cause problems sometimes especially if you are waiting for a solid state or work a bit too fast. Usually flags or value comparisons will do what you want better with less risk of breaking something.

Thank you. Asking on the discord channel, I find that people are generally considering yield to unreliable and that it will be leaving in Godot 4. Good to know.

CharlesMerriam | 2022-06-03 02:42