diff --git a/CMakeLists.txt b/CMakeLists.txt index 3720cab..6aec897 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -104,6 +104,7 @@ set(APP_SOURCES source/game/scenes/instructions.cpp source/game/scenes/intro.cpp source/game/scenes/logo.cpp + source/game/scenes/preload.cpp source/game/scenes/title.cpp # --- game/ui --- diff --git a/data/lang/ba_BA.json b/data/lang/ba_BA.json index f71d1ea..4523241 100644 --- a/data/lang/ba_BA.json +++ b/data/lang/ba_BA.json @@ -72,6 +72,7 @@ "[NOTIFICATIONS] DISCONNECTED": "desconectat", "[RESOURCE] LOADING": "Carregant", + "[RESOURCE] PRESS_TO_CONTINUE": "Prem una tecla per continuar", "[SERVICE_MENU] TITLE": "Menu de servei", "[SERVICE_MENU] RESET": "Reiniciar", diff --git a/data/lang/en_UK.json b/data/lang/en_UK.json index da5be5e..85d7d2c 100644 --- a/data/lang/en_UK.json +++ b/data/lang/en_UK.json @@ -71,6 +71,7 @@ "[NOTIFICATIONS] DISCONNECTED": "disconnected", "[RESOURCE] LOADING": "Loading", + "[RESOURCE] PRESS_TO_CONTINUE": "Press any key to continue", "[SERVICE_MENU] TITLE": "Service Menu", "[SERVICE_MENU] RESET": "Reset", diff --git a/data/lang/es_ES.json b/data/lang/es_ES.json index eeb2fd8..dc2236c 100644 --- a/data/lang/es_ES.json +++ b/data/lang/es_ES.json @@ -71,6 +71,7 @@ "[NOTIFICATIONS] DISCONNECTED": "desconectado", "[RESOURCE] LOADING": "Cargando", + "[RESOURCE] PRESS_TO_CONTINUE": "Pulsa una tecla para continuar", "[SERVICE_MENU] TITLE": "Menu de servicio", "[SERVICE_MENU] RESET": "Reiniciar", diff --git a/source/core/resources/resource.cpp b/source/core/resources/resource.cpp index fad56c3..ad90f51 100644 --- a/source/core/resources/resource.cpp +++ b/source/core/resources/resource.cpp @@ -67,7 +67,8 @@ Resource::Resource(LoadingMode mode) Screen::get()->show(); if (loading_mode_ == LoadingMode::PRELOAD) { loading_text_ = Screen::get()->getText(); - load(); + // Ya NO llamamos load() aquí: Director bombea beginLoad() + loadStep() + // desde iterate() para mantener vivo el bucle SDL3 durante la carga. } else { // En modo lazy, cargamos lo mínimo indispensable initResourceLists(); @@ -413,31 +414,128 @@ void Resource::clear() { demos_.clear(); } -// Carga todos los recursos del juego y muestra el progreso de carga +// Carga síncrona completa: usado por Resource::reload() (hot-reload en debug). +// En arranque normal la carga la bombea Director::iterate() vía loadStep(). void Resource::load() { - // Prepara la gestión del progreso de carga + beginLoad(); + while (!loadStep(INT_MAX)) { + // Presupuesto infinito: una sola pasada carga todo + } + Screen::get()->setVSync(saved_vsync_); +} + +// Prepara el estado del cargador incremental. No carga nada todavía. +void Resource::beginLoad() { calculateTotalResources(); initProgressBar(); - // Muerstra la ventana y desactiva el sincronismo vertical - auto* screen = Screen::get(); - auto vsync = Screen::getVSync(); - screen->setVSync(false); + saved_vsync_ = Screen::getVSync(); + Screen::get()->setVSync(false); // Maximiza FPS durante el preload - // SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "\n** LOADING RESOURCES"); - loadSounds(); // Carga sonidos - loadMusics(); // Carga músicas - loadTextures(); // Carga texturas - loadTextFiles(); // Carga ficheros de texto - loadAnimations(); // Carga animaciones - loadDemoData(); // Carga datos de demo - createText(); // Crea objetos de texto - createTextTextures(); // Crea texturas a partir de texto - createPlayerTextures(); // Crea las texturas de jugadores con todas sus variantes de paleta - // SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "\n** RESOURCES LOADED"); + stage_ = LoadStage::SOUNDS; + stage_index_ = 0; +} - // Restablece el sincronismo vertical a su valor original - screen->setVSync(vsync); +auto Resource::isLoadDone() const -> bool { + return stage_ == LoadStage::DONE; +} + +// Bombea la máquina de etapas hasta agotar el presupuesto de tiempo o completar la carga. +// Devuelve true cuando ya no queda nada por cargar. +auto Resource::loadStep(int budget_ms) -> bool { + if (stage_ == LoadStage::DONE) { return true; } + + const Uint64 start_ns = SDL_GetTicksNS(); + const Uint64 budget_ns = static_cast(budget_ms) * 1'000'000ULL; + + while (stage_ != LoadStage::DONE) { + switch (stage_) { + case LoadStage::SOUNDS: { + auto list = Asset::get()->getListByType(Asset::Type::SOUND); + if (stage_index_ == 0) { sounds_.clear(); } + if (stage_index_ >= list.size()) { + stage_ = LoadStage::MUSICS; + stage_index_ = 0; + break; + } + loadOneSound(stage_index_++); + break; + } + case LoadStage::MUSICS: { + auto list = Asset::get()->getListByType(Asset::Type::MUSIC); + if (stage_index_ == 0) { musics_.clear(); } + if (stage_index_ >= list.size()) { + stage_ = LoadStage::TEXTURES; + stage_index_ = 0; + break; + } + loadOneMusic(stage_index_++); + break; + } + case LoadStage::TEXTURES: { + auto list = Asset::get()->getListByType(Asset::Type::BITMAP); + if (stage_index_ == 0) { textures_.clear(); } + if (stage_index_ >= list.size()) { + stage_ = LoadStage::TEXT_FILES; + stage_index_ = 0; + break; + } + loadOneTexture(stage_index_++); + break; + } + case LoadStage::TEXT_FILES: { + auto list = Asset::get()->getListByType(Asset::Type::FONT); + if (stage_index_ == 0) { text_files_.clear(); } + if (stage_index_ >= list.size()) { + stage_ = LoadStage::ANIMATIONS; + stage_index_ = 0; + break; + } + loadOneTextFile(stage_index_++); + break; + } + case LoadStage::ANIMATIONS: { + auto list = Asset::get()->getListByType(Asset::Type::ANIMATION); + if (stage_index_ == 0) { animations_.clear(); } + if (stage_index_ >= list.size()) { + stage_ = LoadStage::DEMO_DATA; + stage_index_ = 0; + break; + } + loadOneAnimation(stage_index_++); + break; + } + case LoadStage::DEMO_DATA: { + auto list = Asset::get()->getListByType(Asset::Type::DEMODATA); + if (stage_index_ == 0) { demos_.clear(); } + if (stage_index_ >= list.size()) { + stage_ = LoadStage::CREATE_TEXT; + stage_index_ = 0; + break; + } + loadOneDemoData(stage_index_++); + break; + } + case LoadStage::CREATE_TEXT: + createText(); + stage_ = LoadStage::CREATE_TEXT_TEXTURES; + break; + case LoadStage::CREATE_TEXT_TEXTURES: + createTextTextures(); + stage_ = LoadStage::CREATE_PLAYER_TEXTURES; + break; + case LoadStage::CREATE_PLAYER_TEXTURES: + createPlayerTextures(); + stage_ = LoadStage::DONE; + break; + case LoadStage::DONE: + break; + } + + if ((SDL_GetTicksNS() - start_ns) >= budget_ns) { break; } + } + + return stage_ == LoadStage::DONE; } // Recarga todos los recursos (limpia y vuelve a cargar) @@ -450,96 +548,78 @@ void Resource::reload() { } } -// Carga los sonidos del juego -void Resource::loadSounds() { +// Carga un sonido concreto desde la lista de assets +void Resource::loadOneSound(size_t idx) { auto list = Asset::get()->getListByType(Asset::Type::SOUND); - sounds_.clear(); - - for (const auto& l : list) { - auto name = getFileName(l); - updateLoadingProgress(name); - auto audio_data = loadAudioData(l); - JA_Sound_t* sound = nullptr; - if (!audio_data.data.empty()) { - sound = JA_LoadSound(audio_data.data.data(), audio_data.data.size()); - } else { - sound = JA_LoadSound(l.c_str()); - } - if (sound == nullptr) { - std::cout << "Sound load failed: " << name << '\n'; - } - sounds_.emplace_back(name, sound); + const auto& path = list[idx]; + auto name = getFileName(path); + updateLoadingProgress(name); + auto audio_data = loadAudioData(path); + JA_Sound_t* sound = nullptr; + if (!audio_data.data.empty()) { + sound = JA_LoadSound(audio_data.data.data(), audio_data.data.size()); + } else { + sound = JA_LoadSound(path.c_str()); } + if (sound == nullptr) { + std::cout << "Sound load failed: " << name << '\n'; + } + sounds_.emplace_back(name, sound); } -// Carga las músicas del juego -void Resource::loadMusics() { +// Carga una música concreta desde la lista de assets +void Resource::loadOneMusic(size_t idx) { auto list = Asset::get()->getListByType(Asset::Type::MUSIC); - musics_.clear(); - - for (const auto& l : list) { - auto name = getFileName(l); - updateLoadingProgress(name); - auto audio_data = loadAudioData(l); - JA_Music_t* music = nullptr; - if (!audio_data.data.empty()) { - music = JA_LoadMusic(audio_data.data.data(), audio_data.data.size()); - } else { - music = JA_LoadMusic(l.c_str()); - } - if (music == nullptr) { - std::cout << "Music load failed: " << name << '\n'; - } - musics_.emplace_back(name, music); + const auto& path = list[idx]; + auto name = getFileName(path); + updateLoadingProgress(name); + auto audio_data = loadAudioData(path); + JA_Music_t* music = nullptr; + if (!audio_data.data.empty()) { + music = JA_LoadMusic(audio_data.data.data(), audio_data.data.size()); + } else { + music = JA_LoadMusic(path.c_str()); } + if (music == nullptr) { + std::cout << "Music load failed: " << name << '\n'; + } + musics_.emplace_back(name, music); } -// Carga las texturas del juego -void Resource::loadTextures() { +// Carga una textura concreta desde la lista de assets +void Resource::loadOneTexture(size_t idx) { auto list = Asset::get()->getListByType(Asset::Type::BITMAP); - textures_.clear(); - - for (const auto& l : list) { - auto name = getFileName(l); - updateLoadingProgress(name); - textures_.emplace_back(name, std::make_shared(Screen::get()->getRenderer(), l)); - } + const auto& path = list[idx]; + auto name = getFileName(path); + updateLoadingProgress(name); + textures_.emplace_back(name, std::make_shared(Screen::get()->getRenderer(), path)); } -// Carga los ficheros de texto del juego -void Resource::loadTextFiles() { +// Carga un fichero de texto concreto desde la lista de assets +void Resource::loadOneTextFile(size_t idx) { auto list = Asset::get()->getListByType(Asset::Type::FONT); - text_files_.clear(); - - for (const auto& l : list) { - auto name = getFileName(l); - updateLoadingProgress(name); - text_files_.emplace_back(name, Text::loadFile(l)); - } + const auto& path = list[idx]; + auto name = getFileName(path); + updateLoadingProgress(name); + text_files_.emplace_back(name, Text::loadFile(path)); } -// Carga las animaciones del juego -void Resource::loadAnimations() { +// Carga una animación concreta desde la lista de assets +void Resource::loadOneAnimation(size_t idx) { auto list = Asset::get()->getListByType(Asset::Type::ANIMATION); - animations_.clear(); - - for (const auto& l : list) { - auto name = getFileName(l); - updateLoadingProgress(name); - animations_.emplace_back(name, loadAnimationsFromFile(l)); - } + const auto& path = list[idx]; + auto name = getFileName(path); + updateLoadingProgress(name); + animations_.emplace_back(name, loadAnimationsFromFile(path)); } -// Carga los datos para el modo demostración -void Resource::loadDemoData() { +// Carga un fichero de datos de demo concreto desde la lista de assets +void Resource::loadOneDemoData(size_t idx) { auto list = Asset::get()->getListByType(Asset::Type::DEMODATA); - demos_.clear(); - - for (const auto& l : list) { - auto name = getFileName(l); - updateLoadingProgress(name); - demos_.emplace_back(loadDemoDataFromFile(l)); - } + const auto& path = list[idx]; + auto name = getFileName(path); + updateLoadingProgress(name); + demos_.emplace_back(loadDemoDataFromFile(path)); } // Crea las texturas de jugadores con todas sus variantes de paleta @@ -756,12 +836,16 @@ void Resource::renderProgress() { screen->start(); screen->clean(); - // Si la pantalla de carga está desactivada, dejamos todo en negro + // Si la pantalla de carga está desactivada, dejamos todo en negro. + // wait_for_input solo tiene efecto cuando la pantalla está visible. if (!Options::loading.show) { screen->coreRender(); return; } + // Estamos en la fase de espera explícita al usuario tras terminar la carga + const bool WAITING_FOR_INPUT = isLoadDone() && Options::loading.wait_for_input; + auto text_color = param.resource.color; auto bar_color = param.resource.color.DARKEN(100); const auto TEXT_HEIGHT = loading_text_->getCharacterSize(); @@ -774,13 +858,17 @@ void Resource::renderProgress() { SDL_SetRenderDrawColor(renderer, bar_color.r, bar_color.g, bar_color.b, bar_color.a); SDL_RenderRect(renderer, &loading_wired_rect_); - // Escribe el nombre del recurso que se está cargando, centrado sobre la barra - if (Options::loading.show_resource_name && !loading_resource_name_.empty()) { + // Texto centrado sobre la barra: mientras carga, el nombre del recurso; + // al terminar en modo wait_for_input, el prompt traducido. + const std::string OVER_BAR_TEXT = WAITING_FOR_INPUT + ? Lang::getText("[RESOURCE] PRESS_TO_CONTINUE") + : loading_resource_name_; + if ((Options::loading.show_resource_name || WAITING_FOR_INPUT) && !OVER_BAR_TEXT.empty()) { loading_text_->writeDX( Text::CENTER | Text::COLOR, loading_wired_rect_.x + (loading_wired_rect_.w / 2), loading_wired_rect_.y - TEXT_HEIGHT - 2, - loading_resource_name_, + OVER_BAR_TEXT, 1, text_color); } @@ -829,12 +917,12 @@ void Resource::initProgressBar() { loading_full_rect_ = {.x = BAR_X_POSITION, .y = BAR_Y_POSITION, .w = FULL_BAR_WIDTH, .h = BAR_HEIGHT}; } -// Actualiza el progreso de carga y muestra la barra +// Actualiza el estado del progreso. No renderiza: el repintado lo hace +// Preload::iterate una vez por frame llamando a renderProgress(). void Resource::updateLoadingProgress(std::string name) { loading_resource_name_ = std::move(name); loading_count_.increase(); updateProgressBar(); - renderProgress(); } // Actualiza la barra de estado diff --git a/source/core/resources/resource.hpp b/source/core/resources/resource.hpp index eaab821..d66cbbf 100644 --- a/source/core/resources/resource.hpp +++ b/source/core/resources/resource.hpp @@ -42,6 +42,15 @@ class Resource { // --- Métodos de recarga de recursos --- void reload(); // Recarga todos los recursos + // --- Cargador incremental --- + // beginLoad prepara el estado; loadStep carga recursos hasta agotar el presupuesto; + // devuelve true cuando ya no queda nada. renderProgress se llama una vez por frame + // desde la escena Preload. + void beginLoad(); + auto loadStep(int budget_ms) -> bool; + [[nodiscard]] auto isLoadDone() const -> bool; + void renderProgress(); + // --- Método para obtener el modo de carga actual --- [[nodiscard]] auto getLoadingMode() const -> LoadingMode { return loading_mode_; } @@ -146,13 +155,24 @@ class Resource { SDL_FRect loading_wired_rect_; SDL_FRect loading_full_rect_; + // --- Estado del cargador incremental --- + enum class LoadStage { + SOUNDS, + MUSICS, + TEXTURES, + TEXT_FILES, + ANIMATIONS, + DEMO_DATA, + CREATE_TEXT, + CREATE_TEXT_TEXTURES, + CREATE_PLAYER_TEXTURES, + DONE + }; + LoadStage stage_{LoadStage::DONE}; + size_t stage_index_{0}; + bool saved_vsync_{false}; // Vsync previo a beginLoad, restaurado por finishBoot/load + // --- Métodos internos de carga y gestión --- - void loadSounds(); // Carga los sonidos - void loadMusics(); // Carga las músicas - void loadTextures(); // Carga las texturas - void loadTextFiles(); // Carga los ficheros de texto - void loadAnimations(); // Carga las animaciones - void loadDemoData(); // Carga los datos para el modo demostración void loadDemoDataQuiet(); // Carga los datos de demo sin mostrar progreso (para modo lazy) void loadEssentialResources(); // Carga recursos esenciales en modo lazy void loadEssentialTextures(); // Carga solo las texturas esenciales (fuentes) @@ -176,11 +196,18 @@ class Resource { // --- Métodos internos para gestionar el progreso --- void calculateTotalResources(); // Calcula el número de recursos para cargar - void renderProgress(); // Muestra el progreso de carga void updateLoadingProgress(std::string name); // Actualiza el progreso de carga void initProgressBar(); // Inicializa los rectangulos que definen la barra de progreso void updateProgressBar(); // Actualiza la barra de estado + // --- Helpers del cargador incremental (cargan un único recurso) --- + void loadOneSound(size_t idx); + void loadOneMusic(size_t idx); + void loadOneTexture(size_t idx); + void loadOneTextFile(size_t idx); + void loadOneAnimation(size_t idx); + void loadOneDemoData(size_t idx); + // --- Constructores y destructor privados (singleton) --- explicit Resource(LoadingMode mode); // Constructor privado con modo de carga ~Resource(); // Destructor privado diff --git a/source/core/system/defaults.hpp b/source/core/system/defaults.hpp index 76619b6..2504f25 100644 --- a/source/core/system/defaults.hpp +++ b/source/core/system/defaults.hpp @@ -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 diff --git a/source/core/system/director.cpp b/source/core/system/director.cpp index e724a9e..b71ea11 100644 --- a/source/core/system/director.cpp +++ b/source/core/system/director.cpp @@ -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(); + break; + case Section::Name::LOGO: logo_ = std::make_unique(); 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); diff --git a/source/core/system/director.hpp b/source/core/system/director.hpp index dd72d74..31e02ed 100644 --- a/source/core/system/director.hpp +++ b/source/core/system/director.hpp @@ -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_; std::unique_ptr logo_; std::unique_ptr intro_; std::unique_ptr 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 --- diff --git a/source/core/system/global_events.cpp b/source/core/system/global_events.cpp index 7ae28c3..8092eee 100644 --- a/source/core/system/global_events.cpp +++ b/source/core/system/global_events.cpp @@ -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); } diff --git a/source/core/system/section.hpp b/source/core/system/section.hpp index 51fe16e..158c9be 100644 --- a/source/core/system/section.hpp +++ b/source/core/system/section.hpp @@ -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 \ No newline at end of file diff --git a/source/game/options.cpp b/source/game/options.cpp index 3c539c5..084e372 100644 --- a/source/game/options.cpp +++ b/source/game/options.cpp @@ -495,6 +495,7 @@ namespace Options { const auto& ld = yaml["loading"]; parseBoolField(ld, "show", loading.show); parseBoolField(ld, "show_resource_name", loading.show_resource_name); + parseBoolField(ld, "wait_for_input", loading.wait_for_input); } void loadGameFromYaml(const fkyaml::node& yaml) { @@ -696,6 +697,7 @@ namespace Options { file << "loading:\n"; file << " show: " << boolToString(loading.show) << "\n"; file << " show_resource_name: " << boolToString(loading.show_resource_name) << "\n"; + file << " wait_for_input: " << boolToString(loading.wait_for_input) << "\n"; file << "\n"; // GAME diff --git a/source/game/options.hpp b/source/game/options.hpp index c53c1a1..804330a 100644 --- a/source/game/options.hpp +++ b/source/game/options.hpp @@ -112,6 +112,7 @@ namespace Options { struct Loading { bool show = Defaults::Loading::SHOW; // Muestra la pantalla de carga (si no, pantalla en negro) bool show_resource_name = Defaults::Loading::SHOW_RESOURCE_NAME; // Muestra el nombre del recurso en curso sobre la barra de progreso + bool wait_for_input = Defaults::Loading::WAIT_FOR_INPUT; // Al terminar la carga, espera a que el usuario pulse una tecla/botón (solo si show=true) }; struct Settings { diff --git a/source/game/scenes/preload.cpp b/source/game/scenes/preload.cpp new file mode 100644 index 0000000..0f378ff --- /dev/null +++ b/source/game/scenes/preload.cpp @@ -0,0 +1,26 @@ +#include "game/scenes/preload.hpp" + +#include "core/resources/resource.hpp" // Para Resource +#include "core/system/section.hpp" // Para Section +#include "game/options.hpp" // Para Options::loading + +void Preload::iterate() { + // El repintado es independiente del avance de la carga: Director::iterate + // ya ha bombeado Resource::loadStep() antes de llamar aquí. + Resource::get()->renderProgress(); +} + +void Preload::handleEvent(const SDL_Event& event) { + // Solo aceptamos input si la carga ha terminado y estamos en modo + // wait_for_input (que además exige que la pantalla de carga sea visible). + if (!Resource::get()->isLoadDone() || !Options::loading.wait_for_input || !Options::loading.show) { + return; + } + + // Pulsación limpia: ignoramos auto-repeat y eventos de hotplug de mando. + const bool IS_KEY = event.type == SDL_EVENT_KEY_DOWN && !event.key.repeat; + const bool IS_BUTTON = event.type == SDL_EVENT_GAMEPAD_BUTTON_DOWN; + if (IS_KEY || IS_BUTTON) { + Section::name = Section::post_preload; + } +} diff --git a/source/game/scenes/preload.hpp b/source/game/scenes/preload.hpp new file mode 100644 index 0000000..86b5afa --- /dev/null +++ b/source/game/scenes/preload.hpp @@ -0,0 +1,19 @@ +#pragma once + +#include <SDL3/SDL.h> // Para SDL_Event + +// --- Clase Preload: escena mínima durante la carga incremental de recursos --- +// +// No avanza la carga — de eso se encarga Director::iterate() llamando a +// Resource::loadStep(budget_ms) antes de despachar la escena. Aquí solo se +// repinta la barra de progreso y, si Options::loading.wait_for_input está +// activo, se detecta la pulsación que transiciona a la siguiente sección. +class Preload { + public: + Preload() = default; + ~Preload() = default; + + // --- Callbacks para el bucle SDL_MAIN_USE_CALLBACKS --- + void iterate(); // Repinta la barra de progreso + void handleEvent(const SDL_Event& event); // Detecta pulsación en modo wait_for_input +};