"load_threaded_request" is amazing!

So in my game I was dropping frames. My player missions are auto generated and I had to load enemies and other mission assets as they were required by the mission generator. I could not preload them all. It was the load that was causing the frame drops. (Thanks to the profiler that for the first time was infinitely helpful in discovering this). I was stuck!

Then I discovered in the docs:

I was a bit nervous about using this. It didn’t feel well documented and I thought OMG, this is not going to work. But my first attempt and all frame dropping has gone. Not only that but it does it’s own caching (so no danger of loading the same scene multiple times), it does it’s own garbage collection (so no need to free or clear the resource loader for memory management, once the scene is queue freed and no more references remain it removes itself from the cache) and I can use it multiple time to background load machines, enemies, items etc.

I cannot bellieve that every time I get stuck Godot has been there, done that and solved it for me! All I have to do is read the docs!

I am so pleased. I was on the verge of abandoning my auto generated curvy scenery! I thought it was my curve generation, then I thought it was my minimap, then I thought it was my collision polygons. Turns out it was the load without preloading that was blocking.

If anyone is interested here is my machine loading function:


func start_loading_machine_threads() -> void:
	if MissionSettings.mission_machines.is_empty():
		return
	for machine in MissionSettings.mission_machines:
		var machine_type: String = MissionSettings.mission_machines[machine]
		var machine_data: Dictionary = MACHINES_DATA[machine_type]
		ResourceLoader.load_threaded_request(machine_data["scene_uid"])

And then later when I get near the machine it is loaded like this:

	...
	var machine_scene: PackedScene = ResourceLoader.load_threaded_get(machine_data["scene_uid"])
	# TODO need to check it is available
	var new_machine: Node2D = machine_scene.instantiate()
	....
	add_child(new_machine)

The only thing todo now is how to check it is actually available. TBH I have no idea at the moment how to handle any potential loading errors or it is not loaded yet.

I know I can use:

ResourceLoader.load_threaded_get_status

And respond to these:

enum ThreadLoadStatus:

ThreadLoadStatus THREAD_LOAD_INVALID_RESOURCE = 0
The resource is invalid, or has not been loaded with load_threaded_request().

ThreadLoadStatus THREAD_LOAD_IN_PROGRESS = 1
The resource is still being loaded.

ThreadLoadStatus THREAD_LOAD_FAILED = 2
Some error occurred during loading and it failed.

ThreadLoadStatus THREAD_LOAD_LOADED = 3
The resource was loaded successfully and can be accessed via load_threaded_get().

But since the machine is absolutely required when the relevant area of the environment is loaded in the chunk_manager, I am going to have to assume it is available. But this is bad really. If anyone has any idea how to deal with it, since it is game breaking, I would love to know.

I suppose just throwing an error and ending the game, but how awful is that?

But this is my game architecture, it does not take away from the amount of work this one simple command, load_threaded_request, must be doing in the background! Thankyou Godot!

9 Likes

I love a good appreciation post, and this one is way above norm! :sign_of_the_horns:t5:
Cheers !

2 Likes

Realistically, the only way it fails is if the resource doesn’t exist (which you can check and handle beforehand), or it cannot be loaded due to insufficient memory available on the machine at that time (in which case the game will probably crash anyway, so you have bigger problems).

2 Likes

@zigg3c
Yeah I think I agree. I mean this should never happen. The scene in question takes 30ms to load using the ‘load’ function which was causing the frame drops. But it should never happen that the scene being requested is not found. So I suppose that realistically, even the AAA games I play myself sometimes drop, crash or just hang up. This might be one of the cases where if it goes wrong what can I realistically do about it. I just don’t think it is going to be something I can handle in a production version. So I think I will just throw an error and be done with it. (I wonder if this will come back to bite me in the future :slight_smile: )

1 Like

Make sure to add a comment for good measure. I hear this always works wonders:

var resource := ResourceLoader.load_threaded_get(path)
if not resource: # Should never happen
	push_error("Now we've gone and done it")
4 Likes

LOL.

I already have similar things in more places than I care to admit.

(From my current code base)


func enter_state() -> void:
	# New game welcome
	if GameSettings.is_doing_new_game_intro:
		do_landed_at_homebase_welcome()
		return
	
	# Mission completed
	if MissionSettings.is_required_target_list_completed:
		do_landed_at_homebase_mission_completed()
		return
	
	# Just refuelling
	if MissionSettings.is_mission_in_progress:
		do_landed_for_pitt_stop()
		return
	
	# Not sure how it would get here!
	print("I don't know what to do now.")

2 Likes

Another similar (probably little known) feature is the WorkerThreadPool that makes it very easy to do expensive stuff in the background, without having to worry about creating and syncing up threads yourself.

2 Likes

Oh that looks very interesting. I had never seen this but I think this is going to be very useful indeed.

Thank you for sharing that!

Blockquote Once you call load_threaded_get(), either the resource finished loading in the background and will be returned instantly or the load will block at this point like load() would. If you want to guarantee this does not block, you either need to ensure there is enough time between requesting the load and retrieving the resource or you need to check the status manually.