Search code examples
godotgodot4

Is ResourceLoader meant to cache loaded resources?


When using ResourceLoader's .load_threaded_request, .load_threaded_get_status and .load_threaded_get I can't get a loaded resource a second time. I expected it to be cached and now I'm unsure if that is the intended functionality.

I am using Godot 4.2 on MacOS (Intel) and exploring asynchronous resource loading functionality. Here's a snippet showing some of my code:

const ROCKET_SCENE_FILE: String = "res://scenes/rocket.tscn"


func _ready():
    ResourceLoader.load_threaded_request(ROCKET_SCENE_FILE) # asynchronously load the rocket scene


func fire_rocket() -> void:
    var rocket_loading_status: ResourceLoader.ThreadLoadStatus = ResourceLoader.load_threaded_get_status(ROCKET_SCENE_FILE)
    if  rocket_loading_status == ResourceLoader.THREAD_LOAD_LOADED:
        var rocket_scene: Resource = ResourceLoader.load_threaded_get(ROCKET_SCENE_FILE) # get the loaded scene
        var rocket_instance: Node = rocket_scene.instantiate() # instance the scene
        add_child(rocket_instance) # add to this scene
    else:
        assert(rocket_loading_status == ResourceLoader.THREAD_LOAD_IN_PROGRESS, "FAULT failure loading rocket resource: ResourceLoader.ThreadLoadStatus = " + str(rocket_loading_status))


func _process(delta: float) -> void:
    if handle_actions:
        process_actions() # read input and set state/triggers
    
    if shoot == true:
        fire_rocket()
        shoot = false # reset the shoot trigger

The first time fire_rocket executes everything works as I expected: the status is THREAD_LOAD_LOADED and I successfully get the resource from the ResourceLoader. Strangely, the second time the status is THREAD_LOAD_INVALID_RESOURCE - meaning, The resource is invalid, or has not been loaded with load_threaded_request.

I thought that ResourceLoader would cache the loaded resource since the default cache mode for ResourceLoader.load_threaded_request is CACHE_MODE_REUSE. There's no explanation of what the cache modes mean, in the documentation, so I'm not certain if this is a bug or my misinterpretation. Also my C++ knowledge is so old that I don't really understand what's going on in the source code - so looking at that wasn't really helpful for me.

Does anyone know if ResourceLoader is intended to cache resources so that they can be retrieved multiple times, or if it is a one time retrieval only?


Solution

  • Yes there is a cache, and no you are not guaranteed to get the same object.

    This is what happens to the best of my understanding:

    • When you call load_threaded_request a task is created and added to a list.
    • Godot will have threads take the tasks from the list and execute them. The thread will check the cache when starting to actually load the resource.
    • Later you can check the status of your request with load_threaded_get_status, which will look up the task from the list.
    • Once the status is THREAD_LOAD_LOADED, you ca retrieve the resource with load_threaded_get, this also removes the task from the list.
    • If you call load_threaded_get_status on a resource for which there is not a task you get THREAD_LOAD_INVALID_RESOURCE.

    About the cache, yes, there is a cache of loaded resources. While it is not the issue at hand, I want to point out that despite there being a cache, it does not guarantee getting the same resource either, because:

    • The cache keeps weak references. So if you loaded a resource, and that resource is freed (resources are reference counted), then - of course - you are not getting the same object.
    • When you request to load a resource (threaded or not), Godot checks the cache before beginning to load the resource. So you could be in a situation where the resource is loaded by two different threads, and so you get two different objects.

    The solution for you is to keep a reference of the loaded resource, of course.

    The cache would come into play if there are multiple instances of this code running in the game. In an ideal situation, each of those instances could get the same resource from the cache, and it would only be loaded once.

    If you keep the reference in a script variable, once the node that has the script attached is unloaded the reference count would decrease. And so once all the nodes that use this script are freed, the resource would be freed too.