From a8ccb3fae1120b3fe9b029e26b45f8e69cbbbce2 Mon Sep 17 00:00:00 2001 From: Sergio Valor Date: Mon, 25 May 2026 17:29:42 +0200 Subject: [PATCH 1/2] feat(servei): reinici real via execv --- CMakeLists.txt | 1 + source/core/system/relaunch.cpp | 51 +++++++++++++++++++++++++++++++++ source/core/system/relaunch.hpp | 28 ++++++++++++++++++ source/game/ui/service_menu.cpp | 18 ++++++------ source/main.cpp | 10 +++++-- 5 files changed, 97 insertions(+), 11 deletions(-) create mode 100644 source/core/system/relaunch.cpp create mode 100644 source/core/system/relaunch.hpp diff --git a/CMakeLists.txt b/CMakeLists.txt index b6cc044..ed85500 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -65,6 +65,7 @@ set(ENTITY_SOURCES set(APP_SOURCES source/main.cpp source/core/system/director.cpp + source/core/system/relaunch.cpp source/core/audio/audio.cpp source/core/audio/audio_adapter.cpp source/core/audio/audio_effects.cpp diff --git a/source/core/system/relaunch.cpp b/source/core/system/relaunch.cpp new file mode 100644 index 0000000..a41221b --- /dev/null +++ b/source/core/system/relaunch.cpp @@ -0,0 +1,51 @@ +// relaunch.cpp - Implementació del reinici en calent + +#include "core/system/relaunch.hpp" + +#include +#include +#include +#include + +#ifdef _WIN32 +#include // _execv +#else +#include // execv +#endif + +namespace { + + // Estat global (process-scope). Aquesta TU és l'única que gestiona el + // reinici, així que els statics interns no s'escapen. + char** g_argv = nullptr; + bool g_requested = false; + +} // namespace + +namespace System::Relaunch { + + void setArgv(int /*argc*/, char** argv) { + g_argv = argv; + } + + void request() { + g_requested = true; + } + + void execIfRequested() { + if (!g_requested || g_argv == nullptr || g_argv[0] == nullptr) { + return; + } + std::cout << "[Relaunch] Reiniciant " << g_argv[0] << "...\n"; +#ifdef _WIN32 + _execv(g_argv[0], g_argv); +#else + execv(g_argv[0], g_argv); +#endif + // Si arribem aquí, execv ha fallat. Tots els subsistemes ja estan + // destruïts; sortim amb error i el shell rebrà el codi. + std::cerr << "[Relaunch] Ha fallat: " << std::strerror(errno) << '\n'; + std::exit(EXIT_FAILURE); + } + +} // namespace System::Relaunch diff --git a/source/core/system/relaunch.hpp b/source/core/system/relaunch.hpp new file mode 100644 index 0000000..982428c --- /dev/null +++ b/source/core/system/relaunch.hpp @@ -0,0 +1,28 @@ +// relaunch.hpp - Reinici en calent del procés (execv) +// +// Helper desacoblat perquè el menú de servei demani un reinici sense conèixer +// ni Director ni main.cpp. Patró d'ús: +// +// main() → Relaunch::setArgv(argc, argv) (a l'arrencada) +// ServiceMenu → Relaunch::request() (en activar "Reiniciar") +// main() → Relaunch::execIfRequested() (al final de SDL_AppQuit) +// +// L'execv() reemplaça el procés actual: si torna, ha fallat. Es crida després +// de destruir el Director per garantir que GPU/àudio/SDL ja són morts. + +#pragma once + +namespace System::Relaunch { + + // Emmagatzema l'argv original. Cal cridar-ho una vegada des de main. + void setArgv(int argc, char** argv); + + // Demana un reinici (no actua immediatament; només marca el flag). + void request(); + + // Si hi ha reinici demanat i tenim argv vàlid, fa execv. En cas d'èxit + // no torna. Si execv falla, registra l'error per stderr i surt amb + // EXIT_FAILURE (no podem reprendre — els subsistemes ja estan destruïts). + void execIfRequested(); + +} // namespace System::Relaunch diff --git a/source/game/ui/service_menu.cpp b/source/game/ui/service_menu.cpp index aa7cd56..54ad311 100644 --- a/source/game/ui/service_menu.cpp +++ b/source/game/ui/service_menu.cpp @@ -15,6 +15,7 @@ #include "core/rendering/screen.hpp" #include "core/rendering/text.hpp" #include "core/system/event_buffer.hpp" +#include "core/system/relaunch.hpp" #include "core/ui/ui_config.hpp" #include "game/gameplay_config.hpp" #include "game/options.hpp" @@ -1228,18 +1229,17 @@ auto ServiceMenu::buildSystem() -> Page { page.items.push_back(makeLabel( std::string("ver.") + Texts::VERSION + " (" + Version::GIT_HASH + ")")); - // Reiniciar el joc: tornem al LOGO amb `reset=true` perquè LogoScene - // ature música i sons abans de mostrar-se. Tanquem el menú a la - // mateixa acció — Director recollirà la transició al final del frame - // via `takePendingSceneRequest()` i reconstruirà l'escena. + // Reinici real del procés: marquem el flag i empenyem QUIT. SDL_AppQuit + // destruirà tots els subsistemes i llavors farà execv (al final, amb tot + // net) per substituir el binari per ell mateix. Si execv falla, el procés + // surt amb error. page.items.push_back(makeAction( "menu.items.restart", []() { - ServiceMenu::get().requestSceneTransition(ExitRequest{ - .next = SceneId::LOGO, - .params = LogoParams{.reset = true}, - }); - ServiceMenu::get().close(); + System::Relaunch::request(); + SDL_Event quit_event{}; + quit_event.type = SDL_EVENT_QUIT; + EventBuffer::events.push_back(quit_event); })); page.items.push_back(makeAction( diff --git a/source/main.cpp b/source/main.cpp index 6651884..ba6068a 100644 --- a/source/main.cpp +++ b/source/main.cpp @@ -7,11 +7,13 @@ #include "core/system/director.hpp" #include "core/system/event_buffer.hpp" +#include "core/system/relaunch.hpp" #include "utils/log.hpp" auto SDL_AppInit(void** appstate, int argc, char* argv[]) -> SDL_AppResult { - (void)argc; - (void)argv; + // Desem argv perquè el menú de servei pugui demanar un reinici en calent + // (execv) sense haver de conèixer ni Director ni main. + System::Relaunch::setArgv(argc, argv); // Logger primer de tot perquè l'arrencada del Director ja emet logs. Log::init(); @@ -45,6 +47,10 @@ void SDL_AppQuit(void* appstate, SDL_AppResult result) { { std::unique_ptr director(static_cast(appstate)); } + // Si el menú va demanar reinici, fem execv ara que tot està net. En èxit + // no torna (el procés és reemplaçat) i el "Bye!" no s'imprimeix — correcte, + // no és una sortida. + System::Relaunch::execIfRequested(); // Marcador de fi: ha de ser l'ÚLTIMA línia de log del programa. Log::raw(Log::Level::INFO, "Bye!"); } From 0c6bebe8dccd46aba3b3b0b580f8505a65a7e693 Mon Sep 17 00:00:00 2001 From: Sergio Valor Date: Mon, 25 May 2026 17:34:32 +0200 Subject: [PATCH 2/2] refactor(servei): elimina infra del fake reset --- source/core/system/director.cpp | 16 ++-------------- source/game/scenes/logo_scene.cpp | 10 ++-------- source/game/scenes/logo_scene.hpp | 6 +----- source/game/scenes/scene_flow.hpp | 10 +--------- source/game/ui/service_menu.hpp | 20 -------------------- 5 files changed, 6 insertions(+), 56 deletions(-) diff --git a/source/core/system/director.cpp b/source/core/system/director.cpp index 9d0a9c6..edc951c 100644 --- a/source/core/system/director.cpp +++ b/source/core/system/director.cpp @@ -466,16 +466,6 @@ void Director::processMenuInput() { void Director::switchSceneIfRequested() { if (!current_scene_) { return; } - // Prioritat al menú: si un item del ServiceMenu ha encuat una - // transició (típicament "Reiniciar" → LOGO amb reset), s'aplica - // primer. Substitueix l'`ExitRequest` que pugui haver publicat - // l'escena en aquest mateix frame — l'usuari ha cancel·lat l'estat - // de joc explícitament i no ens interessa mantenir-ne la sortida. - if (auto menu_req = ServiceMenu::get().takePendingSceneRequest(); menu_req.has_value()) { - Log::info("Director", "scene transition (menu) → {}", toString(menu_req->next)); - current_scene_ = buildScene(menu_req->next, menu_req->params); - return; - } // L'escena ha publicat un `ExitRequest` dins de `exit_request_`. // Director el desempaqueta i construeix la nova escena via // `buildScene`. Si la `std::any_cast` falla (tipus inesperat), el @@ -498,10 +488,8 @@ auto Director::buildScene(SceneId id, const SceneParams& params) -> std::unique_ // ctors antics no l'utilitzen directament. Quan totes estiguen // migrades a `ExitRequest`, aquestes lambdes desapareixeran. switch (id) { - case SceneId::LOGO: { - const auto& p = std::get(params); - return std::make_unique(logo_full_surface_, p.reset); - } + case SceneId::LOGO: + return std::make_unique(logo_full_surface_); case SceneId::TITLE: return std::make_unique(placeholder_text_, copyright_text_, title_bg_surface_, game_logo_surface_, mini_logo_surface_); case SceneId::INTRO: { diff --git a/source/game/scenes/logo_scene.cpp b/source/game/scenes/logo_scene.cpp index 904c898..bf10dcd 100644 --- a/source/game/scenes/logo_scene.cpp +++ b/source/game/scenes/logo_scene.cpp @@ -26,15 +26,9 @@ namespace { } // namespace -LogoScene::LogoScene(std::shared_ptr logo, bool reset) +LogoScene::LogoScene(std::shared_ptr logo) : logo_(std::move(logo)) { - Log::info("scene", "{} start{}", SCENE_NAME, reset ? " (reset)" : ""); - if (reset) { - if (auto* audio = Audio::get(); audio != nullptr) { - audio->stopMusic(); - audio->stopAllSounds(); - } - } + Log::info("scene", "{} start", SCENE_NAME); // Arrenquem en BLACK_INTRO: ni so ni efecte. El glitch sound i el // begin() de scan_ es disparen quan entrem a SCAN. } diff --git a/source/game/scenes/logo_scene.hpp b/source/game/scenes/logo_scene.hpp index 380e7f4..0e0a053 100644 --- a/source/game/scenes/logo_scene.hpp +++ b/source/game/scenes/logo_scene.hpp @@ -29,13 +29,9 @@ class Surface; // Skip: qualsevol tecla no reservada salta directament a la transició // (sense fade-out) — el TitleScene té el seu propi `intro.fade_in_ms`, // així que la transició queda visualment continua igualment. -// -// Si arriba amb `reset=true` (acció "Reiniciar" del menú de servei), -// atura música i tots els sons al constructor — el següent LOGO arrenca -// silenciós encara que veníssem del joc en marxa. class LogoScene : public Scene { public: - LogoScene(std::shared_ptr logo, bool reset); + explicit LogoScene(std::shared_ptr logo); ~LogoScene() override; void update(int delta_ms) override; diff --git a/source/game/scenes/scene_flow.hpp b/source/game/scenes/scene_flow.hpp index 46facd5..e43212c 100644 --- a/source/game/scenes/scene_flow.hpp +++ b/source/game/scenes/scene_flow.hpp @@ -30,15 +30,7 @@ enum class SceneId : std::uint8_t { // rep cap dada de l'anterior; en cas contrari, només els camps que // fluixen pel canal de transició (els recursos, defaults i config // interna els resol Director quan construeix). -struct LogoParams { - // Quan val `true`, indica que aquesta entrada al LOGO és una reinicialització - // completa (acció "Reiniciar" del menú de servei): l'escena haurà d'aturar - // música i sons abans de mostrar-se. En el flux normal LOGO→TITLE→… es - // queda a `false` perquè qualsevol música prèvia (p. ex. la pista de - // TITLE durant l'attract-out) continui sonant fins que la nova escena la - // substituïsca. - bool reset{false}; -}; +struct LogoParams {}; struct TitleParams {}; struct IntroParams { DebugConfig::StartingPlayers starting_players{DebugConfig::StartingPlayers::BOTH}; diff --git a/source/game/ui/service_menu.hpp b/source/game/ui/service_menu.hpp index 6be6118..84b66fe 100644 --- a/source/game/ui/service_menu.hpp +++ b/source/game/ui/service_menu.hpp @@ -6,13 +6,11 @@ #include #include #include -#include #include #include #include "core/input/define_buttons.hpp" #include "core/rendering/surface.hpp" // Palette -#include "game/scenes/scene_flow.hpp" class Text; @@ -75,19 +73,6 @@ class ServiceMenu { // `define_buttons_->enable(...)` de coffee_crisis_arcade_edition. void startDefineButtons(SDL_Gamepad* pad, const std::string& name, const std::string& path); - // Bústia de transició d'escena des del menú: alguns items (p.ex. - // "Reiniciar" a Sistema) no es poden expressar com a `ExitRequest` de - // l'escena actual perquè el menú és global i no té handle a l'escena. - // Director consulta `takePendingSceneRequest()` cada frame i, si hi ha - // valor, força la transició — el mateix camí que per a `ExitRequest` - // d'una escena, però el dispara el menú directament. - void requestSceneTransition(ExitRequest req) { pending_request_ = req; } - [[nodiscard]] auto takePendingSceneRequest() -> std::optional { - auto out = pending_request_; - pending_request_.reset(); - return out; - } - private: // --- Struct d'un item d'una pàgina --- // @@ -306,9 +291,4 @@ class ServiceMenu { // persisteix la nova configuració via `GamepadBindings`. Replica el // patró `define_buttons_` de coffee_crisis_arcade_edition. DefineButtons define_buttons_; - - // Sol·licitud de canvi d'escena pendent (vore `requestSceneTransition`). - // La consumeix Director al final del frame; si l'item no força la - // transició, queda buida. - std::optional pending_request_; };