Quick answer: Call ResourceLoader.load_threaded_request(path) to start the load, poll with load_threaded_get_status(path) every frame, and only call load_threaded_get(path) when the status is THREAD_LOAD_LOADED. Never modify the scene tree from the background thread — use call_deferred.
You add background loading to your Godot 4 game so the player sees a loading bar instead of a frozen screen. The first scene loads fine. The second scene crashes at random with a Condition "!is_inside_tree()" error. Or load_threaded_get returns null even though the file exists. The bug is almost always a threading violation: you are touching the scene tree from the wrong thread.
The Three-Step Pattern
Threaded loading in Godot 4 is a three-step process. All three steps must happen in the right order, on the right thread.
extends Node
var _loading_path: String = ""
func start_load(scene_path: String) -> void:
_loading_path = scene_path
ResourceLoader.load_threaded_request(scene_path)
func _process(delta):
if _loading_path == "":
return
var progress = []
var status = ResourceLoader.load_threaded_get_status(_loading_path, progress)
match status:
ResourceLoader.THREAD_LOAD_IN_PROGRESS:
$ProgressBar.value = progress[0] * 100
ResourceLoader.THREAD_LOAD_LOADED:
var scene = ResourceLoader.load_threaded_get(_loading_path)
_loading_path = ""
_switch_scene(scene)
ResourceLoader.THREAD_LOAD_FAILED:
push_error("Failed to load: " + _loading_path)
_loading_path = ""
func _switch_scene(scene: PackedScene) -> void:
# This runs on the main thread (called from _process)
get_tree().change_scene_to_packed(scene)
The key points:
load_threaded_requeststarts the background load. It returns immediately.load_threaded_get_statuspolls the state. Call it from_process(main thread) every frame. It also populates a progress array (0.0 to 1.0) for the loading bar.load_threaded_getretrieves the finished resource. Only call it when the status isTHREAD_LOAD_LOADED.
The Scene Tree Rule
Godot’s scene tree is not thread-safe. You cannot call add_child, remove_child, queue_free, change_scene, or any tree-modifying function from a background thread. The engine does not lock the tree during render — a concurrent modification corrupts the node list and produces seemingly random crashes.
The fix is always the same: schedule tree modifications with call_deferred.
# From a background thread or signal callback
call_deferred("_add_loaded_node", loaded_resource)
func _add_loaded_node(resource) -> void:
var instance = resource.instantiate()
add_child(instance) # runs on the main thread, safe
Common Crashes
load_threaded_get returns null. You called it before the load finished. Always check the status first. If the status is THREAD_LOAD_IN_PROGRESS, the resource is not ready yet. If it is THREAD_LOAD_FAILED, the path is wrong or the file is corrupted.
Random crash in the renderer. You instantiated a scene from the loaded resource and added it to the tree from a callback that runs on a non-main thread. Even if the callback looks like it should be on the main thread (it is connected to a signal), some signals in Godot can fire from worker threads. Wrap every tree modification in call_deferred to be safe.
Resource is loaded but looks wrong. You loaded a scene on one thread while another thread was editing the same Resource (e.g. setting shader parameters on a shared material). Resources are not inherently thread-safe. Use resource.duplicate() to create a thread-local copy if you need to modify it.
preload vs. load vs. load_threaded
Choose the right tool for the job:
- preload: compile-time. The resource is loaded when the script is parsed. Zero runtime cost but increases startup time. Best for small, always-needed assets like sound effects and UI textures.
- load: runtime, synchronous. Blocks the main thread until the file is read and parsed. Simple but causes freezes for large files. Fine for small dynamic assets.
- load_threaded_request: runtime, asynchronous. Does not block. Must be polled. Best for large scene files, level data, and anything that takes more than a frame to load.
Verifying the Fix
Add a loading screen that shows the progress bar during threaded loads. Load the largest scene in your project. The progress bar should advance smoothly from 0% to 100% and the scene should appear without any errors in the Output panel. If you see !is_inside_tree or null-access errors, a tree modification is happening off the main thread.
“In Godot 4, background loading is three function calls and one rule: never touch the scene tree from a background thread. Follow the pattern and loading screens are trivial.”
Related Issues
For resource preload cyclic errors, see Godot resource preload error cyclic. For ResourceLoader crashes more broadly, see Godot resource loader thread crash. For scene transition flicker, see Godot scene transition flicker black frame.
Wrap every scene tree modification in call_deferred, even if you think you are on the main thread. The cost is zero and the safety is absolute.