0 votes

I have a splash screen during which I load my levels in a separate thread.

Basically, during my splash animation, I have a separate thread which runs through a queue of level paths and loads them using an InteractiveLoader.

var queue = ["res://levels/level1.tscn", "res://levels/level2.tscn", "res://levels/level3.tscn"]

The code starts with queue[0], calls loader.poll() until the result is ERR_FILE_EOF, at which point it collects the resource into a dictionary, and then moves on to the next element of the queue. When the queue is exhausted, we close the thread and then move from the splash screen to the main menu.

This was working like a charm for a while. But just yesterday I created a new level and noticed that the process no longer finishes.

After much troubleshooting, I think I've determined that it has something to do with size. If the queue is too large, the process simply stops at a certain point. I checked this by creating copies of some very simple levels. I just copied them again and again until the process failed. I also confirmed it doesn't have to do with a specific level, because I tried this with multiple different simple levels.

For example, here is some debugging output showing that when my queue index is changed to 83, the process just hangs up, rather than moving on to finish the entire queue, which has a size of 91.

enter image description here

I also trimmed my levels down to just 83 and confirmed by removing nodes of this level that a single node could be the difference between the process completing and not completing. So it does seem like the issue has something to do with size...

Does anyone have any idea why my process might hang up like this after loading a certain number of levels?

I am posting my code below to provide some additional context. For debugging purposes, rather than background load during the animation, I simply start the thread after, and wait for it to finish before opening the main menu.

extends CanvasLayer

# background loading
var loader
var queue
var index
var target_level_path
var loading_thread
var is_loading = false
signal background_loading_complete

func _ready():
    # disable gui input -- NOTE: this prevents opening pause during splash screen
    get_tree().get_root().gui_disable_input = true


func _on_AnimationPlayer_animation_finished(anim_name):
    # start loading thread
    loading_thread = Thread.new()
    loading_thread.start(self, "load_data", "nominal_argument")

func load_data(nominal_argument):
    initialize_background_loading()

func initialize_background_loading():

    # track is_loading
    is_loading = true

    # set initial queue
    queue = []

    # fill queue -- NOTE: queue is just a list of level paths to be loaded
    var level_paths = Utility.get_files_in_directory("res://levels")
    for level_path in level_paths:
        queue.append("res://levels/" + level_path)

    # set initial index
    index = 0

    # start background loading
    background_load(index)


func background_load(i):
    print("INDEX: ", index, " QUEUE SIZE: ", queue.size())
    # if done loading, stop
    if i == queue.size():
        # track is_loading
        is_loading = false
        # PRINT - TEST - DEBUG
        print("Done background loading.")
        # emit signal
        emit_signal("background_loading_complete")
        # close thread
        loading_thread.wait_to_finish()

    # otherwise, load next
    else:
        target_level_path = queue[i]
        loader = ResourceLoader.load_interactive(target_level_path)
        load_step()

func load_step():
    # advance the load
    var status = loader.poll()
    print("TARGET LEVEL: ", target_level_path, " POLL STATUS: ", status)

    # check status 
    match status:
        # if done, get resource and attempt to start loading the the next in queue
        ERR_FILE_EOF:
            # get resource
            Globals.preloaded_level_dict[target_level_path] = loader.get_resource()
            # load next in queue
            index += 1
            print("CHANGED QUEUE INDEX TO ", index)
            background_load(index)
        #  otherwise, just advance the load
        OK:
            load_step()
        _:
            print(status)

func _on_SplashScreen_background_loading_complete():
    # open main menu
    Transition.transition([], funcref(Menus.main_menu, "open"), null, true, true, true, true, "fade", "fade")
    queue_free()
asked Aug 12, 2019 in Engine by Diet Estus (1,480 points)
edited Aug 12, 2019 by Diet Estus

1 Answer

+1 vote
Best answer

Hi Diet,

You seem to be calling loadingthread.waitto_finish() from loading thread itself, which will deadlock the thread. You should call it from the main thread after you have finished loading. Also, why wouldn't you just use a for loop to go through the queue instead those awkward recursive calls? Something like:

func loading_thread_func(userdata):
    # this is executed on the loading thread
    for path in queue:
        load_data(path)
    call_deferred("on_exiting_thread")

func on_exiting_thread():
    # this is executed on the main thread
    loading_thread.wait_to_finish()
    emit_signal("background_loading_complete")
answered Aug 12, 2019 by Milan Davidovic (46 points)
selected Aug 12, 2019 by Diet Estus

I can't thank you enough for this suggestion! I've got the thread working now using your simplified procedure. Thanks a ton!

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.