càrrega de recursos no bloquejant

This commit is contained in:
2026-04-14 18:10:28 +02:00
parent d493ebf4f0
commit 8706b2c7fb
15 changed files with 356 additions and 118 deletions

View File

@@ -217,4 +217,5 @@ namespace Defaults::Settings {
namespace Defaults::Loading {
constexpr bool SHOW = false;
constexpr bool SHOW_RESOURCE_NAME = true;
constexpr bool WAIT_FOR_INPUT = false;
} // namespace Defaults::Loading

View File

@@ -32,6 +32,7 @@
#include "game/scenes/instructions.hpp" // Para Instructions
#include "game/scenes/intro.hpp" // Para Intro
#include "game/scenes/logo.hpp" // Para Logo
#include "game/scenes/preload.hpp" // Para Preload
#include "game/scenes/title.hpp" // Para Title
#include "game/ui/notifier.hpp" // Para Notifier
#include "game/ui/service_menu.hpp" // Para ServiceMenu
@@ -140,9 +141,40 @@ void Director::init() {
#else
Resource::init(Resource::LoadingMode::PRELOAD);
#endif
if (Resource::get()->getLoadingMode() == Resource::LoadingMode::PRELOAD) {
// Guarda la sección destino (la que fijó loadDebugConfig o el default)
// y redirige el arranque a la escena PRELOAD hasta que loadStep termine.
Section::post_preload = Section::name;
Section::name = Section::Name::PRELOAD;
Resource::get()->beginLoad();
} else {
// LAZY_LOAD: el constructor de Resource ya cargó lo esencial síncronamente;
// no hay fase de preload, pasamos directamente a post-boot.
finishBoot();
boot_loading_ = false;
}
// ServiceMenu/Notifier/getSingletons se mueven a finishBoot() — dependen
// de Resource y se inicializan al terminar la carga incremental.
}
// Inicializaciones que dependen del Resource cargado. Se llama desde iterate()
// cuando Resource::loadStep() devuelve true, con la ventana y el bucle vivos.
void Director::finishBoot() {
ServiceMenu::init(); // Inicializa el menú de servicio
Notifier::init(std::string(), Resource::get()->getText("8bithud")); // Inicialización del sistema de notificaciones
Screen::get()->getSingletons(); // Obtiene los punteros al resto de singletones
// Restaura el vsync a la preferencia del usuario (beginLoad lo había puesto a false)
Screen::get()->setVSync(Options::video.vsync);
// Si NO estamos en modo "wait for input", transiciona ya al destino.
// Si wait_for_input está activo (y la pantalla es visible), nos quedamos
// en PRELOAD hasta que el usuario pulse tecla/botón.
if (!(Options::loading.show && Options::loading.wait_for_input)) {
Section::name = Section::post_preload;
}
}
// Cierra todo y libera recursos del sistema y de los singletons
@@ -327,6 +359,7 @@ void Director::createSystemFolder(const std::string& folder) {
// Libera todos los unique_ptr de sección (solo uno tiene propiedad a la vez)
void Director::resetActiveSection() {
preload_.reset();
logo_.reset();
intro_.reset();
title_.reset();
@@ -353,6 +386,10 @@ void Director::handleSectionTransition() {
// Construye la nueva
switch (Section::name) {
case Section::Name::PRELOAD:
preload_ = std::make_unique<Preload>();
break;
case Section::Name::LOGO:
logo_ = std::make_unique<Logo>();
break;
@@ -435,20 +472,32 @@ auto Director::iterate() -> SDL_AppResult {
return SDL_APP_SUCCESS;
}
// En el primer frame, SDL ya ha drenado los SDL_EVENT_GAMEPAD_ADDED de los
// mandos conectados al iniciar. A partir de ahora los eventos sí son hotplug
// real y deben mostrar notificación.
static bool first_iterate = true;
if (first_iterate) {
first_iterate = false;
GlobalEvents::markStartupComplete();
// Fase de boot: carga incremental frame a frame con presupuesto de 50ms.
// Durante esta fase la escena activa es Preload (una barra de progreso).
if (boot_loading_) {
try {
if (Resource::get()->loadStep(50 /*ms*/)) {
finishBoot();
boot_loading_ = false;
// Los SDL_EVENT_GAMEPAD_ADDED iniciales ya los ha drenado la rama
// durante la carga: marcamos startup completo ahora para que los
// ADDED/REMOVED posteriores sí generen notificación.
GlobalEvents::markStartupComplete();
}
} catch (const std::exception& e) {
std::cerr << "Fatal error during resource load: " << e.what() << '\n';
Section::name = Section::Name::QUIT;
return SDL_APP_FAILURE;
}
}
// Gestiona las transiciones entre secciones (destruye la anterior y construye la nueva)
handleSectionTransition();
// Ejecuta un frame de la sección activa
if (logo_) {
if (preload_) {
preload_->iterate();
} else if (logo_) {
logo_->iterate();
} else if (intro_) {
intro_->iterate();
@@ -473,7 +522,9 @@ auto Director::handleEvent(SDL_Event& event) -> SDL_AppResult {
GlobalEvents::handle(event);
// Reenvía a la sección activa
if (logo_) {
if (preload_) {
preload_->handleEvent(event);
} else if (logo_) {
logo_->handleEvent(event);
} else if (intro_) {
intro_->handleEvent(event);

View File

@@ -12,6 +12,7 @@ namespace Lang {
}
// Declaraciones adelantadas de las secciones
class Preload;
class Logo;
class Intro;
class Title;
@@ -54,6 +55,7 @@ class Director {
std::string system_folder_; // Carpeta del sistema para almacenar datos
// --- Sección activa (una y sólo una viva en cada momento) ---
std::unique_ptr<Preload> preload_;
std::unique_ptr<Logo> logo_;
std::unique_ptr<Intro> intro_;
std::unique_ptr<Title> title_;
@@ -63,8 +65,12 @@ class Director {
std::unique_ptr<Credits> credits_;
Section::Name last_built_section_name_ = Section::Name::RESET;
// --- Fase de arranque no bloqueante ---
bool boot_loading_ = true; // True mientras Resource::loadStep está cargando incremental
// --- Inicialización y cierre del sistema ---
void init(); // Inicializa la aplicación
void init(); // Inicializa la aplicación (pre-boot)
void finishBoot(); // Post-boot: inicializa lo que depende de recursos cargados
static void close(); // Cierra y libera recursos
// --- Configuración inicial ---

View File

@@ -36,7 +36,11 @@ namespace GlobalEvents {
// Reasignar siempre: tanto en arranque como en hotplug en caliente.
Options::gamepad_manager.assignAndLinkGamepads();
Options::gamepad_manager.resyncGamepadsWithPlayers();
ServiceMenu::get()->refresh();
// Durante el preload ServiceMenu aún no existe: solo refresca si está vivo.
if (ServiceMenu::get() != nullptr) {
ServiceMenu::get()->refresh();
}
if (startup_in_progress || message.empty()) {
return;
@@ -51,7 +55,11 @@ namespace GlobalEvents {
message.replace(pos, std::string(" DISCONNECTED").length(), " " + Lang::getText("[NOTIFICATIONS] DISCONNECTED"));
}
Notifier::get()->show({message});
// Notifier también puede no existir todavía si la notificación se
// disparase antes de finishBoot(). Protegido por si acaso.
if (Notifier::get() != nullptr) {
Notifier::get()->show({message});
}
}
void markStartupComplete() {
@@ -79,7 +87,10 @@ namespace GlobalEvents {
break;
}
ServiceMenu::get()->handleEvent(event);
// Durante el preload ServiceMenu aún no existe
if (ServiceMenu::get() != nullptr) {
ServiceMenu::get()->handleEvent(event);
}
Mouse::handleEvent(event);
handleInputEvents(event);
}

View File

@@ -10,6 +10,7 @@ namespace Section {
// --- Enumeraciones de secciones del programa ---
enum class Name {
RESET, // Inicialización
PRELOAD, // Carga incremental de recursos
LOGO, // Pantalla de logo
INTRO, // Introducción
TITLE, // Pantalla de título/menú principal
@@ -43,6 +44,7 @@ namespace Section {
// --- Variables globales de estado ---
inline Name name = Name::RESET;
inline Name post_preload = Name::LOGO; // Sección a la que transiciona PRELOAD al terminar
inline Options options = Options::NONE;
inline AttractMode attract_mode = AttractMode::TITLE_TO_DEMO;
} // namespace Section