ResourceLoader是否应缓存已加载资源?异步加载二次获取异常疑问
在MacOS(Intel)平台使用Godot 4.2测试异步资源加载功能时遇到异常:调用ResourceLoader.load_threaded_request发起加载请求后,首次调用load_threaded_get_status和load_threaded_get能正常获取已加载的资源,但第二次调用时状态变为THREAD_LOAD_INVALID_RESOURCE(表示资源无效或未通过load_threaded_request加载)。
原本以为ResourceLoader会缓存已加载资源,因为load_threaded_request的默认缓存模式是CACHE_MODE_REUSE,但官方文档未明确该缓存模式的具体含义,不确定这是Bug还是对API的理解偏差。相关代码片段如下:
const ROCKET_SCENE_FILE: String = "res://scenes/rocket.tscn" func _ready(): ResourceLoader.load_threaded_request(ROCKET_SCENE_FILE) # 异步加载火箭场景 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) # 获取已加载的场景 var rocket_instance: Node = rocket_scene.instantiate() # 实例化场景 add_child(rocket_instance) # 添加到当前场景 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() # 读取输入并设置状态/触发器 if shoot == true: fire_rocket() shoot = false # 重置射击触发器
疑问:ResourceLoader是否设计为缓存资源以支持多次获取?还是仅允许单次通过异步API获取?
核心结论
ResourceLoader的全局资源缓存是存在的,但异步加载请求的状态仅在单次load_threaded_get调用前有效——调用load_threaded_get获取资源后,对应的异步请求会被销毁,因此后续再调用load_threaded_get_status会返回THREAD_LOAD_INVALID_RESOURCE,这是API的正常行为,不是Bug。
关于CACHE_MODE_REUSE的说明
CACHE_MODE_REUSE控制的是资源本身是否被存入全局资源缓存,而非异步加载请求的缓存。当你第一次通过load_threaded_get拿到资源后,该资源已经被缓存到全局缓存中,但异步加载的请求状态会被清理,所以无法再通过异步API的状态检查来获取它。
解决方案
针对你的场景,有两种更合理的处理方式:
自行保存资源引用
在首次获取到资源后,将其保存为类成员变量,后续直接使用该引用实例化,无需再调用异步加载相关API:const ROCKET_SCENE_FILE: String = "res://scenes/rocket.tscn" var rocket_scene: Resource = null func _ready(): ResourceLoader.load_threaded_request(ROCKET_SCENE_FILE) func fire_rocket() -> void: if rocket_scene: var rocket_instance: Node = rocket_scene.instantiate() add_child(rocket_instance) return var rocket_loading_status = ResourceLoader.load_threaded_get_status(ROCKET_SCENE_FILE) if rocket_loading_status == ResourceLoader.THREAD_LOAD_LOADED: rocket_scene = ResourceLoader.load_threaded_get(ROCKET_SCENE_FILE) var rocket_instance: Node = rocket_scene.instantiate() add_child(rocket_instance) else: assert(rocket_loading_status == ResourceLoader.THREAD_LOAD_IN_PROGRESS, "加载火箭资源失败: " + str(rocket_loading_status))利用全局缓存直接同步加载
因为资源已经被缓存,后续调用ResourceLoader.load()会直接从缓存中获取,不会产生实际的IO操作,性能几乎和直接使用引用一致:func fire_rocket() -> void: var rocket_loading_status = ResourceLoader.load_threaded_get_status(ROCKET_SCENE_FILE) if rocket_loading_status == ResourceLoader.THREAD_LOAD_LOADED: # 首次获取后,后续直接用同步加载从缓存取 var rocket_scene: Resource = ResourceLoader.load(ROCKET_SCENE_FILE) var rocket_instance: Node = rocket_scene.instantiate() add_child(rocket_instance) elif rocket_loading_status == ResourceLoader.THREAD_LOAD_INVALID_RESOURCE: # 异步请求已销毁,但资源在缓存中,直接加载 var rocket_scene: Resource = ResourceLoader.load(ROCKET_SCENE_FILE) var rocket_instance: Node = rocket_scene.instantiate() add_child(rocket_instance) else: assert(rocket_loading_status == ResourceLoader.THREAD_LOAD_IN_PROGRESS, "加载火箭资源失败: " + str(rocket_loading_status))
补充说明
如果确实需要多次发起异步加载(比如资源可能被卸载后重新加载),可以在每次调用fire_rocket时先检查状态,若为THREAD_LOAD_INVALID_RESOURCE则重新调用load_threaded_request,但这种场景在你的案例中并不必要,因为资源一旦加载就会被缓存,除非手动调用ResourceLoader.unload()清除缓存。
内容的提问来源于stack exchange,提问作者Ty N

