Godot Version
4.5.1stable
Question
This is perhaps me not understanding await or a bug, related to Add `become_invalid` signal to Tween · Issue #13296 · godotengine/godot-proposals · GitHub
In the original issue, someone mentioned that hanging on tween getting killed is not a problem, but that’s not always the case.
If a function B awaits another function A which await tween.finished but tween was killed, B will hang.
normal case A.
normal case B, tween killed but fine, _suspended_1() gets cleared away.
problem. _suspended_2() hangs and keeps piling up in memory.
I’m now not sure if this is expected behavior or a bug.
extends Node2D
var time :=0.0
var idx:=0
var tween:Tween
func _process(delta: float) -> void:
_suspended_2()
func _suspended_2()->void:
await _suspended_1()
func _suspended_1()->void:
if tween:
tween.kill()
tween = create_tween()
tween.tween_property(self,"time",time+1,1.0)
await tween.finished
print("finished tween %d"%idx)
idx+=1
Connect to the signal instead, this won’t create a coroutine and will the connection will be cleaned as the signal is killed.
Could you avoid creating coroutines and tween in _process? That can cause many issues on it’s own.
it’s just for showcasing, The behavior holds no matter where you create it.
What is “hang”?
You create a fresh tween and a sleeping coroutine every frame. Piling up is expected.
It’s suppposed to pile up to a few hundred objects as shown in normal case A then get cleared away. In problem case, the coroutines created stays forever.
A sleeping coroutine stays forever if no one wakes it up. That’s how it’s supposed to work.
That’s what I thought initially, but quote ydeltastar from the linked issue,
me:However, if you have await tw.finished somewhere in your code, that coroutine will be dangling forever if tw is killed.
they:You don't need to worry about this. await tween.finished is basically implemented as tween.finished.connect(function_state.resume, CONNECT_ONE_SHOT). When you assign a new tween, the previous is freed, so all signal connections are cleared, which also frees all pending callbacks and function states (coroutines).
Which I tested out to be true in case B. So what is the problem here? why is the coroutines still piling up in case C?
The coroutine _suspended_2() awaits for coroutine _suspended_1() to finish, which will never happen. When the tween is killed the function state object for coroutine _suspended_1() may be deleted but the coroutine will still never continue because the signal won’t be emitted. So _suspended_1() will indefinitely be stopped at await. Since it never finishes it will keep the _suspended_2() state object allocated forever.
1 Like
I see, thanks for the explanation, I was assuming freed coroutines will unblock other coroutines in a chained fashion.