This page documents the ResourceLoader singleton, the ResourceFormatLoader interface, cache modes, and the threaded loading system. For details on the Resource base class itself, see Resource Class. For the resource cache internals, see Resource Cache. For the save-side counterpart, see ResourceSaver.
ResourceLoader is Godot's central dispatch point for loading resources from disk. It is a static singleton class defined in core/io/resource_loader.h that maintains a registry of up to 64 ResourceFormatLoader instances. When a load is requested, ResourceLoader queries each registered loader in order, delegates to the first one that recognizes the file, caches the result, and returns a Ref<Resource>.
The system has two main layers:
ResourceLoader — the static singleton handling path resolution, cache lookup, thread coordination, and loader dispatch.ResourceFormatLoader — a RefCounted-based interface that format-specific implementations override to parse actual file data.Diagram: ResourceLoader Component Overview
Sources: core/io/resource_loader.h105-311 core/io/resource_loader.cpp56-58
ResourceFormatLoader (declared in core/io/resource_loader.h47-95) is a RefCounted class with GDVirtual methods. Subclasses implement the methods that correspond to their file format. All virtual methods have default C++ implementations that call through GDVirtual, so they can be overridden from GDScript.
| Method | Signature | Purpose |
|---|---|---|
_get_recognized_extensions | → Vector<String> | List of file extensions this loader handles |
_recognize_path | (path, type) → bool | Override path recognition logic |
_handles_type | (type) → bool | Whether this loader produces the named type |
_get_resource_type | (path) → String | Inspect the type without fully loading |
_get_resource_script_class | (path) → String | Return script class name stored in file header |
_get_resource_uid | (path) → int | Return the UID associated with the resource |
_get_dependencies | (path, add_types) → Vector<String> | List external resource dependencies |
_get_classes_used | (path) → Vector<String> | Classes referenced inside the file |
_rename_dependencies | (path, renames) → Error | Rewrite dependency paths in-file |
_exists | (path) → bool | Check file existence without loading |
_load | (path, original_path, use_sub_threads, cache_mode) → Variant | Required. Load and return the resource or an Error code |
The _load virtual is marked GDVIRTUAL4RC_REQUIRED (core/io/resource_loader.h73), meaning GDScript subclasses must implement it.
recognize_path() calls _recognize_path if overridden; otherwise it compares the path's extension against get_recognized_extensions() with a case-insensitive suffix match core/io/resource_loader.cpp60-81
If _get_resource_uid is not overridden, the default implementation reads the sidecar <path>.uid file core/io/resource_loader.cpp116-127 Loaders that store UIDs in the file header should override _get_resource_uid and return true from has_custom_uid_support().
Diagram: ResourceFormatLoader Interface (GDVirtual bindings)
Sources: core/io/resource_loader.h47-95 core/io/resource_loader.cpp60-221
ResourceFormatLoader::CacheMode controls how a load interacts with ResourceCache. It is defined as an enum in core/io/resource_loader.h51-57 and exposed to scripts.
| Enum Value | Int | Behavior |
|---|---|---|
CACHE_MODE_IGNORE | 0 | Skip cache on read and write. Dependencies use CACHE_MODE_REUSE. |
CACHE_MODE_REUSE | 1 | Return cached instance if present; cache newly loaded resource. (default) |
CACHE_MODE_REPLACE | 2 | Like REUSE, but refreshes an already-cached instance in-place via copy_from(). |
CACHE_MODE_IGNORE_DEEP | 3 | Like IGNORE, propagated recursively to all external dependencies. |
CACHE_MODE_REPLACE_DEEP | 4 | Like REPLACE, propagated recursively to all external dependencies. |
In practice, REPLACE is used by the editor when reloading a modified resource without invalidating existing references to it. The "deep" variants affect what cache mode is passed down when loading [ext_resource] entries inside .tscn/.tres files.
The cache mode is stored on the ThreadLoadTask struct and is forwarded through _load_start → _run_load_task → ResourceFormatLoader::load.
Sources: core/io/resource_loader.h51-57 core/io/resource_loader.cpp389-424 doc/classes/ResourceLoader.xml168-183
Before any load begins, _validate_local_path() (core/io/resource_loader.cpp475-484) normalizes the path:
uid://...), it is resolved to a filesystem path via ResourceUID.res:// and simplified.ProjectSettings::localize_path().Translation remaps are applied inside _run_load_task via _path_remap() before the actual _load() call. If a remap is applied, the original path becomes p_original_path and the remapped path becomes p_path.
Sources: core/io/resource_loader.cpp475-484 core/io/resource_loader.cpp363-367
ResourceLoader::load() is the primary synchronous entry point (core/io/resource_loader.cpp511-534).
Internal call sequence:
Key internals:
_load_start() (core/io/resource_loader.cpp536-640) creates a ThreadLoadTask, checks for an in-flight load of the same path (to avoid duplicate work), and either runs the task immediately (synchronous) or enqueues it in WorkerThreadPool._load() (core/io/resource_loader.cpp273-331) iterates registered loaders and calls the first matching one's load() method.set_path() on the resource inserts it into ResourceCache unless CACHE_MODE_IGNORE is active.If called from a WorkerThreadPool task, load() promotes itself to LOAD_THREAD_SPAWN_SINGLE to prevent cyclic detection issues (core/io/resource_loader.cpp517-523).
Sources: core/io/resource_loader.cpp273-331 core/io/resource_loader.cpp511-534 core/io/resource_loader.cpp536-640
ResourceLoader supports background loading via three public functions intended to be used together:
| Function | Purpose |
|---|---|
load_threaded_request(path, type_hint, use_sub_threads, cache_mode) | Enqueue a background load and return OK if started |
load_threaded_get_status(path, progress[]) | Poll status; optionally fills a progress float |
load_threaded_get(path) | Block until complete and return the loaded resource |
LoadThreadMode (internal enum, core/io/resource_loader.h123-127) controls dispatch:
| Mode | When Used |
|---|---|
LOAD_THREAD_FROM_CURRENT | Synchronous: run on calling thread |
LOAD_THREAD_SPAWN_SINGLE | Spawn a single dedicated WorkerThreadPool task |
LOAD_THREAD_DISTRIBUTE | Spawn a task + allow sub-tasks (for use_sub_threads=true) |
ResourceLoader::LoadToken (core/io/resource_loader.h129-138) is a RefCounted handle representing an in-flight load. Format loaders (like ResourceLoaderBinary and ResourceLoaderText) hold Ref<LoadToken> for each external dependency they kick off in parallel, then call ResourceLoader::_load_complete() on those tokens when they need the result.
This allows a .tscn file to kick off parallel background loads for all [ext_resource] entries before sequentially parsing the rest of the file.
The internal ThreadLoadTask struct (core/io/resource_loader.h176-211) tracks everything about an in-flight load:
| Field | Type | Purpose |
|---|---|---|
task_id | WorkerThreadPool::TaskID | Worker pool task (if in pool) |
thread_id | Thread::ID | Calling thread ID (if not in pool) |
load_token | LoadToken* | Back-reference to the token |
local_path | String | Normalized resource path |
type_hint | String | Expected type |
progress | float | 0.0–1.0 load fraction |
status | ThreadLoadStatus | INVALID / IN_PROGRESS / FAILED / LOADED |
cache_mode | CacheMode | Cache behavior |
resource | Ref<Resource> | Result once done |
parent_task | ThreadLoadTask* | Outer load that spawned this one |
sub_tasks | HashSet<String> | Paths of spawned sub-loads |
cond_var | ConditionVariable* | Waiter for non-pool threads |
All access to thread_load_tasks (a HashMap<String, ThreadLoadTask>) is protected by thread_load_mutex (a SafeBinaryMutex).
_dependency_get_progress() (core/io/resource_loader.cpp642-671) computes progress recursively across sub-tasks:
progress = (sum of sub-task progresses / count) * 0.5 + own_progress * 0.5
Cycles in the dependency graph are detected via the in_progress_check flag on ThreadLoadTask.
When _load_complete_inner() (core/io/resource_loader.cpp810-944) waits on a pool task and WorkerThreadPool reports ERR_BUSY (would deadlock), it re-runs the blocked task directly on the waiting thread to break the cycle. This is the mechanism that handles cyclic scene dependencies without hanging.
Diagram: Threaded Loading State Machine
Sources: core/io/resource_loader.h116-138 core/io/resource_loader.h176-211 core/io/resource_loader.cpp335-473 core/io/resource_loader.cpp642-715 core/io/resource_loader.cpp810-944
Three format loaders are registered at engine startup:
| Class | File | Formats |
|---|---|---|
ResourceFormatLoaderBinary | core/io/resource_format_binary.h109-124 | .res, .scn (binary) |
ResourceFormatLoaderText | scene/resources/resource_format_text.h | .tres, .tscn (text) |
ResourceFormatImporter | core/io/resource_importer.h41-107 | All imported formats via .import sidecar |
ResourceLoaderText)Parses Godot's text-based resource format. During load(), it:
[ext_resource] tags and immediately calls ResourceLoader::_load_start() on each, obtaining a LoadToken per dependency.[sub_resource] sections, instantiating objects via ClassDB::instantiate().ResourceLoader::_load_complete() on each token when the actual property value is needed.This means external dependencies are loaded concurrently with the rest of the parse when use_sub_threads is enabled.
Source: scene/resources/resource_format_text.cpp451-537
ResourceLoaderBinary)Follows the same pattern: in load(), it first dispatches _load_start() for all external resources, then sequentially reads internal resources and resolves external references via _load_complete().
Source: core/io/resource_format_binary.cpp681-708
ResourceFormatImporter)Handles source assets that have been imported (e.g. .png → .ctex). It reads the .import sidecar file via _get_path_and_type() to find the true path and type of the imported output, then delegates to ResourceLoader::_load() with those values.
Source: core/io/resource_importer.cpp172-200
Loaders are stored in a fixed-size array ResourceLoader::loader[MAX_LOADERS] (max 64) with loader_count tracking the filled count core/io/resource_loader.h151-152
add_resource_format_loader(loader, at_front) — inserts at front or back.remove_resource_format_loader(loader) — removes by pointer comparison.GDScript ResourceFormatLoader subclasses with a class_name are auto-registered via add_custom_loaders() at startup. The lookup in _load() is a linear scan; the first loader whose recognize_path() returns true wins.
Sources: core/io/resource_loader.h248-249 core/io/resource_loader.cpp282-291
During background loads, Resource::connect_changed() and disconnect_changed() cannot safely call into the signal system from off-main-thread. The loader intercepts these calls and queues them as ResourceChangedConnection entries on the active ThreadLoadTask:
resource_changed_connect() (core/io/resource_loader.cpp958-973) — adds to curr_load_task->resource_changed_connectionsresource_changed_disconnect() (core/io/resource_loader.cpp976-988) — removes from the same listresource_changed_emit() (core/io/resource_loader.cpp990-1000) — calls matching callables immediately (still within the load thread context)Once the load completes and is collected on the main thread, the connections are migrated to the real signal system. On non-main threads, MessageQueue::push_callable is used to defer the connection until the main thread processes the queue core/io/resource_loader.cpp914-940
Sources: core/io/resource_loader.cpp958-1000 core/io/resource.cpp200-218
Two behaviors govern what happens when a resource type is unavailable at load time:
| Setting | Effect |
|---|---|
abort_on_missing_resource = true (default) | Load fails with ERR_FILE_MISSING_DEPENDENCIES |
create_missing_resources_if_class_unavailable = true | A MissingResource placeholder is instantiated and its properties recorded |
set_abort_on_missing_resources(bool) / get_abort_on_missing_resources() control the first. set_create_missing_resources_if_class_unavailable(bool) controls the second.
MissingResource is used primarily in editor contexts to round-trip unknown types without data loss.
Sources: core/io/resource_loader.h280-281 core/io/resource_loader.h299-300 scene/resources/resource_format_text.cpp596-616 core/io/resource_format_binary.cpp771-782
ResourceLoader maintains a static HashMap<String, Vector<String>> translation_remaps. When _path_remap() is called, it checks if the path has a remap entry matching the current locale and substitutes the localized path. If a remap is applied, the resource is marked with set_as_translation_remapped(true).
Remaps are loaded from ProjectSettings via load_translation_remaps() and cleared via clear_translation_remaps(). The remapped_list (SelfList<Resource>::List) tracks all currently-remapped resources so they can be reloaded when the locale changes.
Sources: core/io/resource_loader.h161-167 core/io/resource_loader.cpp363-364
| Method | Description |
|---|---|
load(path, type_hint, cache_mode, &error) | Synchronous blocking load |
load_threaded_request(path, type_hint, use_sub_threads, cache_mode) | Start background load |
load_threaded_get_status(path, progress[]) | Poll background load state |
load_threaded_get(path, &error) | Collect background load result (blocks if needed) |
exists(path, type_hint) | Check if a recognized resource exists at path |
get_resource_type(path) | Get type string without loading |
get_resource_uid(path) | Get UID without loading |
get_dependencies(path, &list, add_types) | List external resource dependencies |
add_resource_format_loader(loader, at_front) | Register a new format loader |
remove_resource_format_loader(loader) | Unregister a format loader |
get_recognized_extensions_for_type(type, &list) | Query extensions across all loaders |
set_abort_on_missing_resources(bool) | Control missing dependency behavior |
path_remap(path) | Apply translation remap to a path |
list_directory(dir_path) | List resource files in a virtual directory |
Sources: core/io/resource_loader.h233-311 doc/classes/ResourceLoader.xml
Refresh this wiki
This wiki was recently refreshed. Please wait 3 days to refresh again.