#How Do I Await For a Thread Inside a Signal-Called Function in Godot 4.1?

1 messages · Page 1 of 1 (latest)

daring mist
#

Hello! I have a function called load_ingame_stage_scenes that takes a resource called stage_info and loads the main scene of said stage on a different thread (called load_thread). However, I cannot find a solution that awaits for the load_thread to finish the threaded_transition_load_ingame_stage_root_scene function. Since this load_ingame_stage_scenes function is called from a Signal, I cannot check if the Thread.is_alive() is true without blocking the main thread. I tried using a Signal but it cannot be called in a different thread as it is not thread-safe and does not have call_deferred() recommended by the error message.

Is there a way to await/pause for the threaded_transition_load_ingame_stage_root_scene function to finish in the load_thread thread on the main thread here without blocking the main thread?

Thanks in advance. Please let me know if you'd like me to provide any more information.

#
# GDScript in GODOT 4.1.3stable

# CALLED FROM A SIGNAL FUNCTION, NOT _PROCESS
func load_ingame_stage_scenes(stage_info_resource: stage_info) -> void:
    load_thread.start(threaded_transition_load_ingame_stage_root_scene.bind(stage_info_resource.stage_root_scene_path))
    
    var stage_root_scene: PackedScene = load_thread.wait_to_finish() # wait_to_finish() JOINS THE THREAD, CAUSING THE PAUSE.

func threaded_transition_load_ingame_stage_root_scene(stage_root_scene_path_value: String) -> PackedScene:
    ResourceLoader.load_threaded_request(stage_root_scene_path_value)
    
    var load_status = ResourceLoader.load_threaded_get_status(stage_root_scene_path_value)
    
    while load_status != ResourceLoader.THREAD_LOAD_LOADED:
        load_status = ResourceLoader.load_threaded_get_status(stage_root_scene_path_value)

    var i = 0
    var j = 10000

    while i != 10000000: # LONG WHILE LOOP TO SIMULATE LOADING TIME
        i += 1
        
        if i % j == 0: print(i)
    
    return ResourceLoader.load_threaded_get(stage_root_scene_path_value)
fallow moth
#

You don't want to await threads. That defeats the purpose of using them. Threads are meant to be set and forget type deals where the thread should notify the main thread when it has completed.

fallow moth
#

here is an example of how multithreading is usually done

#

this was made in Godot 4.2.1

daring mist
# fallow moth here is an example of how multithreading is usually done

Ohhh I think I get it now. So from your example, that's how you use call_deferred() which is to call a method on another thread when the other thread is done with a task...?

In hindsight, I can see why my method doesn't work at all as wait_to_finish() literally pauses the thread as it joins the threads together. I was also trying to use a signal within the threaded function but you can't do that with Signals in threaded functions and I assume that's because they aren't thread-safe. It's probably easier to use call_deferred() anyway.

fallow moth
# daring mist Ohhh I think I get it now. So from your example, that's how you use ``call_defer...

call_deferred() calls the function on the main thread.

So, the way it works is as follows:

  1. you spawn a thread
  2. do the work in the thread
  3. once the thread is done, call a function on the Main thread using call_deferred(). In this Main-threaded function is where you will call wait_to_finish(), which should return immediately since your thread should be finished at this point
  4. From there do whatever you need to do with data that was processed in the thread
#

if you need to know when a batch of threads has completed, you can do something like the following:

extends Node

var threads_finished = 0
var threads = []
var mutex = Mutex.new()

func _ready():
    for i in 100:
        threads.push_back(Thread.new())
        threads[-1].start(thread_func.bind(randf_range(1.0, 5.0)))
        
func thread_func(workload): 
    await get_tree().create_timer(workload).timeout
    mutex.lock()
    threads_finished += 1
    print("Thread ", threads_finished, " done")
    if threads_finished == threads.size():
        call_deferred("done")
    mutex.unlock()

func done():
    print("All finished, destroying thread objects")
    for t in threads:
        t.wait_to_finish()
    threads.clear()
    print("Using thread generated data")
daring mist
#

Ahhh interesting! Thank you for the help!

#

Also I am familiar with the concept of mutexes on a basic level so no need to explain them.

daring mist
# fallow moth `call_deferred()` calls the function on the main thread. So, the way it works ...

Gotcha 👍

Also, I assume you should you use call_deferred() right before you return data with a return statement if you are returning data from the thread.

For example:

func threaded_transition_load_ingame_stage_root_scene(stage_root_scene_path_value: String) -> PackedScene:
    ResourceLoader.load_threaded_request(stage_root_scene_path_value)
    
    var load_status = ResourceLoader.load_threaded_get_status(stage_root_scene_path_value)
    
    while load_status != ResourceLoader.THREAD_LOAD_LOADED:
        load_status = ResourceLoader.load_threaded_get_status(stage_root_scene_path_value)
    
    var i = 0
    var j = 10000
    
    while i != 10000000:
        i += 1
        
        if i % j == 0: print(i)
    
    call_deferred("finished") # HERE
    return ResourceLoader.load_threaded_get(stage_root_scene_path_value)
fallow moth
#

I wouldnt return data from a thread. I would just set the value of a global variable in the thread and then use the data once I am out of the thread

daring mist
#

Ah okay 👍 I was actually doing that before then tried to do it with only passed local variables

#

Thanks again Lotus! I really appreciate your assistance.

fallow moth
#

I'm sure there is a way to return data from a thread but I haven't really worked with it that way so I couldn't really tell you best practices

#

You could also try passing the value to the function called in the main thread (the function called using the call_deferred() method) instead of returning a value. This way you wouldn't have to have a global variable.

daring mist
#

Perhaps I should try that as well.

fallow moth
#

I was messing around with this code again today

#

here is how you would return stuff from a thread

#
extends Node

var thread:Thread = Thread.new()

@onready var sprite_2d:Sprite2D= $"../Sprite2D" as Sprite2D
@onready var mona_lisa:Sprite2D = $"../mona_lisa"  as Sprite2D

func _ready():
    thread.start(thread_func)
        
func thread_func(): 
    var mona_lisa_texture = load("res://mona.png")
    var i = 0
    var j = 10000
    
    while i != 30000000: # LONG WHILE LOOP TO SIMULATE LOADING TIME
        i += 1
        if i % j == 0: 
            print(i)
    call_deferred("done")
    return mona_lisa_texture
    
func _process(delta):
    sprite_2d.position.x += 2
    

func done():
    print("All finished, destroying thread objects")
    mona_lisa.texture = thread.wait_to_finish()
#

the wait_to_finish() function will return whatever you returned from your threaded function

#

also, using regular load inside a theaded function alleviates you from having to use the ResourceLoader singleton, checking the status of the loading process, and then manually retrieving the resource once it has finished loading.

#

instead, you just call load and let it do its thing

daring mist
#

Ohh. That's a nicer and simpler approach. However, how are we able to tell if the texture or scene is loaded before returning it from the other thread? For example: with the ResourceLoader, you can use a while statement on a different thread to make sure its loaded before closing the thread and I do not see an equivalent unless the load method itself stops code execution until it's loaded.

fallow moth
#

I believe the load execution will block the thread until it's finished loading, the same way it would block the main thread, but don't quote me on that. Give it a try and let me know.

fallow moth
#

One more thing to keep in mind is that, if you need to know the exact percentage of progress that has been made on the load (to create a loading bar for example), then the only way to do that is to use a ResourceLoader

daring mist
#

ah gotcha