0 votes

Hello,
I have a while cycle script in _ready() which is very long to be executed, and it is been executed by many nodes all loaded in the same frame.
This means that i wont see any results until the last node finishes its calculation, and then I see all the results all together in a sudden.

Is there a way to tell Godot to stop calculating for a moment and update the scene so that i can see what´s going on during calculation?
I used to put the code in _process(delta) (using the frame of the process as cycles instead of using while) but I feel like this is an inefficient way of doing it, because it will update the scene every frame, instead of doing it only when I care.

asked Jun 11 in Engine by Andrea (148 points)

1 Answer

0 votes
Best answer

If you want to see the scene before, you can delay your calculation by 1 frame by using yield(get_tree(), "idle_frame"). You'll be able to see the scene, but it's still going to hang until all calculations are done.
If that's just what you need, then you don't need to read what I wrote next (but do so if you're curious^^)

Just like any calculation which takes time in any kind of programming language, what you can do is to either optimize the calculations (make sure it's not doing anything silly), spread out the calculations using "time-slicing" or use a thread.
Godot cannot do these magically for you, so you have a bit of setup work to make this happen.

Time-slicing is a technique where you basically queue all tasks and execute them one after the other in a controlled way over time. If the time spent doing tasks gets bigger than some interval (say, 8ms), then you pause the process and wait for the next frame, allowing the game to run. Then you continue processing tasks until none are left.
_ready is controlled by Godot, not you. So this technique requires you to not use _ready for each task, and instead fetch all nodes you want to initialize in a single function which will do something like:

# initializer.gd

# Call this when you want stuff to initialize all nodes
func _initialize_stuff():

    # You can put nodes to initialize in a group to easily get them all
    var nodes_to_init = get_tree().get_nodes_in_group("heavy_initialize")

    var time_before = OS.get_ticks_msec()
    var max_interval = 8

    # Go through all of them and initialize
    for node in nodes_to_init:
        # You would define an `initialize` function on those nodes
        node.initialize()

        var now = OS.get_ticks_msec()
        if now - time_before > max_interval:
            # Took too long to process, suspend loading and continue next frame
            yield(get_tree(), "idle_frame")
            time_before = OS.get_ticks_msec()

    print("Done!")
    # If needed you can emit a signal or use `call_group`
    # to notify things finished to load

I used yield here but other solutions exist using _process and some member variables.

Note that you could also instance your nodes one by one if they are procedurally generated for example.

Another way is to use threads. It can be a little bit trickier, because you can't access the scene tree from within a thread (it's not "thread-safe") so be sure your heavy processing is just dealing with data and resources. See http://docs.godotengine.org/en/3.0/tutorials/io/background_loading.html

answered Jun 11 by Zylann (14,838 points)
selected Jun 12 by Andrea

I think yield is what I am looking for, cause my code is not that different from your example (except you used the 8ms timing to activate the yield, while i want to activate it when one calculation is finished), but it doesnt seem to work.
I suppose placing the yield doesn't magically help, cause I placed it at the end of the long calculation, immediately after printing the result of one node, but Godot still waits for all of the nodes to finish calculation and then print them all together.
The code is something like this, in the Main node:

func _ready():
      for child in get_children():
          child.start_calculation()

in the child nodes

func start_calculation():
     while x<y:
        #hard calculation here
     print(result)
     yield(get_tree(), "idle_frame")

Godot is not "waiting", it's your code that takes up all the CPU, so the point here is to give some breath to Godot. Delaying to the next frame is one way to accomplish that, but you should not yield in each node's calculation. You should yield in the main for loop which actually runs them, just like I did.

If you yield in the nodes, then your code will basically run them all, which will hang, and then delay the end of the execution of all functions to the next frame, which is useless because 1) they will resume all at once at the next frame, and 2) the functions do nothing at the end anyways

Ok, I think I got it!
Long story short, when activated, the yield doesn´t stop ALL of the code, and doesnt even stop all of the code inside a script, but it only stops and delay the function in which it is called.

Thank you very much, now that I understood the mechanic I can play around with it!

Welcome to Godot Engine Q&A, where you can ask questions and receive answers from other members of the community.

Please make sure to read How to use this Q&A? before posting your first questions.