67 lines
2.7 KiB
GDScript
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)
|