Problem with screen display versus code execution

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

I’m developing an image processing application where, one of the routines takes more than 5 seconds to process. To make this process more professional, I want to display a window, showing a “please wait” message while that routine takes it’s time.

The problem is that, although the code executes sequentially, like it should, the popups only appear after the 5 second-routine runs, and not before like they should.

The logic is:

  • show_accept_dialog(saying image file loaded ok, requires pressing the
    ok button)
  • call the image processing method
  • show_message_window(show popup panel, no input required)
  • process_image()
  • show_message_window(hide popup panel)

This image processing method translates to the following code (heavily simplified):

func carregar_imagem(_foto):
	
	Foto = _foto
	
    # This calls the method to show the "please wait" popup panel
	apresentar_mensagem_espera(true)

	if Foto.path_ficheiro.length() > 0:

		var pal = palette_cores.new()
           # The next method takes between 5 to 6 seconds to run
		var cores = pal.calcular(Foto.path_ficheiro)


	# Hides the "please wait" popup panel
	apresentar_mensagem_espera(false)

With this code, the popup never appears, although it is executed in the right sequence, because Godot only updates the GUI after the routine completes.
I’ve tried several yields (idle_frame, physics_frame, completed, custom signals, splitting the method in two, having a signal call the second part) between apresentar_mensagem_espera(true) and pal.calcular but nothing makes Godot show the popup before running the pal.calcular method.

Does anyone knows a way to make it stop, update the GUI and then process the rest? Having the program “freeze” for 5 seconds with no feedback is bad.

:bust_in_silhouette: Reply From: whiteshampoo

You can/should do your image processing in a thread.
Then your program will not freeze.

Thank you. I’ll try that. But the program doesn’t freeze, only the GUI updates. It looks like the engine only updates the GUI after all the code is executed and the stack frames get to zero.

cgeadas | 2020-07-07 20:51

I’ve tried and made absolutely no diference. I’ve managed to make it work as intended by inserting this hack before calling my intensive code:

for x in range(2):
	yield(get_tree(), "idle_frame")

I don’t know why 2 times but with one doesn’t work. I had tried before only the yield out a for loop (I had even tried two lines with the same yield!) and nothing. Like this works - but is one of those things that I would really like to understand because I hate “magical” code that “just works” but there is no explanation why.
Anyway, thank you again whiteshampoo - what you recommended is sound advice and I will study it further.

cgeadas | 2020-07-07 21:40

If nothing works, you can also upload your projects. Then i’ll have a look and see what i can do for you :wink:

whiteshampoo | 2020-07-08 05:54

Thank you. With that hack is working.
Is there any documentation regarding execution order in Godot? Something like: code gets executed first (to the end of the stack frame?), then signals, then group calls, then deferred calls… Knowing the exact order would be useful.

cgeadas | 2020-07-08 14:01

There’s an issue about documenting the execution order, but I don’t know exactly how it works either.

Waiting for 2 frames probably solves the issue because it takes one frame for the dialog to appear. Therefore, if you want the dialog to be visible while it freezes, you need to wait another frame.

Calinou | 2020-07-09 07:57

So many things that should have been properly documented a long time ago…

cgeadas | 2020-07-09 15:03

Maybe i’ll have a look after the next gamejam, that starts tomorrow…
But i have never contributed to the docs, so no promise!

whiteshampoo | 2020-07-09 17:03

After some code rewriting I managed to have threads working. I had not realised that threading is a 3 part process, where (in a schematic way), main() starts the thread, my_thread does the processing and pos_process closes the processing and syncs back with the program execution:

var thr = Thread.new()
func main():

   thr.start(self, "my_thread", 0)

func my_thread(x):
   var result = [do the work]
   call_deferred("pos_process")
   return(result)

func pos_process():
   var result = thr.wait_to_finish()
   [do stuff with the result]

I started by placing the wait_to_finish in main(), blocking my main processing. So wait_to_finish must be called, and placed where the post-thread processing takes place. Now I know :slight_smile:
(I just don’t know, and it doesn’t seem possible, how to kill a thread that, during processing, I no longer need)

cgeadas | 2020-07-11 17:42