From 8bb052981dc1de5767dfa202daf928fa225fb43f Mon Sep 17 00:00:00 2001 From: Sergio Valor Date: Fri, 22 May 2026 12:35:19 +0200 Subject: [PATCH 1/4] refactor(director): locals de run() a membres unique_ptr MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Preparació per a SDL_MAIN_USE_CALLBACKS: SDLManager, SceneContext, DebugOverlay i l'escena actual ja viuen com a membres del Director. El flux de run() és idèntic; només canvia el storage. --- source/core/system/director.cpp | 34 ++++++++++++++++++++++----------- source/core/system/director.hpp | 9 +++++++++ 2 files changed, 32 insertions(+), 11 deletions(-) diff --git a/source/core/system/director.cpp b/source/core/system/director.cpp index 4eea305..b7b9420 100644 --- a/source/core/system/director.cpp +++ b/source/core/system/director.cpp @@ -122,6 +122,15 @@ Director::~Director() { // Guardar opciones persistence_.save(); + // Destruir subsistemes en ordre invers a la construcció. Crític: el + // renderer i la finestra (dins de sdl_) han de morir abans de cridar + // SDL_Quit(). Si fossin destruïts pel destructor implícit del Director + // morirïen DESPRÉS dels Input/Audio/SDL_Quit que vénen a sota. + current_scene_.reset(); + debug_overlay_.reset(); + context_.reset(); + sdl_.reset(); + // Cleanup input Input::destroy(); @@ -227,7 +236,7 @@ auto Director::run() -> int { // Crear gestor SDL amb la engine_config + callback de persistència // per a quan toggleVSync (F4) muti vsync. Mantenim sdl_manager agnòstic. - SDLManager sdl(initial_width, initial_height, cfg_->window.fullscreen, *cfg_, [this] { persistence_.save(); }); + sdl_ = std::make_unique(initial_width, initial_height, cfg_->window.fullscreen, *cfg_, [this] { persistence_.save(); }); // CRÍTIC: Forçar ocultació del cursor DESPRÉS de toda la inicialización SDL // Això evita que SDL mostre el cursor automàticament durante la creació de la finestra @@ -255,30 +264,33 @@ auto Director::run() -> int { } // Crear context de escenes - SceneContext context; + context_ = std::make_unique(); #ifdef _DEBUG - context.setNextScene(SceneType::TITLE); + context_->setNextScene(SceneType::TITLE); #else - context.setNextScene(SceneType::LOGO); + context_->setNextScene(SceneType::LOGO); #endif // Overlay de debug (FPS + VSync). Vive en el Director porque es global // a todas las escenas. Toggle con F11 (visible por defecto en _DEBUG). - System::DebugOverlay debug_overlay(sdl.getRenderer(), cfg_->rendering); + debug_overlay_ = std::make_unique( + sdl_->getRenderer(), + cfg_->rendering); // Sistema de notificacions toast: singleton accessible des d'on calgui // (F1-F5 a sdl_manager, ESC a global_events). El renderer ha de viure // tant com el Notifier; el destruim explícitament abans de tornar. - System::Notifier::init(sdl.getRenderer()); + System::Notifier::init(sdl_->getRenderer()); // Bucle principal: construir escena → frame loop → destruir → siguiente. - while (context.nextScene() != SceneType::EXIT) { - SceneManager::actual = context.nextScene(); - std::unique_ptr scene = buildScene(context.nextScene(), sdl, context); - if (!scene) { + while (context_->nextScene() != SceneType::EXIT) { + SceneManager::actual = context_->nextScene(); + current_scene_ = buildScene(context_->nextScene(), *sdl_, *context_); + if (!current_scene_) { break; } - runFrameLoop(*scene, sdl, context, debug_overlay); + runFrameLoop(*current_scene_, *sdl_, *context_, *debug_overlay_); + current_scene_.reset(); } SceneManager::actual = SceneType::EXIT; diff --git a/source/core/system/director.hpp b/source/core/system/director.hpp index 712202a..206722a 100644 --- a/source/core/system/director.hpp +++ b/source/core/system/director.hpp @@ -32,6 +32,15 @@ class Director { Config::EngineConfig* cfg_; Config::ConfigPersistence persistence_; + // Subsistemes que viuen tant com el Director (abans eren locals de run()). + // Preparació per a la migració a SDL_MAIN_USE_CALLBACKS: amb les 4 + // callbacks de SDL3 no hi ha un scope que englobi tot el bucle, així + // que cal que aquest estat sigui membre del Director. + std::unique_ptr sdl_; + std::unique_ptr context_; + std::unique_ptr debug_overlay_; + std::unique_ptr current_scene_; + auto checkProgramArguments(std::vector const& args) -> std::string; void createSystemFolder(const std::string& folder); From 120b8ada3851156689b9fb2ba6948f0c5d338c22 Mon Sep 17 00:00:00 2001 From: Sergio Valor Date: Fri, 22 May 2026 12:38:16 +0200 Subject: [PATCH 2/4] refactor(director): extreu iterate/handleEvent/advanceScene del runFrameLoop MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit run() ara delega a iterate() i handleEvent() per cada frame. runFrameLoop desapareix; la seva lògica es divideix entre els tres nous mètodes. La primera escena es construeix lazy via advanceScene() dins d'iterate(). Cap canvi de comportament visible. --- source/core/system/director.cpp | 170 ++++++++++++++++++++------------ source/core/system/director.hpp | 23 ++++- 2 files changed, 126 insertions(+), 67 deletions(-) diff --git a/source/core/system/director.cpp b/source/core/system/director.cpp index b7b9420..cde1a13 100644 --- a/source/core/system/director.cpp +++ b/source/core/system/director.cpp @@ -282,20 +282,28 @@ auto Director::run() -> int { // tant com el Notifier; el destruim explícitament abans de tornar. System::Notifier::init(sdl_->getRenderer()); - // Bucle principal: construir escena → frame loop → destruir → siguiente. - while (context_->nextScene() != SceneType::EXIT) { - SceneManager::actual = context_->nextScene(); - current_scene_ = buildScene(context_->nextScene(), *sdl_, *context_); - if (!current_scene_) { - break; - } - runFrameLoop(*current_scene_, *sdl_, *context_, *debug_overlay_); - current_scene_.reset(); - } + // Comptador de delta time per al primer iterate(). + last_ticks_ms_ = SDL_GetTicks(); - SceneManager::actual = SceneType::EXIT; - System::Notifier::destroy(); - return 0; + // Bucle principal: poll d'events + iterate() per frame. La primera escena + // es construeix lazy dins d'iterate() via advanceScene(). En migrar a + // SDL_MAIN_USE_CALLBACKS, aquest while desapareixerà i SDL_AppEvent/ + // SDL_AppIterate cridaran handleEvent()/iterate() directament. + while (true) { + SDL_Event event; + while (SDL_PollEvent(&event)) { + SDL_AppResult r = handleEvent(event); + if (r != SDL_APP_CONTINUE) { + System::Notifier::destroy(); + return (r == SDL_APP_SUCCESS) ? 0 : 1; + } + } + SDL_AppResult r = iterate(); + if (r != SDL_APP_CONTINUE) { + System::Notifier::destroy(); + return (r == SDL_APP_SUCCESS) ? 0 : 1; + } + } } auto Director::buildScene(SceneType type, SDLManager& sdl, SceneContext& context) @@ -313,55 +321,91 @@ auto Director::buildScene(SceneType type, SDLManager& sdl, SceneContext& context } } -void Director::runFrameLoop(Scene& scene, SDLManager& sdl, SceneContext& context, System::DebugOverlay& debug_overlay) { - SDL_Event event; - Uint64 last_time = SDL_GetTicks(); - - while (!scene.isFinished()) { - // Delta time real, capeado a 50ms para evitar grandes saltos. - const Uint64 NOW = SDL_GetTicks(); - float delta_time = static_cast(NOW - last_time) / 1000.0F; - last_time = NOW; - delta_time = std::min(delta_time, 0.05F); - - Mouse::updateCursorVisibility(); - Input::get()->update(); - - // Event loop: primero ventana, después globales, después F11 - // (toggle del overlay), después escena. - while (SDL_PollEvent(&event)) { - if (sdl.handleWindowEvent(event)) { - continue; - } - if (GlobalEvents::handle(event, sdl, context)) { - continue; - } - if (event.type == SDL_EVENT_KEY_DOWN && event.key.scancode == SDL_SCANCODE_F11) { - debug_overlay.toggle(); - continue; - } - scene.handleEvent(event); - } - - scene.update(delta_time); - debug_overlay.update(delta_time); - if (auto* notifier = System::Notifier::get(); notifier != nullptr) { - notifier->update(delta_time); - } - Audio::update(); - - // Si la swapchain no está disponible (ventana minimizada, etc.), - // saltarse draw+present ese frame: dibujar dejaría vértices - // colgando en el batch interno sin nadie que los presente. - if (!sdl.clear(0, 0, 0)) { - continue; - } - sdl.updateRenderingContext(); - scene.draw(); - debug_overlay.draw(); // sempre per damunt de l'escena - if (const auto* notifier = System::Notifier::get(); notifier != nullptr) { - notifier->draw(); // toast: per damunt de tot - } - sdl.present(); +auto Director::advanceScene() -> SDL_AppResult { + current_scene_.reset(); + const SceneType NEXT = context_->nextScene(); + if (NEXT == SceneType::EXIT) { + SceneManager::actual = SceneType::EXIT; + return SDL_APP_SUCCESS; } + SceneManager::actual = NEXT; + current_scene_ = buildScene(NEXT, *sdl_, *context_); + if (!current_scene_) { + SceneManager::actual = SceneType::EXIT; + return SDL_APP_SUCCESS; + } + return SDL_APP_CONTINUE; +} + +auto Director::handleEvent(const SDL_Event& event) -> SDL_AppResult { + // 1. Window events (resize, minimize, focus...) + if (sdl_->handleWindowEvent(event)) { + return SDL_APP_CONTINUE; + } + + // 2. Events globals (F1-F6, ESC, QUIT, gamepad hotplug). + // GlobalEvents marca context_->nextScene() = EXIT en ESC doble o QUIT; + // activem la bandera per fer-ho fluir cap a SDL_APP_SUCCESS al pròxim tick. + if (GlobalEvents::handle(event, *sdl_, *context_)) { + if (context_->nextScene() == SceneType::EXIT) { + wants_quit_ = true; + } + return SDL_APP_CONTINUE; + } + + // 3. F11 → toggle del debug overlay (cas especial fora de GlobalEvents). + if (event.type == SDL_EVENT_KEY_DOWN && event.key.scancode == SDL_SCANCODE_F11) { + debug_overlay_->toggle(); + return SDL_APP_CONTINUE; + } + + // 4. Esdeveniment específic de l'escena actual. + if (current_scene_) { + current_scene_->handleEvent(event); + } + return SDL_APP_CONTINUE; +} + +auto Director::iterate() -> SDL_AppResult { + if (wants_quit_) { + return SDL_APP_SUCCESS; + } + + // Pivotar a la següent escena si l'actual ha acabat (o és la primera). + if (!current_scene_ || current_scene_->isFinished()) { + SDL_AppResult pivot = advanceScene(); + if (pivot != SDL_APP_CONTINUE) { + return pivot; + } + } + + // Delta time real, capeado a 50ms per evitar grans salts. + const Uint64 NOW = SDL_GetTicks(); + float delta_time = static_cast(NOW - last_ticks_ms_) / 1000.0F; + last_ticks_ms_ = NOW; + delta_time = std::min(delta_time, 0.05F); + + Mouse::updateCursorVisibility(); + Input::get()->update(); + + current_scene_->update(delta_time); + debug_overlay_->update(delta_time); + if (auto* notifier = System::Notifier::get(); notifier != nullptr) { + notifier->update(delta_time); + } + Audio::update(); + + // Si la swapchain no està disponible (finestra minimitzada, etc.), + // saltar-se draw+present aquest frame. + if (!sdl_->clear(0, 0, 0)) { + return SDL_APP_CONTINUE; + } + sdl_->updateRenderingContext(); + current_scene_->draw(); + debug_overlay_->draw(); // sempre per damunt de l'escena + if (const auto* notifier = System::Notifier::get(); notifier != nullptr) { + notifier->draw(); // toast: per damunt de tot + } + sdl_->present(); + return SDL_APP_CONTINUE; } diff --git a/source/core/system/director.hpp b/source/core/system/director.hpp index 206722a..158eb26 100644 --- a/source/core/system/director.hpp +++ b/source/core/system/director.hpp @@ -1,5 +1,7 @@ #pragma once +#include + #include #include #include @@ -26,6 +28,16 @@ class Director { // Bucle principal del juego. auto run() -> int; + // Una iteració del bucle: pivot d'escena si cal, delta time, update i + // render. Retorna SDL_APP_CONTINUE per seguir, SDL_APP_SUCCESS si vol + // sortir net, SDL_APP_FAILURE si no es pot recuperar. + auto iterate() -> SDL_AppResult; + + // Enruta un sol esdeveniment cap a la cadena finestra → globals → F11 → + // escena. Si detecta sortida (ESC doble, QUIT) marca wants_quit_ perquè + // el següent iterate() retorni SDL_APP_SUCCESS. + auto handleEvent(const SDL_Event& event) -> SDL_AppResult; + private: std::string executable_path_; std::string system_folder_; @@ -41,6 +53,9 @@ class Director { std::unique_ptr debug_overlay_; std::unique_ptr current_scene_; + Uint64 last_ticks_ms_{0}; + bool wants_quit_{false}; + auto checkProgramArguments(std::vector const& args) -> std::string; void createSystemFolder(const std::string& folder); @@ -52,8 +67,8 @@ class Director { SceneManager::SceneContext& context) -> std::unique_ptr; - // Ejecuta el bucle de frames de UNA escena hasta que scene.isFinished() - // sea true. Maneja delta_time, eventos (globales + escena), update y draw. - // El debug_overlay es global a todas las escenas; el Director lo posee. - static void runFrameLoop(Scene& scene, SDLManager& sdl, SceneManager::SceneContext& context, System::DebugOverlay& debug_overlay); + // Pivota a la següent escena: destrueix l'actual, llegeix context_->nextScene() + // i construeix la nova. Retorna SDL_APP_SUCCESS si la nova és EXIT o no es pot + // construir; SDL_APP_CONTINUE si tot OK. + auto advanceScene() -> SDL_AppResult; }; From 6b8f6a267d734084f22467478fa7b6b3fde6247e Mon Sep 17 00:00:00 2001 From: Sergio Valor Date: Fri, 22 May 2026 12:41:05 +0200 Subject: [PATCH 3/4] =?UTF-8?q?refactor(director):=20migra=20la=20persist?= =?UTF-8?q?=C3=A8ncia=20ConfigYaml=20al=20Director?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit main.cpp queda només amb 'Director director(argc, argv); return director.run()'. El Director crida ConfigYaml::* directament; l'struct ConfigPersistence desapareix de engine_config.hpp. La separació core/game es relaxa al Director, que és EL programa, no part del motor. --- source/core/config/engine_config.hpp | 11 ----------- source/core/system/director.cpp | 25 ++++++++++++------------- source/core/system/director.hpp | 13 +++++-------- source/game/config_yaml.hpp | 6 +++--- source/main.cpp | 27 +++------------------------ 5 files changed, 23 insertions(+), 59 deletions(-) diff --git a/source/core/config/engine_config.hpp b/source/core/config/engine_config.hpp index 72ae20d..4ba9bec 100644 --- a/source/core/config/engine_config.hpp +++ b/source/core/config/engine_config.hpp @@ -12,7 +12,6 @@ #include -#include #include namespace Config { @@ -67,14 +66,4 @@ namespace Config { bool console{false}; }; - // Capa de persistència delegada cap a l'EngineConfig. Permet al Director - // orquestrar init/load/save sense conèixer cap esquema concret (YAML, - // SQLite, ...) ni la capa que el conté (`game/config_yaml.cpp`). - struct ConfigPersistence { - std::function init_defaults; // Restaura valors per defecte - std::function set_path; // Indica on guardar - std::function load; // Llegeix path → EngineConfig - std::function save; // Escriu EngineConfig → path - }; - } // namespace Config diff --git a/source/core/system/director.cpp b/source/core/system/director.cpp index cde1a13..39317b4 100644 --- a/source/core/system/director.cpp +++ b/source/core/system/director.cpp @@ -21,6 +21,7 @@ #include "core/system/notifier.hpp" #include "core/utils/path_utils.hpp" #include "debug_overlay.hpp" +#include "game/config_yaml.hpp" #include "game/scenes/game_scene.hpp" #include "game/scenes/logo_scene.hpp" #include "game/scenes/title_scene.hpp" @@ -39,17 +40,15 @@ using SceneManager::SceneContext; using SceneType = SceneContext::SceneType; // Constructor -Director::Director(std::vector const& args, - Config::EngineConfig& cfg, - Config::ConfigPersistence persistence) - : cfg_(&cfg), - persistence_(std::move(persistence)) { +Director::Director(int argc, char* argv[]) + : cfg_(&ConfigYaml::engine_config) { std::cout << "Orni Attack - Inici\n"; // Inicialitzar opciones con valors per defecte - persistence_.init_defaults(); + ConfigYaml::init(); - // Comprovar arguments del programa + // Convertir arguments a std::vector i comprovar-los + std::vector args(argv, argv + argc); executable_path_ = checkProgramArguments(args); // Inicialitzar sistema de rutes @@ -95,10 +94,10 @@ Director::Director(std::vector const& args, createSystemFolder(std::string("jailgames/") + Project::NAME); // Establir ruta del file de configuración - persistence_.set_path(system_folder_ + "/config.yaml"); + ConfigYaml::setConfigFile(system_folder_ + "/config.yaml"); // Carregar o crear configuración - persistence_.load(); + ConfigYaml::loadFromFile(); // Inicialitzar sistema de input Input::init("data/gamecontrollerdb.txt"); @@ -120,7 +119,7 @@ Director::Director(std::vector const& args, Director::~Director() { // Guardar opciones - persistence_.save(); + ConfigYaml::saveToFile(); // Destruir subsistemes en ordre invers a la construcció. Crític: el // renderer i la finestra (dins de sdl_) han de morir abans de cridar @@ -153,8 +152,8 @@ auto Director::checkProgramArguments(std::vector const& args) cfg_->console = true; std::cout << "Mode consola activat\n"; } else if (argument == "--reset-config") { - persistence_.init_defaults(); - persistence_.save(); + ConfigYaml::init(); + ConfigYaml::saveToFile(); std::cout << "Configuración restablida als valors per defecte\n"; } } @@ -236,7 +235,7 @@ auto Director::run() -> int { // Crear gestor SDL amb la engine_config + callback de persistència // per a quan toggleVSync (F4) muti vsync. Mantenim sdl_manager agnòstic. - sdl_ = std::make_unique(initial_width, initial_height, cfg_->window.fullscreen, *cfg_, [this] { persistence_.save(); }); + sdl_ = std::make_unique(initial_width, initial_height, cfg_->window.fullscreen, *cfg_, [] { ConfigYaml::saveToFile(); }); // CRÍTIC: Forçar ocultació del cursor DESPRÉS de toda la inicialización SDL // Això evita que SDL mostre el cursor automàticament durante la creació de la finestra diff --git a/source/core/system/director.hpp b/source/core/system/director.hpp index 158eb26..de4b198 100644 --- a/source/core/system/director.hpp +++ b/source/core/system/director.hpp @@ -17,12 +17,10 @@ namespace System { class Director { public: - // `cfg` ha de viure tant com el Director (típicament owned per main). - // `persistence` encapsula init/load/save delegats a la capa concreta - // (game/ConfigYaml::*). - Director(std::vector const& args, - Config::EngineConfig& cfg, - Config::ConfigPersistence persistence); + // El Director és el programa: posseeix la configuració (via ConfigYaml) + // i orquestra tots els subsistemes. main.cpp és pur tràmit que el + // construeix i delega cap a SDL. + Director(int argc, char* argv[]); ~Director(); // Bucle principal del juego. @@ -41,8 +39,7 @@ class Director { private: std::string executable_path_; std::string system_folder_; - Config::EngineConfig* cfg_; - Config::ConfigPersistence persistence_; + Config::EngineConfig* cfg_{nullptr}; // Subsistemes que viuen tant com el Director (abans eren locals de run()). // Preparació per a la migració a SDL_MAIN_USE_CALLBACKS: amb les 4 diff --git a/source/game/config_yaml.hpp b/source/game/config_yaml.hpp index 95322b3..79ae832 100644 --- a/source/game/config_yaml.hpp +++ b/source/game/config_yaml.hpp @@ -3,9 +3,9 @@ // // La configuració runtime viu en Config::EngineConfig (core/config/). // Aquest fitxer afegeix una capa de persistència YAML que llegeix i -// escriu aquesta struct a disc. La connexió amb el Director es fa via -// Config::ConfigPersistence (lambdes a `main.cpp`), mantenint `core/` -// agnòstic respecte d'aquesta capa. +// escriu aquesta struct a disc. El Director crida ConfigYaml::* directament +// (init / setConfigFile / loadFromFile / saveToFile): la separació +// core/game queda relaxada al Director, que és EL programa, no part del motor. #pragma once diff --git a/source/main.cpp b/source/main.cpp index 58a7458..2089766 100644 --- a/source/main.cpp +++ b/source/main.cpp @@ -1,33 +1,12 @@ // main.cpp - Punt d'entrada de l'aplicació // © 2026 JailDesigner // -// Aquí orquestrem la capa de persistència (YAML via game/ConfigYaml::*) i -// injectem el resultat al Director. El Director queda independent de -// game/config_yaml.hpp i pot operar només amb Config::EngineConfig. +// El Director és EL programa: posseeix la configuració, els subsistemes i +// el bucle. main.cpp només construeix el Director i delega. -#include -#include - -#include "core/config/engine_config.hpp" #include "core/system/director.hpp" -#include "game/config_yaml.hpp" auto main(int argc, char* argv[]) -> int { - // Convertir arguments a std::vector - std::vector args(argv, argv + argc); - - // Capa de persistència delegada: lambdes prim que enllacen el contracte - // de Config::ConfigPersistence amb la implementació YAML de ConfigYaml::*. - const Config::ConfigPersistence PERSISTENCE{ - .init_defaults = [] { ConfigYaml::init(); }, - .set_path = [](const std::string& path) { ConfigYaml::setConfigFile(path); }, - .load = [] { return ConfigYaml::loadFromFile(); }, - .save = [] { return ConfigYaml::saveToFile(); }, - }; - - // El Director rep la struct d'engine_config + la capa de persistència. - // No coneix ConfigYaml:: directament. - Director director(args, ConfigYaml::engine_config, PERSISTENCE); - + Director director(argc, argv); return director.run(); } From be3d696f6036ec42e9a6c114aea3a60fc5799d38 Mon Sep 17 00:00:00 2001 From: Sergio Valor Date: Fri, 22 May 2026 12:45:12 +0200 Subject: [PATCH 4/4] feat(main): activa SDL_MAIN_USE_CALLBACKS MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit main.cpp queda amb les 4 callbacks de SDL3: AppInit construeix el Director, AppEvent enruta cada event a handleEvent(), AppIterate crida iterate(), AppQuit reabsorbeix la propietat amb unique_ptr. El Director::run() i el bucle while interns desapareixen; el bootstrap de SDLManager/Audio/Context/DebugOverlay/Notifier viu ara al final del constructor. SDL_Quit() ja no es crida explícitament — SDL ho fa després de SDL_AppQuit. --- source/core/system/director.cpp | 142 ++++++++++++-------------------- source/core/system/director.hpp | 3 - source/main.cpp | 34 ++++++-- 3 files changed, 81 insertions(+), 98 deletions(-) diff --git a/source/core/system/director.cpp b/source/core/system/director.cpp index 39317b4..5e20cd4 100644 --- a/source/core/system/director.cpp +++ b/source/core/system/director.cpp @@ -115,30 +115,72 @@ Director::Director(int argc, char* argv[]) } std::cout << '\n'; + + // === Bootstrap de finestra, audio i subsistemes de runtime === + + int initial_width = static_cast(std::round( + Defaults::Window::WIDTH * cfg_->window.zoom_factor)); + int initial_height = static_cast(std::round( + Defaults::Window::HEIGHT * cfg_->window.zoom_factor)); + + sdl_ = std::make_unique(initial_width, initial_height, cfg_->window.fullscreen, *cfg_, [] { ConfigYaml::saveToFile(); }); + + // CRÍTIC: forçar ocultació del cursor DESPRÉS d'inicialitzar SDL, + // perquè la creació de la finestra el reactiva. + if (!cfg_->window.fullscreen) { + Mouse::forceHide(); + } + + const Audio::Config AUDIO_CONFIG{ + .enabled = Defaults::Audio::ENABLED, + .volume = Defaults::Audio::VOLUME, + .music_enabled = Defaults::Audio::MUSIC_ENABLED, + .music_volume = Defaults::Audio::MUSIC_VOLUME, + .sound_enabled = Defaults::Audio::SOUND_ENABLED, + .sound_volume = Defaults::Audio::SOUND_VOLUME, + }; + Audio::init(AUDIO_CONFIG); + Audio::get()->applySettings(AUDIO_CONFIG); + + AudioResource::getMusic("title.ogg"); + AudioResource::getMusic("game.ogg"); + if (cfg_->console) { + std::cout << "Música precacheada\n"; + } + + context_ = std::make_unique(); +#ifdef _DEBUG + context_->setNextScene(SceneType::TITLE); +#else + context_->setNextScene(SceneType::LOGO); +#endif + + debug_overlay_ = std::make_unique( + sdl_->getRenderer(), + cfg_->rendering); + + System::Notifier::init(sdl_->getRenderer()); + + last_ticks_ms_ = SDL_GetTicks(); } Director::~Director() { // Guardar opciones ConfigYaml::saveToFile(); - // Destruir subsistemes en ordre invers a la construcció. Crític: el - // renderer i la finestra (dins de sdl_) han de morir abans de cridar - // SDL_Quit(). Si fossin destruïts pel destructor implícit del Director - // morirïen DESPRÉS dels Input/Audio/SDL_Quit que vénen a sota. + // Destruir subsistemes en ordre invers a la construcció. El Notifier + // referencia el renderer, així que ha de morir abans que sdl_. + // SDL_Quit() el crida SDL automàticament després de SDL_AppQuit; no + // l'hem de cridar nosaltres. current_scene_.reset(); debug_overlay_.reset(); + System::Notifier::destroy(); context_.reset(); sdl_.reset(); - // Cleanup input Input::destroy(); - - // Cleanup audio Audio::destroy(); - // Cleanup SDL - SDL_Quit(); - std::cout << "\nAdéu!\n"; } @@ -225,86 +267,6 @@ void Director::createSystemFolder(const std::string& folder) { } } -// Bucle principal del juego -auto Director::run() -> int { - // Calculate initial size from saved zoom_factor - int initial_width = static_cast(std::round( - Defaults::Window::WIDTH * cfg_->window.zoom_factor)); - int initial_height = static_cast(std::round( - Defaults::Window::HEIGHT * cfg_->window.zoom_factor)); - - // Crear gestor SDL amb la engine_config + callback de persistència - // per a quan toggleVSync (F4) muti vsync. Mantenim sdl_manager agnòstic. - sdl_ = std::make_unique(initial_width, initial_height, cfg_->window.fullscreen, *cfg_, [] { ConfigYaml::saveToFile(); }); - - // CRÍTIC: Forçar ocultació del cursor DESPRÉS de toda la inicialización SDL - // Això evita que SDL mostre el cursor automàticament durante la creació de la finestra - if (!cfg_->window.fullscreen) { - Mouse::forceHide(); - } - - // Inicializar sistema de audio (config inyectada desde Defaults) - const Audio::Config AUDIO_CONFIG{ - .enabled = Defaults::Audio::ENABLED, - .volume = Defaults::Audio::VOLUME, - .music_enabled = Defaults::Audio::MUSIC_ENABLED, - .music_volume = Defaults::Audio::MUSIC_VOLUME, - .sound_enabled = Defaults::Audio::SOUND_ENABLED, - .sound_volume = Defaults::Audio::SOUND_VOLUME, - }; - Audio::init(AUDIO_CONFIG); - Audio::get()->applySettings(AUDIO_CONFIG); // Aplicar volúmenes iniciales al motor - - // Precachear música para evitar lag al empezar - AudioResource::getMusic("title.ogg"); - AudioResource::getMusic("game.ogg"); - if (cfg_->console) { - std::cout << "Música precacheada\n"; - } - - // Crear context de escenes - context_ = std::make_unique(); -#ifdef _DEBUG - context_->setNextScene(SceneType::TITLE); -#else - context_->setNextScene(SceneType::LOGO); -#endif - - // Overlay de debug (FPS + VSync). Vive en el Director porque es global - // a todas las escenas. Toggle con F11 (visible por defecto en _DEBUG). - debug_overlay_ = std::make_unique( - sdl_->getRenderer(), - cfg_->rendering); - - // Sistema de notificacions toast: singleton accessible des d'on calgui - // (F1-F5 a sdl_manager, ESC a global_events). El renderer ha de viure - // tant com el Notifier; el destruim explícitament abans de tornar. - System::Notifier::init(sdl_->getRenderer()); - - // Comptador de delta time per al primer iterate(). - last_ticks_ms_ = SDL_GetTicks(); - - // Bucle principal: poll d'events + iterate() per frame. La primera escena - // es construeix lazy dins d'iterate() via advanceScene(). En migrar a - // SDL_MAIN_USE_CALLBACKS, aquest while desapareixerà i SDL_AppEvent/ - // SDL_AppIterate cridaran handleEvent()/iterate() directament. - while (true) { - SDL_Event event; - while (SDL_PollEvent(&event)) { - SDL_AppResult r = handleEvent(event); - if (r != SDL_APP_CONTINUE) { - System::Notifier::destroy(); - return (r == SDL_APP_SUCCESS) ? 0 : 1; - } - } - SDL_AppResult r = iterate(); - if (r != SDL_APP_CONTINUE) { - System::Notifier::destroy(); - return (r == SDL_APP_SUCCESS) ? 0 : 1; - } - } -} - auto Director::buildScene(SceneType type, SDLManager& sdl, SceneContext& context) -> std::unique_ptr { switch (type) { diff --git a/source/core/system/director.hpp b/source/core/system/director.hpp index de4b198..e418b58 100644 --- a/source/core/system/director.hpp +++ b/source/core/system/director.hpp @@ -23,9 +23,6 @@ class Director { Director(int argc, char* argv[]); ~Director(); - // Bucle principal del juego. - auto run() -> int; - // Una iteració del bucle: pivot d'escena si cal, delta time, update i // render. Retorna SDL_APP_CONTINUE per seguir, SDL_APP_SUCCESS si vol // sortir net, SDL_APP_FAILURE si no es pot recuperar. diff --git a/source/main.cpp b/source/main.cpp index 2089766..c03c04d 100644 --- a/source/main.cpp +++ b/source/main.cpp @@ -1,12 +1,36 @@ -// main.cpp - Punt d'entrada de l'aplicació +// main.cpp - Punt d'entrada amb SDL_MAIN_USE_CALLBACKS // © 2026 JailDesigner // // El Director és EL programa: posseeix la configuració, els subsistemes i -// el bucle. main.cpp només construeix el Director i delega. +// l'estat. Aquestes 4 callbacks són la fontaneria mínima que SDL3 demana +// per arrencar, processar events, iterar i tancar. + +#define SDL_MAIN_USE_CALLBACKS 1 + +#include +#include + +#include #include "core/system/director.hpp" -auto main(int argc, char* argv[]) -> int { - Director director(argc, argv); - return director.run(); +auto SDL_AppInit(void** appstate, int argc, char* argv[]) -> SDL_AppResult { + auto director = std::make_unique(argc, argv); + *appstate = director.release(); + return SDL_APP_CONTINUE; +} + +auto SDL_AppEvent(void* appstate, SDL_Event* event) -> SDL_AppResult { + auto* director = static_cast(appstate); + return director->handleEvent(*event); +} + +auto SDL_AppIterate(void* appstate) -> SDL_AppResult { + auto* director = static_cast(appstate); + return director->iterate(); +} + +void SDL_AppQuit(void* appstate, SDL_AppResult /*result*/) { + // Reabsorbim la propietat: el destructor del Director allibera tot. + std::unique_ptr director(static_cast(appstate)); }