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 4eea305..5e20cd4 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"); @@ -116,21 +115,72 @@ Director::Director(std::vector const& args, } 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 - persistence_.save(); + ConfigYaml::saveToFile(); + + // 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"; } @@ -144,8 +194,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"; } } @@ -217,75 +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. - SDLManager sdl(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 - 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 - SceneContext context; -#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). - System::DebugOverlay debug_overlay(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()); - - // 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) { - break; - } - runFrameLoop(*scene, sdl, context, debug_overlay); - } - - SceneManager::actual = SceneType::EXIT; - System::Notifier::destroy(); - return 0; -} - auto Director::buildScene(SceneType type, SDLManager& sdl, SceneContext& context) -> std::unique_ptr { switch (type) { @@ -301,55 +282,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 712202a..e418b58 100644 --- a/source/core/system/director.hpp +++ b/source/core/system/director.hpp @@ -1,5 +1,7 @@ #pragma once +#include + #include #include #include @@ -15,22 +17,38 @@ 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. - 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_; - 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 + // 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_; + + Uint64 last_ticks_ms_{0}; + bool wants_quit_{false}; auto checkProgramArguments(std::vector const& args) -> std::string; @@ -43,8 +61,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; }; 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..c03c04d 100644 --- a/source/main.cpp +++ b/source/main.cpp @@ -1,33 +1,36 @@ -// main.cpp - Punt d'entrada de l'aplicació +// main.cpp - Punt d'entrada amb SDL_MAIN_USE_CALLBACKS // © 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 +// l'estat. Aquestes 4 callbacks són la fontaneria mínima que SDL3 demana +// per arrencar, processar events, iterar i tancar. -#include -#include +#define SDL_MAIN_USE_CALLBACKS 1 + +#include +#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); - - 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)); }