Why does the yield function change the type of a method it's inserted in?

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

A number of people have advocated using the yield function to create a delay. In particular, in his excellent tutorial (1), Ombarus advocates the folowing code:

extends Node2D

func _ready():
method2()

func method2():
print("Called method2")
yield(method3(), "completed")
print("Exiting method2")

func method3():
print("Called method3")
yield(get_tree().create_timer(2.0), "timeout")
print("Exiting method3")

Which produces the following output:

Called method2
Called method3
------ 2 second delay ------
Exiting method3
Exiting method2

(I have changed his method numbers, simply because I want to insert one more method.) Now if we change the code as follows:

extends Node2D

func _ready():
method1()

func method1():
print("Called method1")
var var1: Node2D
var1 = method2()
var var2 = var1.getMyParent()
print("method2 returned ", str(var1))
print("Exiting method1")

func method2():
print("Called method2")
yield(method3(), "completed")
print("Exiting method2")
return $Child

func method3():
print("Called method3")
yield(get_tree().create_timer(2.0), "timeout")
print("Exiting method3")

Here Child is a simple Node2D with a script attached with a single getMyParent() function. Now Godot generates an error:

Trying to assign value of type 'GDScriptFunctionState' to a variable of type 'Node2D

If I comment out the var2 assignment, no error is generated.

In my game I am dealing cards and using using a 0.5 sec delay after each card dealt to make the deal more natural. The yield function works on normal deals, say 5 cards to each player. But when I try to deal until the first jack shows up and then return the player who got it, I run into this error. I have tried a number of workarounds such as moving the yield into one of the called functions, delegating the delay to a delay function, etc. but none of them work.

Can anyone explain what is going on here and suggest a way around it?

:bust_in_silhouette: Reply From: Magso

The problem is var var1: Node2D because you’re declaring the type of the variable but nothing is assigned to it. If the child is called Node2D use get_node("Node2D") or $Node2D and if you’re wanting to create a Node2D use var var1: Node2D.new() and add_child(var1).

Also is getMyParent() a custom function or do you mean get_parent()?

That is not the problem. Rather than posting my original code, I’m creating the simplest possible example (as is recommended). getMyParent() is a function that I built which invokes get_parent(). All I’m doing is returning an object, the Child node, upon which I’m executing a legal call to a user defined function. The function exists only in Child node, not in any of the nodes from which it is derived (such as Node2D). Here is the entire script for the Child node

extends Node2D

func _ready():
pass

func getMyParent():
return get_parent()

The point is that method2 does return a Child and I should be able to execute a legal method call on that return. However, adding the yield statement to method2 has meant that Godot no longer recognizes what is returned as a Child type.

It is particularly perplexing in that methods 1, 2 & 3 are part of a script attached to the root node, so it is surprising the yield statement in the root node’s script would revert the type of a child node

This is very unexpected behaviour. It should have a good explanation unless it is actually a bug in Godot

michaelpbl | 2020-07-08 14:10

I see now, I misunderstood at first. I just did this test which works fine

func _ready() -> void:
	var var1 : Node2D
	var1 = test()
	print(var1)

func test():
	print($child)
	return $child

So the reason it isn’t returning is because method2() doesn’t return until after the yield but var var2 = var1.getMyParent() is requesting it immediately so put a yield function before assigning var2 and it should return normally.

edit: yep just added a yield and got the error you got

func test():
	yield(get_tree(),"idle_frame")
	print($child)
	return $child

rakkarage has posted the right answer but without any explanation.

Magso | 2020-07-08 15:32

:bust_in_silhouette: Reply From: rakkarage
func test0() -> int:
	return 3

func test1() -> int:
	yield(get_tree(), "idle_frame")
	return 4

func _ready() -> void:
	print(test0()) # 3
	print(test1()) # GDScriptFunctionState
	print(yield(test1(), "completed")) # 4

Interesting. I have now modified your example to look more like mine. In particular, I went back to timeout to get the delay and I had test1() return an object instead of an integer.

func test0() -> int:
return 3

func test1():
yield(get_tree().create_timer(2.0), "timeout")
return $Child

func _ready() -> void:
print(test0()) # 3
print(test1()) # GDScriptFunctionState
print(yield(test1(), "completed").test3()) # 4

And modified the $Child script

func test3():
print("test3")
return self

The output now is as follows:

3
[GDScriptFunctionState:1174]
test3
[Node2D:1170]

Which is clearly successful in that it executed a 2 sec delay, returned the Child object as well as executing the user function defined in the Child script.
Can you explain what is happening? The function returning an object is now inside a yield but it’s not clear to me why that works (and why my way doesn’t).

michaelpbl | 2020-07-08 15:41

not really sure but i think when you use yield the function needs to return an intermediary because the function might not be done yet… calling yield(function, “completed”) from outside will wait for the task to finish and return whatever it returns

if you google how to return a value from yield or HTTPRequest.request you will find the same

Implement return value from yield() · Issue #1397 · godotengine/godot · GitHub

rakkarage | 2020-07-08 16:31

I don’t see that that’s apposite. I’m not asking the yield function to return anything.

My method2 is returning an object and it has a yield function in the middle so it will pause until method3 completes. Yet having the yield function in the middle of the method (and it doesn’t matter where - my real function is far longer than method2 and I’ve moved the yield function around) affects method2’s fundamental behaviour.

It’s very presence within method2 is changing what method2 returns.

The more we talk about it, the more I think it’s a bug!

michaelpbl | 2020-07-08 17:54

After studying rakkarage’s example plus my modified version, I went back and modified my original game code which now works beautifully. In essence, I have to make sure the yield(method, “completion”) is called on the method returning the object and put the delay in that method. I’m not entirely sure why it has to be arranged just like that but it is an easy rule to work with

michaelpbl | 2020-07-10 11:53