From 63424429ca75bc9f9797266dacda09560473c563 Mon Sep 17 00:00:00 2001 From: Sergio Valor Date: Sat, 4 Apr 2026 23:03:34 +0200 Subject: [PATCH] threads --- CLAUDE.md | 69 +++++++--- CMakeLists.txt | 3 + source/core/jail/jdraw8.cpp | 4 +- source/core/jail/jinput.cpp | 154 ++++++++------------- source/core/rendering/overlay.cpp | 4 + source/core/rendering/overlay.hpp | 2 + source/core/rendering/screen.cpp | 10 ++ source/core/system/director.cpp | 217 ++++++++++++++++++++++++++++++ source/core/system/director.hpp | 59 ++++++++ source/main.cpp | 45 +------ 10 files changed, 407 insertions(+), 160 deletions(-) create mode 100644 source/core/system/director.cpp create mode 100644 source/core/system/director.hpp diff --git a/CLAUDE.md b/CLAUDE.md index 49bb5dc..33893cd 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -51,6 +51,10 @@ Flat C-style APIs (no classes), prefixed by subsystem. **Do not touch gameplay l - **JI** (`jinput`) — Input: keyboard state polling, key debouncing, cheat code detection. Filters GUI keys from game, calls `GlobalInputs::handle()` and `Mouse::updateCursorVisibility()` each update - **JF** (`jfile`) — File I/O: filesystem folder mode (`data/`) or packed resource file (`.jrf`). Config folder at `~/.config/jailgames/aee/` +### System Layer (`source/core/system/`) + +- **Director** (`director.hpp/cpp`) — **Orchestrator singleton**. Owns main thread. Launches game thread that runs `ModuleGame`/`ModuleSequence::Go()`. Emulator-style architecture: Director runs at ~60 FPS independently, polls SDL events, updates overlay, presents frames. Game thread blocks at `JD8_Flip()` → `Director::publishFrame()` until Director consumes the frame. Director is **non-blocking**: if no new frame is available, it re-presents the last known game frame with fresh overlay on top + ### Presentation Layer (`source/core/rendering/`) - **Screen** (`screen.hpp/cpp`) — Singleton. Manages SDL_Window, SDL_Renderer, SDL_Texture. Dual rendering path: SDL3GPU with shaders (primary) or SDL_Renderer fallback. Handles fullscreen, zoom, aspect ratio 4:3, integer scaling, VSync. Counts FPS and updates render info text @@ -93,23 +97,45 @@ Follows the pattern from `jaildoctors_dilemma`, persists to YAML: All key bindings are configurable via `Options::keys_gui` and stored in `config.yaml`. -### Rendering Pipeline +### Threading Model (Emulator Architecture) ``` -JD8_Flip(): - 1. palette→ARGB in pixel_data[320×200] (original engine) - 2. Screen::present(pixel_data): - a. FPS count + render info text update - b. Overlay::render(pixel_data) (notifications, render info, directly on ARGB) - c. IF GPU + shaders enabled: - - uploadPixels → scene_texture (320×200) - - [IF 4:3] stretch pass fused with upscale: scene → scaled_texture (W×factor, H×factor×1.2) - - [IF SS] upscale pass: scene → scaled_texture (W×factor, H×factor) - - PostFX or CRT-Pi shader → swapchain (with viewport letterboxing) - d. ELSE IF GPU without shaders: - - uploadPixels → clean render → swapchain - e. ELSE (fallback): - - SDL_UpdateTexture → SDL_RenderPresent +Main thread (Director) Game thread (ModuleGame/Sequence::Go()) +──────────────────── ──────────────────────────────────── +loop at ~60 FPS { loop { + SDL_PollEvent() ... game logic ... + GlobalInputs, Mouse JD8_Flip(): + if new_frame_available: palette→ARGB in pixel_data + copy to game_frame publishFrame(pixel_data) ⏸ + signal → ────────────────────→ (blocks until Director consumes) + copy game_frame → present_buffer ←──── signal_consumed + Overlay::render(present_buffer) continue game loop + Screen::present(present_buffer) } + SDL_Delay to hit 60fps +} +``` + +**Key points:** +- Director is NON-BLOCKING: if no new frame, re-presents the last one with fresh overlay +- Double buffer: `game_frame` (untouched copy from game) + `presentation_buffer` (regenerated with overlay each frame) +- Game thread pauses at `JD8_Flip()` waiting for Director — natural sync point +- SDL events processed ONLY on main thread (SDL requirement) +- `JI_Update()` no longer polls events — reads Director's state + +### Rendering Pipeline (inside Screen::present) + +``` +Screen::present(pixel_data): + 1. FPS count + render info text update + 2. IF GPU + shaders enabled: + - uploadPixels → scene_texture (320×200) + - [IF 4:3] stretch pass fused with upscale: scene → scaled_texture (W×factor, H×factor×1.2) + - [IF SS] upscale pass: scene → scaled_texture (W×factor, H×factor) + - PostFX or CRT-Pi shader → swapchain (with viewport letterboxing) + 3. ELSE IF GPU without shaders: + - uploadPixels → clean render → swapchain + 4. ELSE (fallback): + - SDL_UpdateTexture → SDL_RenderPresent ``` ### Pixel Format @@ -139,12 +165,15 @@ JD8_Flip produces ABGR byte order: `0xFF000000 + R + (G<<8) + (B<<16)`. SDL text ### Known Issues & Technical Debt -1. **ESC double-press does not work in ModuleGame**: `modulegame.cpp:136` polls `JI_KeyPressed(ESCAPE)` each frame and calls `JG_QuitSignal()` immediately. The overlay intercepts the KEY_UP event and blocks polling via `esc_blocked_`, but there is a race condition — the game's polling can fire before the block takes effect. Needs deeper integration (possibly intercepting at `JI_KeyPressed` level with state tracking across frames, or modifying the game module to use the overlay's quit flow) +1. **gif.h cannot be included twice**: Functions are not `static` or `inline`, causing multiple definition errors. Text class uses `extern` forward declarations as workaround -2. **Overlay freezes during intro sequences**: ModuleSequence does blocking loops with delays that don't call `JI_Update()`, so `Overlay::render()` doesn't execute and notifications appear frozen. Would require refactoring the original modules to use non-blocking animation — conflicts with golden rule +### Previously Fixed (kept for reference) -3. **gif.h cannot be included twice**: Functions are not `static` or `inline`, causing multiple definition errors. Text class uses `extern` forward declarations as workaround +- **ESC double-press**: Fixed by intercepting KEY_DOWN in Director, setting atomic `esc_blocked_` immediately. `JI_KeyPressed(ESCAPE)` consults this flag. No race condition possible because Director's flag wins before game polls +- **Overlay freeze during intros**: Fixed by threading model. Director runs independently at 60 FPS regardless of game delays. Double buffer avoids overlay smearing on re-presented frames -### Main Loop (`source/main.cpp`) +### Main Entry (`source/main.cpp`) -Init order: `file_setconfigfolder` → `Options::load` → `Options::loadPostFX/CrtPi` → `JG_Init` → `Screen::init` → `JD8_Init` → `JA_Init` → `Overlay::init`. Shutdown reverse. A state machine alternates between `ModuleSequence` (state 1) and `ModuleGame` (state 0). Each module's `Go()` returns the next state (-1 to quit). +Init order: `file_setconfigfolder` → `Options::load` → `Options::loadPostFX/CrtPi` → `JG_Init` → `Screen::init` → `JD8_Init` → `JA_Init` → `Overlay::init` → `Director::init` → `Director::run()` (blocks until quit). Shutdown: `Options::save` → `Director::destroy` → `Overlay::destroy` → `JA_Quit` → `JD8_Quit` → `Screen::destroy` → `JG_Finalize`. + +The state machine (alternating `ModuleSequence` state=1 and `ModuleGame` state=0) now lives inside `Director::gameThreadFunc()`, running on the game thread. diff --git a/CMakeLists.txt b/CMakeLists.txt index 0d98701..229b313 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -31,6 +31,9 @@ set(APP_SOURCES source/core/input/global_inputs.cpp source/core/input/mouse.cpp + # Core - System (nova capa) + source/core/system/director.cpp + # Game source/game/options.cpp source/game/bola.cpp diff --git a/source/core/jail/jdraw8.cpp b/source/core/jail/jdraw8.cpp index e5e3fae..9e84ea6 100644 --- a/source/core/jail/jdraw8.cpp +++ b/source/core/jail/jdraw8.cpp @@ -3,7 +3,7 @@ #include #include "core/jail/jfile.hpp" -#include "core/rendering/screen.hpp" +#include "core/system/director.hpp" #include "external/gif.h" JD8_Surface screen = NULL; @@ -153,7 +153,7 @@ void JD8_Flip() { pixel_data[x + (y * 320)] = color; } } - Screen::get()->present(pixel_data); + Director::get()->publishFrame(pixel_data); } void JD8_FreeSurface(JD8_Surface surface) { diff --git a/source/core/jail/jinput.cpp b/source/core/jail/jinput.cpp index 7541d1d..3566a27 100644 --- a/source/core/jail/jinput.cpp +++ b/source/core/jail/jinput.cpp @@ -1,99 +1,55 @@ -#include "core/jail/jinput.hpp" - -#include - -#include "core/input/global_inputs.hpp" -#include "core/input/mouse.hpp" -#include "core/jail/jgame.hpp" -#include "core/rendering/overlay.hpp" -#include "game/options.hpp" - -const bool* keystates; // = SDL_GetKeyboardState( NULL ); -SDL_Event event; -Uint8 cheat[5]; -bool key_pressed = false; -int waitTime = 0; -static bool esc_blocked_ = false; // Bloqueja ESC per polling quan l'overlay l'ha consumit - -// Comprova si un scancode pertany a les tecles reservades per a la GUI -static bool isGuiKey(SDL_Scancode sc) { - return sc == Options::keys_gui.dec_zoom || - sc == Options::keys_gui.inc_zoom || - sc == Options::keys_gui.fullscreen || - sc == Options::keys_gui.toggle_shader || - sc == Options::keys_gui.toggle_aspect_ratio || - sc == Options::keys_gui.toggle_supersampling || - sc == Options::keys_gui.next_shader || - sc == Options::keys_gui.next_shader_preset || - sc == Options::keys_gui.toggle_stretch_filter || - sc == Options::keys_gui.toggle_render_info; -} - -void JI_DisableKeyboard(Uint32 time) { - waitTime = time; -} - -void JI_moveCheats(Uint8 new_key) { - cheat[0] = cheat[1]; - cheat[1] = cheat[2]; - cheat[2] = cheat[3]; - cheat[3] = cheat[4]; - cheat[4] = new_key; -} - -void JI_Update() { - key_pressed = false; - keystates = SDL_GetKeyboardState(NULL); - - if (waitTime > 0) waitTime--; - - while (SDL_PollEvent(&event)) { - if (event.type == SDL_EVENT_QUIT) JG_QuitSignal(); - if (event.type == SDL_EVENT_KEY_UP) { - // ESC interceptat per l'overlay (doble pulsació per eixir) - if (event.key.scancode == SDL_SCANCODE_ESCAPE) { - if (Overlay::handleEscape()) { - // Consumit: primera pulsació, bloqueja ESC per polling - esc_blocked_ = true; - } else { - // Segona pulsació: desbloqueja i passa al joc - esc_blocked_ = false; - key_pressed = true; - JG_QuitSignal(); - } - } else if (!isGuiKey(event.key.scancode)) { - // Tecles normals del joc - key_pressed = true; - JI_moveCheats(event.key.scancode); - } - } - Mouse::handleEvent(event); - } - - // Desbloqueja ESC quan la tecla ja no està polsada i l'overlay ha fet timeout - if (esc_blocked_ && !keystates[SDL_SCANCODE_ESCAPE]) { - esc_blocked_ = false; - } - - Mouse::updateCursorVisibility(); - GlobalInputs::handle(); -} - -bool JI_KeyPressed(int key) { - if (waitTime > 0) return false; - // Si ESC està bloquejat per l'overlay, no la passem al joc - if (key == SDL_SCANCODE_ESCAPE && esc_blocked_) return false; - return keystates[key] != 0; -} - -bool JI_CheatActivated(const char* cheat_code) { - bool found = true; - for (size_t i = 0; i < strlen(cheat_code); i++) { - if (cheat[i] != cheat_code[i]) found = false; - } - return found; -} - -bool JI_AnyKey() { - return waitTime > 0 ? false : key_pressed; -} +#include "core/jail/jinput.hpp" + +#include + +#include "core/system/director.hpp" + +// keystates és actualitzat per SDL internament. Des del joc només fem lectures. +const bool* keystates = nullptr; +Uint8 cheat[5]; +bool key_pressed = false; +int waitTime = 0; + +void JI_DisableKeyboard(Uint32 time) { + waitTime = time; +} + +void JI_moveCheats(Uint8 new_key) { + cheat[0] = cheat[1]; + cheat[1] = cheat[2]; + cheat[2] = cheat[3]; + cheat[3] = cheat[4]; + cheat[4] = new_key; +} + +void JI_Update() { + // El director ha processat tots els events. Ací només refresquem + // el snapshot del teclat i consumim el flag de tecla polsada. + if (keystates == nullptr) { + keystates = SDL_GetKeyboardState(NULL); + } + + if (waitTime > 0) waitTime--; + + // Consumim el flag de "alguna tecla no-GUI polsada" del director + key_pressed = Director::get()->consumeKeyPressed(); +} + +bool JI_KeyPressed(int key) { + if (waitTime > 0 || keystates == nullptr) return false; + // ESC bloquejada pel Director (primera pulsació mostra notificació) + if (key == SDL_SCANCODE_ESCAPE && Director::get()->isEscBlocked()) return false; + return keystates[key] != 0; +} + +bool JI_CheatActivated(const char* cheat_code) { + bool found = true; + for (size_t i = 0; i < strlen(cheat_code); i++) { + if (cheat[i] != cheat_code[i]) found = false; + } + return found; +} + +bool JI_AnyKey() { + return waitTime > 0 ? false : key_pressed; +} diff --git a/source/core/rendering/overlay.cpp b/source/core/rendering/overlay.cpp index 93f4da8..10b5452 100644 --- a/source/core/rendering/overlay.cpp +++ b/source/core/rendering/overlay.cpp @@ -183,6 +183,10 @@ namespace Overlay { render_info_text_ = text; } + auto isEscConsumed() -> bool { + return esc_waiting_; + } + auto handleEscape() -> bool { if (!esc_waiting_) { // Primera pulsació: mostra avís i consumeix diff --git a/source/core/rendering/overlay.hpp b/source/core/rendering/overlay.hpp index 548bbdc..7dd1e30 100644 --- a/source/core/rendering/overlay.hpp +++ b/source/core/rendering/overlay.hpp @@ -19,4 +19,6 @@ namespace Overlay { // Gestió d'eixida amb doble ESC // Retorna true si l'ESC ha sigut consumit (no s'ha de passar al joc) auto handleEscape() -> bool; + // True mentre s'espera la segona pulsació d'ESC + auto isEscConsumed() -> bool; } // namespace Overlay diff --git a/source/core/rendering/screen.cpp b/source/core/rendering/screen.cpp index 48699e4..8f33464 100644 --- a/source/core/rendering/screen.cpp +++ b/source/core/rendering/screen.cpp @@ -304,6 +304,7 @@ auto Screen::getActiveShaderName() const -> const char* { } void Screen::updateRenderInfo() { + static const Uint32 start_ticks = SDL_GetTicks(); std::string driver = gpu_driver_.empty() ? "sdl" : toLower(gpu_driver_); std::string info = std::to_string(fps_.last_value) + " fps - " + driver; @@ -314,6 +315,15 @@ void Screen::updateRenderInfo() { if (Options::video.supersampling) info += " (ss)"; } + // Temps de joc: m:ss.cc + Uint32 elapsed = SDL_GetTicks() - start_ticks; + int minutes = elapsed / 60000; + int seconds = (elapsed / 1000) % 60; + int centis = (elapsed / 10) % 100; + char time_buf[32]; + snprintf(time_buf, sizeof(time_buf), " - %d:%02d.%02d", minutes, seconds, centis); + info += time_buf; + Overlay::setRenderInfoText(info.c_str()); } diff --git a/source/core/system/director.cpp b/source/core/system/director.cpp new file mode 100644 index 0000000..4e17fde --- /dev/null +++ b/source/core/system/director.cpp @@ -0,0 +1,217 @@ +#include "core/system/director.hpp" + +#include +#include + +#include "core/input/global_inputs.hpp" +#include "core/input/mouse.hpp" +#include "core/jail/jgame.hpp" +#include "core/rendering/overlay.hpp" +#include "core/rendering/screen.hpp" +#include "game/info.hpp" +#include "game/modulegame.hpp" +#include "game/modulesequence.hpp" +#include "game/options.hpp" + +// Cheats del joc original — declarats a jinput.cpp +extern void JI_moveCheats(Uint8 new_key); + +Director* Director::instance_ = nullptr; + +void Director::init() { + instance_ = new Director(); +} + +void Director::destroy() { + delete instance_; + instance_ = nullptr; +} + +auto Director::get() -> Director* { + return instance_; +} + +void Director::run() { + // Llança el game thread + game_thread_ = std::thread(&Director::gameThreadFunc, this); + + // Doble buffer: game_frame és el frame net del joc, presentation_buffer + // és el frame + overlay (es regenera cada iteració des de game_frame) + Uint32 game_frame[320 * 200]{}; + Uint32 presentation_buffer[320 * 200]{}; + bool has_frame = false; + + constexpr Uint32 TARGET_FRAME_MS = 16; // ~60 FPS + + // Bucle principal del director (no-bloquejant) + while (!game_thread_done_ && !quit_requested_) { + Uint32 frame_start = SDL_GetTicks(); + + handleEvents(); + GlobalInputs::handle(); + Mouse::updateCursorVisibility(); + + // Si l'overlay ja no bloqueja ESC (timeout), desbloquegem + if (esc_blocked_ && !Overlay::isEscConsumed()) { + esc_blocked_ = false; + } + + // Consumeix un frame nou si n'hi ha un disponible (no bloqueja) + bool new_frame = false; + { + std::lock_guard lock(mutex_); + if (frame_ready_ && latest_frame_ != nullptr) { + memcpy(game_frame, latest_frame_, sizeof(game_frame)); + frame_ready_ = false; + frame_consumed_ = true; + has_frame = true; + new_frame = true; + } + } + if (new_frame) { + frame_consumed_cv_.notify_one(); // desbloqueja el joc + } + + // Presenta sempre: parteix del frame net del joc, afegeix overlay i envia + if (has_frame) { + memcpy(presentation_buffer, game_frame, sizeof(presentation_buffer)); + Screen::get()->present(presentation_buffer); + } + + // Limita a ~60 FPS + Uint32 elapsed = SDL_GetTicks() - frame_start; + if (elapsed < TARGET_FRAME_MS) { + SDL_Delay(TARGET_FRAME_MS - elapsed); + } + } + + // Assegura que el game thread ix (despertar-lo per si està esperant) + quit_requested_ = true; + JG_QuitSignal(); + { + std::lock_guard lock(mutex_); + frame_consumed_ = true; + } + frame_consumed_cv_.notify_all(); + + if (game_thread_.joinable()) { + game_thread_.join(); + } +} + +void Director::handleEvents() { + SDL_Event event; + while (SDL_PollEvent(&event)) { + if (event.type == SDL_EVENT_QUIT) { + JG_QuitSignal(); + requestQuit(); + } + // ESC: interceptem KEY_DOWN per bloquejar-la ABANS que el joc la veja per polling + if (event.type == SDL_EVENT_KEY_DOWN && event.key.scancode == SDL_SCANCODE_ESCAPE && !event.key.repeat) { + esc_blocked_ = true; // Bloqueja ESC per polling immediatament + if (!Overlay::isEscConsumed()) { + // Primera pulsació: mostra notificació + Overlay::handleEscape(); + } else { + // Segona pulsació: senyal d'eixida al joc + esc_blocked_ = false; + key_pressed_ = true; + JG_QuitSignal(); + } + continue; // no processa més aquest event + } + if (event.type == SDL_EVENT_KEY_UP) { + if (event.key.scancode == SDL_SCANCODE_ESCAPE) { + // Ja processat a KEY_DOWN, només deixem netejar el bloqueig + // quan l'overlay faça timeout + continue; + } else { + // Comprova si és una tecla GUI (no passa al joc) + const auto sc = event.key.scancode; + const bool is_gui_key = (sc == Options::keys_gui.dec_zoom || + sc == Options::keys_gui.inc_zoom || + sc == Options::keys_gui.fullscreen || + sc == Options::keys_gui.toggle_shader || + sc == Options::keys_gui.toggle_aspect_ratio || + sc == Options::keys_gui.toggle_supersampling || + sc == Options::keys_gui.next_shader || + sc == Options::keys_gui.next_shader_preset || + sc == Options::keys_gui.toggle_stretch_filter || + sc == Options::keys_gui.toggle_render_info); + if (!is_gui_key) { + key_pressed_ = true; + JI_moveCheats(sc); + } + } + } + Mouse::handleEvent(event); + } +} + +void Director::publishFrame(Uint32* pixels) { + { + std::lock_guard lock(mutex_); + latest_frame_ = pixels; + frame_ready_ = true; + frame_consumed_ = false; + } + frame_produced_cv_.notify_one(); + + // Espera que el director consumeixca el frame + { + std::unique_lock lock(mutex_); + frame_consumed_cv_.wait(lock, [this] { + return frame_consumed_ || quit_requested_; + }); + } +} + +void Director::requestQuit() { + quit_requested_ = true; + JG_QuitSignal(); + frame_consumed_cv_.notify_all(); + frame_produced_cv_.notify_all(); +} + +auto Director::consumeKeyPressed() -> bool { + return key_pressed_.exchange(false); +} + +void Director::gameThreadFunc() { + info::num_habitacio = Options::game.habitacio_inicial; + info::num_piramide = Options::game.piramide_inicial; + info::diners = 0; + info::diamants = 0; + info::vida = Options::game.vides; + info::momies = 0; + info::nou_personatge = false; + info::pepe_activat = false; + + FILE* ini = fopen("trick.ini", "rb"); + if (ini != nullptr) { + info::nou_personatge = true; + fclose(ini); + } + + int gameState = 1; + while (gameState != -1 && !quit_requested_) { + switch (gameState) { + case 0: { + auto* moduleGame = new ModuleGame(); + gameState = moduleGame->Go(); + delete moduleGame; + break; + } + case 1: { + auto* moduleSequence = new ModuleSequence(); + gameState = moduleSequence->Go(); + delete moduleSequence; + break; + } + } + } + + game_thread_done_ = true; + // Despertar el director per si esperava un frame + frame_produced_cv_.notify_all(); +} diff --git a/source/core/system/director.hpp b/source/core/system/director.hpp new file mode 100644 index 0000000..dc70fda --- /dev/null +++ b/source/core/system/director.hpp @@ -0,0 +1,59 @@ +#pragma once + +#include + +#include +#include +#include +#include +#include + +// El Director és el thread principal que controla la presentació i els inputs. +// Executa el joc en un thread secundari (game thread) com si fos una "fibra emulada": +// el joc produeix un frame, es bloqueja a JD8_Flip(), i el director el presenta +// abans de donar-li via per produir el següent. +class Director { + public: + static void init(); + static void destroy(); + static auto get() -> Director*; + + // Bucle principal del director. Crida des de main(). + void run(); + + // Invocat pel game thread des de JD8_Flip(). Bloqueja fins que el director + // consumeix el frame i dona via per produir el següent. + void publishFrame(Uint32* pixels); + + // Demana l'eixida (ex: segona pulsació d'ESC o SDL_QUIT) + void requestQuit(); + + // 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_; } + + private: + Director() = default; + ~Director() = default; + + static Director* instance_; + + void gameThreadFunc(); + void handleEvents(); + + std::thread game_thread_; + std::mutex mutex_; + std::condition_variable frame_produced_cv_; + std::condition_variable frame_consumed_cv_; + + Uint32* latest_frame_{nullptr}; + bool frame_ready_{false}; + bool frame_consumed_{true}; + + std::atomic quit_requested_{false}; + std::atomic game_thread_done_{false}; + std::atomic key_pressed_{false}; + std::atomic esc_blocked_{false}; +}; diff --git a/source/main.cpp b/source/main.cpp index 910032d..69b12f7 100644 --- a/source/main.cpp +++ b/source/main.cpp @@ -1,20 +1,16 @@ #include #include -#include "core/input/global_inputs.hpp" #include "core/jail/jail_audio.hpp" #include "core/jail/jdraw8.hpp" #include "core/jail/jfile.hpp" #include "core/jail/jgame.hpp" #include "core/rendering/overlay.hpp" #include "core/rendering/screen.hpp" -#include "game/defines.hpp" -#include "game/info.hpp" -#include "game/modulegame.hpp" -#include "game/modulesequence.hpp" +#include "core/system/director.hpp" #include "game/options.hpp" -int main(int argc, char* args[]) { +int main(int /*argc*/, char* /*args*/[]) { srand(unsigned(time(NULL))); // Crea la carpeta de configuració i carrega les opcions @@ -33,43 +29,14 @@ int main(int argc, char* args[]) { JD8_Init(); JA_Init(48000, SDL_AUDIO_S16, 2); Overlay::init(); + Director::init(); - info::num_habitacio = Options::game.habitacio_inicial; - info::num_piramide = Options::game.piramide_inicial; - info::diners = 0; - info::diamants = 0; - info::vida = Options::game.vides; - info::momies = 0; - info::nou_personatge = false; - info::pepe_activat = false; - - FILE* ini = fopen("trick.ini", "rb"); - if (ini != NULL) { - info::nou_personatge = true; - fclose(ini); - } - - int gameState = 1; - - while (gameState != -1) { - switch (gameState) { - case 0: - ModuleGame* moduleGame; - moduleGame = new ModuleGame(); - gameState = moduleGame->Go(); - delete moduleGame; - break; - case 1: - ModuleSequence* moduleSequence; - moduleSequence = new ModuleSequence(); - gameState = moduleSequence->Go(); - delete moduleSequence; - break; - } - } + // Arranca el Director: crea game thread, bucle principal, sincronització de frames + Director::get()->run(); Options::saveToFile(); + Director::destroy(); Overlay::destroy(); JA_Quit(); JD8_Quit();