Files
Kalulu/sources/utils/autoloads/scene_loader.gd
T
2026-06-11 11:16:03 +02:00

67 lines
2.7 KiB
GDScript

extends Node
## Scene changer that keeps peak memory low on low-end devices.
##
## [method SceneTree.change_scene_to_file] loads the new scene while the old
## one is still fully resident, so transitions between heavy scenes (gardens,
## minigames) momentarily need the memory of both — enough to OOM-crash old
## tablets. This helper frees the current scene first, then streams the new
## scene from disk on a background thread so the main thread keeps rendering
## frames instead of freezing.
##
## Callers are expected to have covered the screen (OpeningCurtain closed)
## before calling [method change_scene]; the curtain stays visible while the
## tree has no current scene.
## Scene shown if the requested scene fails to load, so the player is never
## stuck on a black screen.
const FALLBACK_SCENE_PATH: String = "res://sources/menus/main/main_menu.tscn"
var _is_changing: bool = false
func change_scene(scene_path: String) -> void:
if _is_changing:
Log.warn("SceneLoader: Change to %s requested while another change is in progress; ignoring" % scene_path)
return
if not ResourceLoader.exists(scene_path):
Log.error("SceneLoader: Scene does not exist: %s" % scene_path)
return
_is_changing = true
# Defer so the calling scene is never freed while one of its methods is
# still on the stack.
_change_scene_deferred.call_deferred(scene_path)
func _change_scene_deferred(scene_path: String) -> void:
var tree: SceneTree = get_tree()
# Free the old scene before loading the new one: this roughly halves the
# memory peak of the transition.
if tree.current_scene:
tree.unload_current_scene()
var error: Error = ResourceLoader.load_threaded_request(scene_path)
if error != OK:
Log.error("SceneLoader: Could not start loading %s (error %s)" % [scene_path, error_string(error)])
_finish_with_fallback(tree, scene_path)
return
var status: ResourceLoader.ThreadLoadStatus = ResourceLoader.load_threaded_get_status(scene_path)
while status == ResourceLoader.THREAD_LOAD_IN_PROGRESS:
await tree.process_frame
status = ResourceLoader.load_threaded_get_status(scene_path)
if status != ResourceLoader.THREAD_LOAD_LOADED:
Log.error("SceneLoader: Failed to load %s (status %d)" % [scene_path, status])
_finish_with_fallback(tree, scene_path)
return
var packed_scene: PackedScene = ResourceLoader.load_threaded_get(scene_path) as PackedScene
if not packed_scene:
Log.error("SceneLoader: %s is not a PackedScene" % scene_path)
_finish_with_fallback(tree, scene_path)
return
_is_changing = false
tree.change_scene_to_packed(packed_scene)
func _finish_with_fallback(tree: SceneTree, failed_scene_path: String) -> void:
_is_changing = false
if failed_scene_path != FALLBACK_SCENE_PATH:
tree.change_scene_to_file(FALLBACK_SCENE_PATH)