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"
|
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
@@ -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:
|
||||||
|
|||||||
@@ -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,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()
|
||||||
|
|||||||
@@ -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:
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
Reference in New Issue
Block a user