Skip to content

Instantly share code, notes, and snippets.

@NovemberDev
Last active February 10, 2024 22:57
Show Gist options
  • Save NovemberDev/9eded35e06a418d6d431658fe3b51e63 to your computer and use it in GitHub Desktop.
Save NovemberDev/9eded35e06a418d6d431658fe3b51e63 to your computer and use it in GitHub Desktop.
Asynchronously loads scenes in godot
# Loads a scene in the background using a seperate thread and a queue.
# Foreach new scene there will be an instance of ResourceInteractiveLoader
# that will raise an on_scene_loaded event once the scene has been loaded.
# Hooking the on_progress event will give you the current progress of any
# scene that is being processed in realtime. The loader also uses caching
# to avoid duplicate loading of scenes and it will prevent loading the
# same scene multiple times concurrently.
#
# Sample usage:
#
# # Copy & Paste this script and create and AutoLoad for it, then on your world
# # manager copy & paste this snippet, make sure to replace the load_scene with
# # a scene that exists in your project
#
#func _ready():
# SceneLoader.connect("on_scene_loaded", self, "do_scene_loaded")
# SceneLoader.load_scene("res://myscene.tscn", { hii = "cool" })
#
#func do_scene_loaded(scene):
# # You can hook the instance name to run your specific per scene logic
# # Example: parse the name for a substring such as "ITEM_" and then
# # run your item specific logic
# print(scene.path)
# print(scene.instance.name)
# print(props.hii)
# --
#
# Author: @November_Dev
#
extends Node
var thread
var scene_queue = {}
var file = File.new()
var cache = {}
var awaiters = []
signal on_progress
signal on_scene_loaded
func _ready():
thread = Thread.new()
thread.start(self, "_thread_runner", null)
func _thread_runner(o):
while true:
OS.delay_msec(5)
if scene_queue.size() > 0:
for i in scene_queue:
var err = scene_queue[i].loader.poll()
call_deferred("emit_signal", "on_progress", scene_queue[i].path, scene_queue[i].loader.get_stage_count(), scene_queue[i].loader.get_stage())
if err == ERR_FILE_EOF:
scene_queue[i].loader = scene_queue[i].loader.get_resource()
scene_queue[i].instance = scene_queue[i].loader.instance()
cache[scene_queue[i].path] = scene_queue[i]
call_deferred("emit_signal", "on_scene_loaded", scene_queue[i])
scene_queue.erase(scene_queue[i].path)
elif err != OK:
print("Failed to load: " + scene_queue[i].path)
scene_queue.erase(scene_queue[i].path)
for awaiter in awaiters:
if cache.has(awaiter.path):
if awaiter.path == cache[awaiter.path].path:
awaiter.loader = cache[awaiter.path].loader
awaiter.instance = cache[awaiter.path].instance.duplicate()
call_deferred("emit_signal", "on_scene_loaded", awaiter)
awaiters.remove(awaiters.find(awaiter))
func load_scene(path, props = null):
if !file.file_exists(path):
print("File does not exist: " + path)
return
if cache.has(path):
call_deferred("emit_signal", "on_scene_loaded", { path = path, loader = cache[path].loader, instance = cache[path].loader.instance(), props = props })
return
if !scene_queue.has(path):
scene_queue[path] = { path = path, loader = ResourceLoader.load_interactive(path), instance = null, props = props }
else:
awaiters.push_back({ path = path, loader = null, instance = null, props = props })
func is_loading_scene(path):
return scene_queue.has(path)
func clear_cache():
for item in cache:
item.instance.queue_free()
cache = {}
@nezvers
Copy link

nezvers commented Dec 12, 2019

This is amazing and seems like exactly what I needed. I'm in the process to create the next example project (Project template with all needed basic stuff for a game - main menu, options menu, transition) and I wanted to include background loading for smooth gameplay.
I got an error when I went from the main menu to the test level, then back to the main menu and pressed the new game again. At the start, I found that instead of the supposed new scene the scene swapper received InputEventMouseButton. But then I pinpointed different error:

ERROR:
SceneLoader.gd 79
instance = cache[path].instance.duplicate()
Attempt to call function 'duplicate' in base 'previously freed instance' on a null instance.
Here's the project - https://drive.google.com/open?id=19UR8QtA1AX-3I0LCdgXDC8lktI2dHzY1

@NovemberDev
Copy link
Author

NovemberDev commented Dec 15, 2019

This is amazing and seems like exactly what I needed. I'm in the process to create the next example project (Project template with all needed basic stuff for a game - main menu, options menu, transition) and I wanted to include background loading for smooth gameplay.
I got an error when I went from the main menu to the test level, then back to the main menu and pressed the new game again. At the start, I found that instead of the supposed new scene the scene swapper received InputEventMouseButton. But then I pinpointed different error:

ERROR:
SceneLoader.gd 79
instance = cache[path].instance.duplicate()
Attempt to call function 'duplicate' in base 'previously freed instance' on a null instance.
Here's the project - https://drive.google.com/open?id=19UR8QtA1AX-3I0LCdgXDC8lktI2dHzY1

I'm sorry for the late response and the inconvenience you had to face! I resolved this issue by making an .instance() call rather than a .duplicate() call on the cached version, it should work now. =)

[...] instance = cache[path].instance.duplicate(), props = props })
to:
[...] instance = cache[path].loader.instance(), props = props })

@rburing
Copy link

rburing commented Jan 4, 2020

Nice script, thanks! There's a small typo in the Sample usage: print(props.hii) should be print(scene.props.hii).

To stop the thread when the game exits, you can break out of the while loop based on a boolean (protected with a mutex), so that in _exit_tree() you can set the boolean to true and call thread.wait_to_finish() as shown in the documentation. This fixes the error message(s) you would otherwise get when you quit the game.

@sairam4123
Copy link

Hi, does this work with Godot 4?

@sairam4123
Copy link

Hi, what's the license of this? I'd be happy to use this on my game, could you share the license of this?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment