Troubleshooting background loading issue

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

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.

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()
:bust_in_silhouette: Reply From: Milan Davidovic

Hi Diet,

You seem to be calling loading_thread.wait_to_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")

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

Diet Estus | 2019-08-12 22:54