#pragma once #include #include #include #include #include "scenes/scene.hpp" // El Director és l'únic thread del runtime. Cada iterate() fa input → // tick de l'escena actual → JD8_Flip → overlay → present → sleep al frame // target. Totes les escenes (`scenes::Scene` i `ModuleGame`) són // tick-based i no bloquegen — no hi ha fibers, mutex ni condition_variable. // Compatible amb SDL_AppIterate i amb el futur port a emscripten. class Director { public: static void init(); static void destroy(); static auto get() -> Director*; // Bucle principal clàssic (build natiu sense SDL_MAIN_USE_CALLBACKS). // Internament crida setup() + bucle d'iterate() + teardown(). Crida des de main(). void run(); // Punts d'entrada compatibles amb SDL_AppInit / SDL_AppIterate / // SDL_AppEvent / SDL_AppQuit. Permeten que el Director siga driven // per l'event loop de SDL3 en lloc d'un bucle propi — imprescindible // per al port a emscripten, on el runtime posseïx el main loop. void setup(); bool iterate(); // torna false quan el joc vol eixir void teardown(); void handleEvent(const SDL_Event& event); // Demana l'eixida (ex: segona pulsació d'ESC o SDL_QUIT) void requestQuit(); auto isQuitRequested() const -> bool { return quit_requested_; } // Consumeix el flag de "tecla polsada" (com l'antic JI_AnyKey) auto consumeKeyPressed() -> bool; // Indica si ESC està bloquejada (el joc no l'ha de veure) auto isEscBlocked() const -> bool { return esc_blocked_ || esc_swallow_until_release_; } // Pausa: mentre està activa, iterate() no avança l'escena — es // continua presentant el darrer frame amb overlay fresc. void togglePause(); auto isPaused() const -> bool { return paused_; } private: Director() = default; ~Director(); static Director* instance_; void pollAllEvents(); // drenatge amb SDL_PollEvent, només per al bucle natiu // Inicialitza info::ctx a partir de Options::game.* i comprova trick.ini. // Es crida una sola vegada des d'iterate() a la primera invocació. void initGameContext(); // Construeix l'escena apropiada segons game_state_ i info::ctx. // Retorna nullptr si l'state actual no té escena registrada (bug). std::unique_ptr createNextScene(); // Buffers persistents entre iteracions. Abans eren locals a run(), // ara són membres perquè iterate() els pot reutilitzar sense tornar-los // a reservar en cada crida del callback. Uint32 game_frame_[320 * 200]{}; Uint32 presentation_buffer_[320 * 200]{}; bool has_frame_{false}; // Estat de l'escena actual. Abans vivia al stack del GameFiber; des // de la Phase B.2 de la migració viu directament al Director. std::unique_ptr current_scene_; int game_state_{1}; // 0 = gameplay (ModuleGame), 1 = via SceneRegistry, -1 = quit Uint32 last_tick_ms_{0}; bool context_initialized_{false}; std::atomic quit_requested_{false}; std::atomic key_pressed_{false}; std::atomic esc_blocked_{false}; std::atomic paused_{false}; // Quan el menú tanca amb ESC, empassem-nos l'ESC fins que l'usuari la deixe anar, // per no fer eixir el joc al proper poll de JI_KeyPressed. std::atomic esc_swallow_until_release_{false}; // Tecles consumides pel menú (KEY_DOWN): el KEY_UP associat cal empassar-lo // per evitar que el joc (JI_AnyKey / JI_moveCheats) les veja quan el menú tanca. bool menu_keys_held_[SDL_SCANCODE_COUNT]{}; };