Is yield still waiting for me?

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

This function display a tooltip when hovering over items after a short delay.

func tooltip_switch(slot, mouse_enter):

  if mouse_enter && slot.has_node("Item"):
	  timer.start()
	  yield(timer,"timeout")
	
	  follow = true
	  open_tooltip(slot)

  elif !mouse_enter:
	  timer.stop()
	
	  follow = false
	  close_tooltip()

The timer is a one shot that starts when you hover over an item but stops when you leave the item’s slot.

So there’s times when the timer starts and the function yields but the mouse leaves the slot so the timeout signal is never emitted I was wondering what happens with this yield.
Do these yields overlap or overwrite? For how long can the yield stay in a resumable state? Does it get freed on its own or do I have to do it manually?

I don’t think it’s really possible to answer that without knowing how the tooltip_switch function is used. Mind sharing more of your script?

I’m not sure, but here’s what I think may happen:

  • you hover an item, tooltip_switch is called, starts the timer and yields waiting for the timeout
  • you move the mouse away before the timer ends, tooltip_switch is called again, it stops the timer, preventing the timeout signal from being sent.
  • you hover over another item, tooltip_switch is called, restarts the timer, and yield
  • at this point, I think there may be 2 functions in a yielded state waiting for the same timer
  • the timeout signal is sent. The yielded function from the first item is resumed, and open_tooltip(slot) is called with item 1
  • The yielded function from the second item resumes immediately after item 1 (in the same frame), and open_tooltip(slot) is called with item 2

I’m not sure this is the case though. I’d suggest trying these steps while putting a breakpoint on open_tooltip(slot), or adding a print statement that tells you the item’s name, and then see if the timer triggers multiple function calls.

Bernard Cloutier | 2020-10-01 13:34

Thanks for the reply.
This is exactly what is happening. I didn’t realise because calling the same function again didn’t change anything visually but once I implemented a second type of tooltip they both displayed if I hovered over a slot and moved to another with other type of tooltip.

the logic goes like this:

  • on enter or exiting node, emit signal with arg: [node, true/false]
    tooltip_switch is connected to this signal.

  • tooltip_switch starts a timer (pop up delay) and calls open_tooltip
    with arg: [node] or close_tooltip if arg2 = false

  • open_tooltip checks the "Tooltip" meta and displays the corresponding
    tooltip.

I tried to follow this post to return the function if the mouse left the slot but I didn’t manage to save the function state because of the arguments of the function that are passed via signal. I still don’t understand very well how the return value works when arguments are involved since the only way I know to return a value from a function is to save the function as a variable but the arguments may not be in scope and saving the variable inside the function will cause an infinite loop.

https://forum.godotengine.org/35584/how-to-break-out-of-a-function-that-has-an-in-progress-yield

I tried to ditch the yield and use only signals. I start the timer but I cant make the signal timeout pass any arguments. I came up with 2 options:

Store the arguments and make a short function like which seems unnecessary:

var arg
func tooltip_switch(slot, mouse):
if mouse:
  arg = slot  
  timer.start()

else:
    close_tooltip()

func timeout():
   open_tooltip(arg)

func open_tooltip(slot):

   var tooltip = slot.get_meta("Tooltip")

   match tooltip:
       "Details":
             (display details tooltip)
       "Progress":
             (display progress tooltip)

Or connect and disconnect the timeout signal just to update the argument which seems unnecessary.

func tooltip_switch(slot, mouse):
if mouse:
  if timer.is_connected("timeout",self,"open_tooltip")
    timer.disconnect("timeout",self,"open_tooltip")

  timer.connect("timeout",self,"open_tooltip",[slot])
  timer.start()

else:
    close_tooltip()
    
func open_tooltip(slot):

   var tooltip = slot.get_meta("Tooltip")

   match tooltip:
       "Details":
             (display details tooltip)
       "Progress":
             (display progress tooltip)

What would be the best way to do this? And do you think there’s a way to break the yield if I wanted to use that instead of signals? I can provide the code if needed.

IHate | 2020-10-01 21:09

Personally I find the first option more readable. I prefer leaving the connect/disconnect logic in the node setup, as it gets hard to follow whether the function is currently connected to a signal or not.

First option is clearer. Also I don’t think you can prevent a yielded function to resume without doing something wacky and messy. You can’t cancel it.

Bernard Cloutier | 2020-10-02 01:15

That’s what I’m using on right now but it feels unrefined. I wished I could set an argument when I start the timer so the signal sends that argument to the connected function. Or maybe I just have to think of a better structure for all this logic.

IHate | 2020-10-02 03:22