Optimization global

This commit is contained in:
Adrien Ufferte
2026-06-11 11:12:46 +02:00
parent 90ce89f880
commit 30c1a9863f
8 changed files with 105 additions and 42 deletions
+1
View File
@@ -36,6 +36,7 @@ Globals="*res://sources/language_tool/globals.gd"
LessonLogger="*res://sources/utils/autoloads/lesson_logger.gd" LessonLogger="*res://sources/utils/autoloads/lesson_logger.gd"
MusicManager="*res://sources/utils/autoloads/music_manager.tscn" MusicManager="*res://sources/utils/autoloads/music_manager.tscn"
OpeningCurtain="*res://sources/utils/autoloads/opening_curtain.tscn" OpeningCurtain="*res://sources/utils/autoloads/opening_curtain.tscn"
SceneLoader="*res://sources/utils/autoloads/scene_loader.gd"
ServerManager="*res://sources/utils/autoloads/server_manager.tscn" ServerManager="*res://sources/utils/autoloads/server_manager.tscn"
UnicodeNormalizer="*res://sources/utils/autoloads/unicode_normalizer.gd" UnicodeNormalizer="*res://sources/utils/autoloads/unicode_normalizer.gd"
UserDataManager="*res://sources/utils/autoloads/user_data_manager.gd" UserDataManager="*res://sources/utils/autoloads/user_data_manager.gd"
+32 -35
View File
@@ -20,21 +20,25 @@ const LESSON_BUTTON_OUTLINE_RADIUS: float = 192.0
# button center, mirroring the movie icon just above it. The 384x384 button has # button center, mirroring the movie icon just above it. The 384x384 button has
# its center at y=192; the label (vertical-centered) then centers at y=(this+384)/2. # its center at y=192; the label (vertical-centered) then centers at y=(this+384)/2.
const LESSON_BUTTON_LABEL_TOP_OFFSET: float = 104.0 const LESSON_BUTTON_LABEL_TOP_OFFSET: float = 104.0
const GARDEN_SCENES: Array[PackedScene] = [ # Loaded on demand instead of preloaded: this script never unloads (it has
preload("res://resources/gardens/garden_01.tscn"), # static variables), so preloaded constants would pin every garden's assets in
preload("res://resources/gardens/garden_02.tscn"), # memory for the whole app lifetime — including while minigames run, which
preload("res://resources/gardens/garden_03.tscn"), # OOM-crashes low-memory devices.
preload("res://resources/gardens/garden_04.tscn"), const GARDEN_SCENE_PATHS: Array[String] = [
preload("res://resources/gardens/garden_05.tscn"), "res://resources/gardens/garden_01.tscn",
preload("res://resources/gardens/garden_06.tscn"), "res://resources/gardens/garden_02.tscn",
preload("res://resources/gardens/garden_07.tscn"), "res://resources/gardens/garden_03.tscn",
preload("res://resources/gardens/garden_08.tscn"), "res://resources/gardens/garden_04.tscn",
preload("res://resources/gardens/garden_09.tscn"), "res://resources/gardens/garden_05.tscn",
preload("res://resources/gardens/garden_10.tscn"), "res://resources/gardens/garden_06.tscn",
preload("res://resources/gardens/garden_11.tscn"), "res://resources/gardens/garden_07.tscn",
preload("res://resources/gardens/garden_12.tscn"), "res://resources/gardens/garden_08.tscn",
"res://resources/gardens/garden_09.tscn",
"res://resources/gardens/garden_10.tscn",
"res://resources/gardens/garden_11.tscn",
"res://resources/gardens/garden_12.tscn",
] ]
const LOOK_AND_LEARN_SCENE: PackedScene = preload("res://sources/look_and_learn/look_and_learn.tscn") const LOOK_AND_LEARN_SCENE_PATH: String = "res://sources/look_and_learn/look_and_learn.tscn"
const BOSS_BUTTON_SCENE: PackedScene = preload("res://sources/gardens/boss_button.tscn") const BOSS_BUTTON_SCENE: PackedScene = preload("res://sources/gardens/boss_button.tscn")
const BOSS_MINIGAME_SCENE_PATH: String = "res://sources/minigames/boss/boss_minigame.tscn" const BOSS_MINIGAME_SCENE_PATH: String = "res://sources/minigames/boss/boss_minigame.tscn"
const GARDEN_SIZE: int = 2400 const GARDEN_SIZE: int = 2400
@@ -71,7 +75,6 @@ static var cached_layout_lessons: int = 0
@export var minigames_body_icons: Array[Texture] = [] @export var minigames_body_icons: Array[Texture] = []
@export var minigames_face_icons: Array[Texture] = [] @export var minigames_face_icons: Array[Texture] = []
var _minigame_scene_cache: Dictionary = {}
var lessons: Dictionary = {} var lessons: Dictionary = {}
var _gardens_layout: GardensLayout var _gardens_layout: GardensLayout
var points: Array[Array] = [] var points: Array[Array] = []
@@ -988,20 +991,13 @@ func _lesson_button_label_color(status: StudentProgression.Status) -> Color:
return lesson_button.unlocked_label_color return lesson_button.unlocked_label_color
func _get_minigame_scene(scene_index: int) -> PackedScene: func _get_minigame_scene_path(scene_index: int) -> String:
if scene_index < 0 or scene_index >= minigame_scene_paths.size(): if scene_index < 0 or scene_index >= minigame_scene_paths.size():
return null return ""
if _minigame_scene_cache.has(scene_index):
return _minigame_scene_cache[scene_index] as PackedScene
var scene_path: String = minigame_scene_paths[scene_index] var scene_path: String = minigame_scene_paths[scene_index]
if scene_path.is_empty(): if scene_path.is_empty() or not ResourceLoader.exists(scene_path):
return null return ""
var scene_resource: Resource = load(scene_path) return scene_path
if scene_resource is PackedScene:
var packed_scene: PackedScene = scene_resource as PackedScene
_minigame_scene_cache[scene_index] = packed_scene
return packed_scene
return null
func _count_completed_minigames(lesson_number: int) -> int: func _count_completed_minigames(lesson_number: int) -> int:
@@ -1073,7 +1069,8 @@ func add_gardens() -> void:
for layout_index: int in range(gardens_layout.gardens.size()): for layout_index: int in range(gardens_layout.gardens.size()):
Log.trace("Gardens: Preparing garden %s with layout index %s" % [str(garden_index), str(layout_index)]) Log.trace("Gardens: Preparing garden %s with layout index %s" % [str(garden_index), str(layout_index)])
var garden_layout: GardenLayout = gardens_layout.gardens[layout_index] var garden_layout: GardenLayout = gardens_layout.gardens[layout_index]
var garden: Garden = GARDEN_SCENES[layout_index].instantiate() var garden_scene: PackedScene = load(GARDEN_SCENE_PATHS[layout_index]) as PackedScene
var garden: Garden = garden_scene.instantiate()
garden_parent.add_child(garden) garden_parent.add_child(garden)
garden.garden_index = garden_index garden.garden_index = garden_index
garden_index += 1 garden_index += 1
@@ -1422,7 +1419,7 @@ func _on_lesson_button_pressed() -> void:
current_garden_index = current_garden.garden_index, current_garden_index = current_garden.garden_index,
look_and_learn_completed = false look_and_learn_completed = false
} }
get_tree().change_scene_to_packed(LOOK_AND_LEARN_SCENE) SceneLoader.change_scene(LOOK_AND_LEARN_SCENE_PATH)
func _on_boss_button_pressed(lesson_number: int, garden_index: int) -> void: func _on_boss_button_pressed(lesson_number: int, garden_index: int) -> void:
@@ -1438,7 +1435,7 @@ func _on_boss_button_pressed(lesson_number: int, garden_index: int) -> void:
skip_minigame_layout = true, skip_minigame_layout = true,
boss_gate_lesson = lesson_number boss_gate_lesson = lesson_number
} }
get_tree().change_scene_to_file(BOSS_MINIGAME_SCENE_PATH) SceneLoader.change_scene(BOSS_MINIGAME_SCENE_PATH)
func _on_final_boss_button_pressed(lesson_number: int, garden_index: int) -> void: func _on_final_boss_button_pressed(lesson_number: int, garden_index: int) -> void:
@@ -1455,14 +1452,14 @@ func _on_final_boss_button_pressed(lesson_number: int, garden_index: int) -> voi
boss_gate_lesson = lesson_number, boss_gate_lesson = lesson_number,
is_final_boss = true is_final_boss = true
} }
get_tree().change_scene_to_file(BOSS_MINIGAME_SCENE_PATH) SceneLoader.change_scene(BOSS_MINIGAME_SCENE_PATH)
func _on_minigame_button_pressed(scene_index: int, minigame_number: int) -> void: func _on_minigame_button_pressed(scene_index: int, minigame_number: int) -> void:
if is_locked: if is_locked:
return return
var minigame_scene: PackedScene = _get_minigame_scene(scene_index) var scene_path: String = _get_minigame_scene_path(scene_index)
if not minigame_scene: if scene_path.is_empty():
Log.error("Gardens: Missing minigame scene for index %d" % scene_index) Log.error("Gardens: Missing minigame scene for index %d" % scene_index)
return return
# Block a second wedge click during the curtain-close await below. # Block a second wedge click during the curtain-close await below.
@@ -1476,7 +1473,7 @@ func _on_minigame_button_pressed(scene_index: int, minigame_number: int) -> void
minigame_number = minigame_number, minigame_number = minigame_number,
minigame_completed = false minigame_completed = false
} }
get_tree().change_scene_to_packed(minigame_scene) SceneLoader.change_scene(scene_path)
func _on_scroll_container_gui_input(event: InputEvent) -> void: func _on_scroll_container_gui_input(event: InputEvent) -> void:
@@ -1515,7 +1512,7 @@ func _scroll_by_garden(p_direction: int) -> void:
func _confirm_back_button_pressed() -> void: func _confirm_back_button_pressed() -> void:
UserDataManager.logout_student() UserDataManager.logout_student()
await (OpeningCurtain as OpeningCurtainClass).close() await (OpeningCurtain as OpeningCurtainClass).close()
get_tree().change_scene_to_file("res://sources/menus/login/login.tscn") SceneLoader.change_scene("res://sources/menus/login/login.tscn")
func _on_back_button_button_down() -> void: func _on_back_button_button_down() -> void:
+1 -1
View File
@@ -160,7 +160,7 @@ func _back_to_gardens() -> void:
Log.info("LookAndLearn: Returning to gardens for lesson %d" % lesson_nb) Log.info("LookAndLearn: Returning to gardens for lesson %d" % lesson_nb)
await (OpeningCurtain as OpeningCurtainClass).close() await (OpeningCurtain as OpeningCurtainClass).close()
Gardens.transition_data = gardens_data Gardens.transition_data = gardens_data
get_tree().change_scene_to_file("res://sources/gardens/gardens.tscn") SceneLoader.change_scene("res://sources/gardens/gardens.tscn")
func _on_back_button_pressed() -> void: func _on_back_button_pressed() -> void:
+2 -3
View File
@@ -2,7 +2,7 @@ extends Control
const TEACHER_PASSWORD: String = "42" const TEACHER_PASSWORD: String = "42"
const BACK_SCENE_PATH: String = "res://sources/menus/main/main_menu.tscn" const BACK_SCENE_PATH: String = "res://sources/menus/main/main_menu.tscn"
const NEXT_SCENE: PackedScene = preload("res://sources/gardens/gardens.tscn") const NEXT_SCENE_PATH: String = "res://sources/gardens/gardens.tscn"
const TEACHER_SCENE_PATH: String = "res://sources/menus/settings/teacher_settings.tscn" const TEACHER_SCENE_PATH: String = "res://sources/menus/settings/teacher_settings.tscn"
const DEVELOPER_SCENE_PATH: String = "res://sources/menus/settings/developer_settings.tscn" const DEVELOPER_SCENE_PATH: String = "res://sources/menus/settings/developer_settings.tscn"
const PACKAGE_LOADER_SCENE_PATH: String = "res://sources/menus/language_selection/package_downloader.tscn" const PACKAGE_LOADER_SCENE_PATH: String = "res://sources/menus/language_selection/package_downloader.tscn"
@@ -73,8 +73,7 @@ func _on_code_keyboard_password_entered(password: String) -> void:
await kalulu.play_kalulu_speech(right_password_speech) await kalulu.play_kalulu_speech(right_password_speech)
await OpeningCurtain.close() await OpeningCurtain.close()
Log.trace("LoginScreen: Start loading next scene") Log.trace("LoginScreen: Start loading next scene")
get_tree().change_scene_to_packed(NEXT_SCENE) SceneLoader.change_scene(NEXT_SCENE_PATH)
Log.trace("LoginScreen: End loading next scene")
else: else:
Log.warn("LoginScreen: Unknown student code entered (length=%d)" % password.length()) Log.warn("LoginScreen: Unknown student code entered (length=%d)" % password.length())
kalulu_button.hide() kalulu_button.hide()
+1 -1
View File
@@ -435,7 +435,7 @@ func _go_back_to_the_garden() -> void:
_save_logs() _save_logs()
Gardens.transition_data = gardens_data Gardens.transition_data = gardens_data
get_tree().change_scene_to_file("res://sources/gardens/gardens.tscn") SceneLoader.change_scene("res://sources/gardens/gardens.tscn")
func _play_stimulus() -> void: func _play_stimulus() -> void:
+1 -2
View File
@@ -5,8 +5,7 @@ signal lilypad_in_center(lilypad: Lilypad)
const LILYPAD_SCENE_PATH: String = "res://sources/minigames/frog/lilypad.tscn" const LILYPAD_SCENE_PATH: String = "res://sources/minigames/frog/lilypad.tscn"
static var _lilypad_scene: PackedScene var _lilypad_scene: PackedScene
var top_to_bottom: bool = false var top_to_bottom: bool = false
var is_stopped: bool = false var is_stopped: bool = false
var is_cleared: bool = false var is_cleared: bool = false
+66
View File
@@ -0,0 +1,66 @@
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)
@@ -0,0 +1 @@
uid://bm3xnmft2e4ul