Merge pull request #327 from Excello-Recherche-Education/optimization
Optimization global
This commit is contained in:
@@ -36,6 +36,7 @@ Globals="*res://sources/language_tool/globals.gd"
|
||||
LessonLogger="*res://sources/utils/autoloads/lesson_logger.gd"
|
||||
MusicManager="*res://sources/utils/autoloads/music_manager.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"
|
||||
UnicodeNormalizer="*res://sources/utils/autoloads/unicode_normalizer.gd"
|
||||
UserDataManager="*res://sources/utils/autoloads/user_data_manager.gd"
|
||||
|
||||
+32
-35
@@ -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
|
||||
# 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 GARDEN_SCENES: Array[PackedScene] = [
|
||||
preload("res://resources/gardens/garden_01.tscn"),
|
||||
preload("res://resources/gardens/garden_02.tscn"),
|
||||
preload("res://resources/gardens/garden_03.tscn"),
|
||||
preload("res://resources/gardens/garden_04.tscn"),
|
||||
preload("res://resources/gardens/garden_05.tscn"),
|
||||
preload("res://resources/gardens/garden_06.tscn"),
|
||||
preload("res://resources/gardens/garden_07.tscn"),
|
||||
preload("res://resources/gardens/garden_08.tscn"),
|
||||
preload("res://resources/gardens/garden_09.tscn"),
|
||||
preload("res://resources/gardens/garden_10.tscn"),
|
||||
preload("res://resources/gardens/garden_11.tscn"),
|
||||
preload("res://resources/gardens/garden_12.tscn"),
|
||||
# Loaded on demand instead of preloaded: this script never unloads (it has
|
||||
# static variables), so preloaded constants would pin every garden's assets in
|
||||
# memory for the whole app lifetime — including while minigames run, which
|
||||
# OOM-crashes low-memory devices.
|
||||
const GARDEN_SCENE_PATHS: Array[String] = [
|
||||
"res://resources/gardens/garden_01.tscn",
|
||||
"res://resources/gardens/garden_02.tscn",
|
||||
"res://resources/gardens/garden_03.tscn",
|
||||
"res://resources/gardens/garden_04.tscn",
|
||||
"res://resources/gardens/garden_05.tscn",
|
||||
"res://resources/gardens/garden_06.tscn",
|
||||
"res://resources/gardens/garden_07.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_MINIGAME_SCENE_PATH: String = "res://sources/minigames/boss/boss_minigame.tscn"
|
||||
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_face_icons: Array[Texture] = []
|
||||
|
||||
var _minigame_scene_cache: Dictionary = {}
|
||||
var lessons: Dictionary = {}
|
||||
var _gardens_layout: GardensLayout
|
||||
var points: Array[Array] = []
|
||||
@@ -988,20 +991,13 @@ func _lesson_button_label_color(status: StudentProgression.Status) -> 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():
|
||||
return null
|
||||
if _minigame_scene_cache.has(scene_index):
|
||||
return _minigame_scene_cache[scene_index] as PackedScene
|
||||
return ""
|
||||
var scene_path: String = minigame_scene_paths[scene_index]
|
||||
if scene_path.is_empty():
|
||||
return null
|
||||
var scene_resource: Resource = load(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
|
||||
if scene_path.is_empty() or not ResourceLoader.exists(scene_path):
|
||||
return ""
|
||||
return scene_path
|
||||
|
||||
|
||||
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()):
|
||||
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: 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.garden_index = garden_index
|
||||
garden_index += 1
|
||||
@@ -1422,7 +1419,7 @@ func _on_lesson_button_pressed() -> void:
|
||||
current_garden_index = current_garden.garden_index,
|
||||
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:
|
||||
@@ -1438,7 +1435,7 @@ func _on_boss_button_pressed(lesson_number: int, garden_index: int) -> void:
|
||||
skip_minigame_layout = true,
|
||||
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:
|
||||
@@ -1455,14 +1452,14 @@ func _on_final_boss_button_pressed(lesson_number: int, garden_index: int) -> voi
|
||||
boss_gate_lesson = lesson_number,
|
||||
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:
|
||||
if is_locked:
|
||||
return
|
||||
var minigame_scene: PackedScene = _get_minigame_scene(scene_index)
|
||||
if not minigame_scene:
|
||||
var scene_path: String = _get_minigame_scene_path(scene_index)
|
||||
if scene_path.is_empty():
|
||||
Log.error("Gardens: Missing minigame scene for index %d" % scene_index)
|
||||
return
|
||||
# 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_completed = false
|
||||
}
|
||||
get_tree().change_scene_to_packed(minigame_scene)
|
||||
SceneLoader.change_scene(scene_path)
|
||||
|
||||
|
||||
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:
|
||||
UserDataManager.logout_student()
|
||||
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:
|
||||
|
||||
@@ -160,7 +160,7 @@ func _back_to_gardens() -> void:
|
||||
Log.info("LookAndLearn: Returning to gardens for lesson %d" % lesson_nb)
|
||||
await (OpeningCurtain as OpeningCurtainClass).close()
|
||||
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:
|
||||
|
||||
@@ -2,7 +2,7 @@ extends Control
|
||||
|
||||
const TEACHER_PASSWORD: String = "42"
|
||||
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 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"
|
||||
@@ -73,8 +73,7 @@ func _on_code_keyboard_password_entered(password: String) -> void:
|
||||
await kalulu.play_kalulu_speech(right_password_speech)
|
||||
await OpeningCurtain.close()
|
||||
Log.trace("LoginScreen: Start loading next scene")
|
||||
get_tree().change_scene_to_packed(NEXT_SCENE)
|
||||
Log.trace("LoginScreen: End loading next scene")
|
||||
SceneLoader.change_scene(NEXT_SCENE_PATH)
|
||||
else:
|
||||
Log.warn("LoginScreen: Unknown student code entered (length=%d)" % password.length())
|
||||
kalulu_button.hide()
|
||||
|
||||
@@ -435,7 +435,7 @@ func _go_back_to_the_garden() -> void:
|
||||
_save_logs()
|
||||
|
||||
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:
|
||||
|
||||
@@ -5,8 +5,7 @@ signal lilypad_in_center(lilypad: Lilypad)
|
||||
|
||||
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 is_stopped: bool = false
|
||||
var is_cleared: bool = false
|
||||
|
||||
@@ -0,0 +1,70 @@
|
||||
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
|
||||
error = tree.change_scene_to_packed(packed_scene)
|
||||
if error != OK:
|
||||
Log.error("SceneLoader: Could not change to %s (error %s)" % [scene_path, error_string(error)])
|
||||
_finish_with_fallback(tree, scene_path)
|
||||
return
|
||||
_is_changing = false
|
||||
|
||||
|
||||
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
|
||||
Reference in New Issue
Block a user