threads
This commit is contained in:
217
source/core/system/director.cpp
Normal file
217
source/core/system/director.cpp
Normal file
@@ -0,0 +1,217 @@
|
||||
#include "core/system/director.hpp"
|
||||
|
||||
#include <cstring>
|
||||
#include <iostream>
|
||||
|
||||
#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();
|
||||
}
|
||||
59
source/core/system/director.hpp
Normal file
59
source/core/system/director.hpp
Normal file
@@ -0,0 +1,59 @@
|
||||
#pragma once
|
||||
|
||||
#include <SDL3/SDL.h>
|
||||
|
||||
#include <atomic>
|
||||
#include <condition_variable>
|
||||
#include <cstdint>
|
||||
#include <mutex>
|
||||
#include <thread>
|
||||
|
||||
// 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<bool> quit_requested_{false};
|
||||
std::atomic<bool> game_thread_done_{false};
|
||||
std::atomic<bool> key_pressed_{false};
|
||||
std::atomic<bool> esc_blocked_{false};
|
||||
};
|
||||
Reference in New Issue
Block a user