#include "core/system/director.hpp" #include #include #include #include #include #include #include #include #include "core/rendering/screen.hpp" #include "core/resources/resource_cache.hpp" #include "core/scene/scene.hpp" #include "core/system/event_buffer.hpp" #include "game/options.hpp" #include "game/scenes/game_scene.hpp" #include "utils/defaults.hpp" Director::Director() { if (!SDL_Init(0)) { std::fprintf(stderr, "SDL_Init failed: %s\n", SDL_GetError()); } // Prepara la carpeta de sistema (~/.config/jailgames/aee_2026/) i carrega // les opcions; si no existeix config.yaml el crea amb els defaults. createSystemFolder(); Options::setConfigFile(system_folder_ + "config.yaml"); if (!Options::loadFromFile()) { std::cerr << "Director: no s'ha pogut carregar ni desar config.yaml a " << system_folder_ << '\n'; } // Carrega els presets de PostFX des de shaders/postfx.yaml; si no existeix // es crea amb presets per defecte (off, crt, ntsc). Options::setPostFXFile(system_folder_ + Defaults::Video::POSTFX_FILE); if (!Options::loadPostFXFromFile()) { std::cerr << "Director: no s'ha pogut carregar ni desar postfx.yaml.\n"; } // Presets del shader CRT-Pi (shaders/crtpi.yaml). Options::setCrtPiFile(system_folder_ + Defaults::Video::CRTPI_FILE); if (!Options::loadCrtPiFromFile()) { std::cerr << "Director: no s'ha pogut carregar ni desar crtpi.yaml.\n"; } // Screen es configura ara (subsystem + finestra + GPU/renderer); la seua // destrucció la fa aquest Director (veure ~Director). Res de static dtor. Screen::get().configure(Options::buildScreenConfig()); Screen::get().setActiveShader(Options::buildActiveShader()); Screen::get().applyPostFXParams(Options::buildPostFXParams()); Screen::get().applyCrtPiParams(Options::buildCrtPiParams()); (void)Resource::Cache::get(); current_scene_ = std::make_unique(); last_ticks_ms_ = SDL_GetTicks(); } Director::~Director() { // Ordre imprescindible: primer l'escena (pot tenir recursos), després // Screen (GPU device, finestra, subsystem de vídeo), i finalment SDL_Quit. // Fer-ho així -- NO via atexit -- evita crashes a vkDestroyInstance quan // el runtime desfà les llibreries abans que corren els destructors estàtics. current_scene_.reset(); Screen::get().shutdown(); SDL_Quit(); } void Director::createSystemFolder() { // Volem la config a $XDG_CONFIG_HOME/jailgames/aee_2026/ (defecte // ~/.config/jailgames/aee_2026/). SDL_GetPrefPath apunta a XDG_DATA_HOME // (.local/share), que és el lloc per a dades, no per a configuració. const char* xdg = std::getenv("XDG_CONFIG_HOME"); std::string base; if (xdg != nullptr && *xdg != '\0') { base = xdg; } else { const char* home = std::getenv("HOME"); base = (home != nullptr) ? std::string(home) + "/.config" : "."; } system_folder_ = base + "/jailgames/aee_2026/"; std::error_code error; std::filesystem::create_directories(system_folder_, error); // Subcarpeta per als presets de shaders. std::filesystem és tolerant si ja existeix. std::filesystem::create_directories(system_folder_ + "shaders/", error); if (error) { std::cerr << "Director: no s'ha pogut crear " << system_folder_ << ": " << error.message() << '\n'; } } void Director::processEvents() { const bool QUIT_REQUESTED = std::ranges::any_of( EventBuffer::events, [](const SDL_Event& event) { return event.type == SDL_EVENT_QUIT; }); if (QUIT_REQUESTED) { current_scene_.reset(); } } void Director::switchSceneIfRequested() { if (!current_scene_) { return; } if (auto next = current_scene_->nextScene()) { current_scene_ = std::move(next); } } auto Director::iterate() -> SDL_AppResult { processEvents(); if (!current_scene_) { EventBuffer::events.clear(); return SDL_APP_SUCCESS; } const std::uint64_t NOW_MS = SDL_GetTicks(); int delta_ms = static_cast(NOW_MS - last_ticks_ms_); delta_ms = std::max(delta_ms, 0); last_ticks_ms_ = NOW_MS; current_scene_->update(delta_ms); current_scene_->render(); if (current_scene_->wantsQuit()) { EventBuffer::events.clear(); return SDL_APP_SUCCESS; } switchSceneIfRequested(); EventBuffer::events.clear(); return SDL_APP_CONTINUE; }